diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b645d057175..b819a07b72e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash - uses: actions-rs/toolchain@v1 with: profile: default diff --git a/.github/workflows/msrv.yml b/.github/workflows/msrv.yml index bf86d3b0719..ae0a0a9ed6d 100644 --- a/.github/workflows/msrv.yml +++ b/.github/workflows/msrv.yml @@ -22,6 +22,9 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash - uses: actions-rs/toolchain@v1 with: profile: minimal diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6257e4fdb2c..a44e8299fb8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -121,6 +121,10 @@ jobs: run: | ci/macos-install-packages + - run: rustup set auto-self-update disable + if: contains(runner.os, 'windows') + shell: bash + - name: Install Rust uses: actions-rs/toolchain@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 07256bec33d..c497f0f1085 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,22 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.13.0 (2022-07-22) + +### New Features + + - `gix config` with section and sub-section filtering. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. ### Commit Statistics - - 12 commits contributed to the release over the course of 61 calendar days. - - 67 days passed between releases. - - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) + - 37 commits contributed to the release over the course of 101 calendar days. + - 107 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) ### Thanks Clippy -[Clippy](https://github.com/rust-lang/rust-clippy) helped 2 times to make code idiomatic. +[Clippy](https://github.com/rust-lang/rust-clippy) helped 3 times to make code idiomatic. ### Commit Details @@ -37,7 +43,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - refactor ([`3ff991d`](https://github.com/Byron/gitoxide/commit/3ff991d0ca0d63632fc5710680351840f51c14c3)) - frame for `gix repo exclude query` ([`a331314`](https://github.com/Byron/gitoxide/commit/a331314758629a93ba036245a5dd03cf4109dc52)) - make fmt ([`50ff7aa`](https://github.com/Byron/gitoxide/commit/50ff7aa7fa86e5e2a94fb15aab86470532ac3f51)) + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - fix journey tests after `gix` restructuring ([`59b95c9`](https://github.com/Byron/gitoxide/commit/59b95c94aacac174e374048b7d11d2c0984a19e0)) + - `gix config` with section and sub-section filtering. ([`eda39ec`](https://github.com/Byron/gitoxide/commit/eda39ec7d736d49af1ad9e2ad775e4aa12b264b7)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - refactor ([`a437abe`](https://github.com/Byron/gitoxide/commit/a437abe8e77ad07bf25a16f19ca046ebdaef42d6)) + - move 'exclude' up one level and dissolve 'repo' subcommand ([`8e5b796`](https://github.com/Byron/gitoxide/commit/8e5b796ea3fd760839f3c29a4f65bb42b1f3e893)) + - move 'mailmap' up one level ([`5cf08ce`](https://github.com/Byron/gitoxide/commit/5cf08ce3d04d635bbfee169cb77ce259efbf6bc3)) + - move 'odb' up one level ([`0ed65da`](https://github.com/Byron/gitoxide/commit/0ed65da9b66d4cc3c85d3b70fa4bc383c7a0d1a3)) + - move 'tree' up one level ([`38a8350`](https://github.com/Byron/gitoxide/commit/38a8350d75720a8455e9c55d12f7cdf4b1742e56)) + - move 'commit' up one level ([`72876f1`](https://github.com/Byron/gitoxide/commit/72876f1fd65efc816b704db6880ab881c89cff01)) + - move 'verify' up one level ([`ac7d99a`](https://github.com/Byron/gitoxide/commit/ac7d99ac42ff8561e81f476856d0bbe86b5fa627)) + - move 'revision' one level up ([`c9c78e8`](https://github.com/Byron/gitoxide/commit/c9c78e86c387c09838404c90de420892f41f4356)) + - move 'remote' to 'free' ([`8967fcd`](https://github.com/Byron/gitoxide/commit/8967fcd009260c2d32881866244ba673894775f2)) + - move commitgraph to 'free' ([`f99c3b2`](https://github.com/Byron/gitoxide/commit/f99c3b29cea30f1cbbea7e5855abfec3de6ca630)) + - move index to 'free' ([`83585bd`](https://github.com/Byron/gitoxide/commit/83585bdfccdc42b5307255b2d56d8cb12d4136cb)) + - move 'pack' to 'free' ([`1cdecbc`](https://github.com/Byron/gitoxide/commit/1cdecbc583ae412e7f25cade73b46e00a182125f)) + - migrate mailmap to the new 'free' section ([`141c5f1`](https://github.com/Byron/gitoxide/commit/141c5f1145f9d3864e2d879089c66c62f38a2b5d)) + - first step towards moving all repository-commands one level up. ([`f4e1810`](https://github.com/Byron/gitoxide/commit/f4e1810fb711d57778be79c88f49aa583821abab)) + - make obvious what plumbing and porcelain really are ([`faaf791`](https://github.com/Byron/gitoxide/commit/faaf791cc960c37b180ddef9792dfabc7d106138)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) + - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) + - frame for `gix repo rev explain` ([`12e6277`](https://github.com/Byron/gitoxide/commit/12e6277a65a6572a0e43e8324d2d1dfb23d0bb40)) * **Uncategorized** + - thanks clippy ([`48b3f4a`](https://github.com/Byron/gitoxide/commit/48b3f4a5077ba66d47482a80e505feb69e9ac9fc)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) - make fmt ([`251b6df`](https://github.com/Byron/gitoxide/commit/251b6df5dbdda24b7bdc452085f808f3acef69d8)) - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) - thanks clippy ([`056e8d2`](https://github.com/Byron/gitoxide/commit/056e8d26dc511fe7939ec87c62ef16aafd34fa9c)) diff --git a/Cargo.lock b/Cargo.lock index 1a5f65bad4a..b6868fd8b7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -943,6 +943,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.21" @@ -959,6 +974,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -986,6 +1012,29 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -1018,7 +1067,7 @@ dependencies = [ [[package]] name = "git-actor" -version = "0.10.1" +version = "0.11.0" dependencies = [ "bstr", "btoi", @@ -1045,8 +1094,8 @@ dependencies = [ "git-path", "git-quote", "git-testtools", - "quick-error", "serde", + "thiserror", "unicode-bom", ] @@ -1100,7 +1149,7 @@ dependencies = [ "nom", "serde", "serde_derive", - "serial_test", + "serial_test 0.7.0", "smallvec", "tempfile", "thiserror", @@ -1131,18 +1180,19 @@ dependencies = [ [[package]] name = "git-date" -version = "0.0.1" +version = "0.0.2" dependencies = [ "bstr", "document-features", "git-testtools", "itoa 1.0.2", "serde", + "time", ] [[package]] name = "git-diff" -version = "0.16.0" +version = "0.17.0" dependencies = [ "git-hash", "git-object", @@ -1170,7 +1220,7 @@ dependencies = [ [[package]] name = "git-features" -version = "0.21.1" +version = "0.22.0" dependencies = [ "bstr", "bytes", @@ -1183,12 +1233,12 @@ dependencies = [ "jwalk", "libc", "num_cpus", + "once_cell", "parking_lot 0.12.1", "prodash", "quick-error", "sha-1", "sha1_smol", - "time", "walkdir", ] @@ -1198,7 +1248,7 @@ version = "0.0.0" [[package]] name = "git-glob" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bitflags", "bstr", @@ -1209,7 +1259,7 @@ dependencies = [ [[package]] name = "git-hash" -version = "0.9.5" +version = "0.9.6" dependencies = [ "document-features", "git-testtools", @@ -1220,7 +1270,7 @@ dependencies = [ [[package]] name = "git-index" -version = "0.3.0" +version = "0.4.0" dependencies = [ "atoi", "bitflags", @@ -1254,7 +1304,7 @@ dependencies = [ [[package]] name = "git-mailmap" -version = "0.2.0" +version = "0.3.0" dependencies = [ "bstr", "git-actor", @@ -1269,7 +1319,7 @@ version = "0.0.0" [[package]] name = "git-object" -version = "0.19.0" +version = "0.20.0" dependencies = [ "bstr", "btoi", @@ -1360,7 +1410,7 @@ dependencies = [ [[package]] name = "git-path" -version = "0.3.0" +version = "0.4.0" dependencies = [ "bstr", "tempfile", @@ -1370,6 +1420,15 @@ dependencies = [ [[package]] name = "git-pathspec" version = "0.0.0" +dependencies = [ + "bitflags", + "bstr", + "git-attributes", + "git-glob", + "git-testtools", + "once_cell", + "thiserror", +] [[package]] name = "git-protocol" @@ -1442,6 +1501,7 @@ dependencies = [ "git-attributes", "git-config", "git-credentials", + "git-date", "git-diff", "git-discover", "git-features", @@ -1467,6 +1527,7 @@ dependencies = [ "git-worktree", "is_ci", "log", + "serial_test 0.8.0", "signal-hook", "tempfile", "thiserror", @@ -1475,7 +1536,7 @@ dependencies = [ [[package]] name = "git-revision" -version = "0.2.1" +version = "0.3.0" dependencies = [ "bstr", "document-features", @@ -1514,7 +1575,7 @@ version = "0.0.0" [[package]] name = "git-tempfile" -version = "2.0.1" +version = "2.0.2" dependencies = [ "dashmap", "libc", @@ -1526,7 +1587,7 @@ dependencies = [ [[package]] name = "git-testtools" -version = "0.7.0" +version = "0.7.1" dependencies = [ "bstr", "crc", @@ -1578,7 +1639,7 @@ dependencies = [ [[package]] name = "git-traverse" -version = "0.15.0" +version = "0.16.0" dependencies = [ "git-hash", "git-object", @@ -2587,7 +2648,20 @@ dependencies = [ "lazy_static", "log", "parking_lot 0.12.1", - "serial_test_derive", + "serial_test_derive 0.7.0", +] + +[[package]] +name = "serial_test" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec42e7232e5ca56aa59d63af3c7f991fe71ee6a3ddd2d3480834cf3902b007" +dependencies = [ + "futures", + "lazy_static", + "log", + "parking_lot 0.12.1", + "serial_test_derive 0.8.0", ] [[package]] @@ -2603,6 +2677,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serial_test_derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b95bb2f4f624565e8fe8140c789af7e2082c0e0561b5a82a1b678baa9703dc" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "sha-1" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index 167ad114203..abbfcd7b70b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ resolver = "2" [[bin]] name = "ein" -path = "src/porcelain-cli.rs" +path = "src/ein.rs" test = false doctest = false [[bin]] name = "gix" -path = "src/plumbing-cli.rs" +path = "src/gix.rs" test = false doctest = false @@ -35,7 +35,7 @@ fast = ["git-features/parallel", "git-features/fast-sha1", "git-features/zlib-ng ## Use `clap` 3.0 to build the prettiest, best documented and most user-friendly CLI at the expense of binary size. ## Provides a terminal user interface for detailed and exhaustive progress. ## Provides a line renderer for leaner progress display, without the need for a full-blown TUI. -pretty-cli = [ "gitoxide-core/serde1", "prodash/progress-tree", "prodash/progress-tree-log", "prodash/local-time", "gitoxide-core/local-time-support", "env_logger/humantime", "env_logger/termcolor", "env_logger/atty" ] +pretty-cli = [ "gitoxide-core/serde1", "prodash/progress-tree", "prodash/progress-tree-log", "prodash/local-time", "env_logger/humantime", "env_logger/termcolor", "env_logger/atty" ] ## The `--verbose` flag will be powered by an interactive progress mechanism that doubles as log as well as interactive progress ## that appears after a short duration. @@ -83,7 +83,7 @@ cache-efficiency-debug = ["git-features/cache-efficiency-debug"] anyhow = "1.0.42" gitoxide-core = { version = "^0.15.0", path = "gitoxide-core" } -git-features = { version = "^0.21.1", path = "git-features" } +git-features = { version = "^0.22.0", path = "git-features" } git-repository = { version = "^0.20.0", path = "git-repository", default-features = false } git-transport-for-configuration-only = { package = "git-transport", optional = true, version = "^0.19.0", path = "git-transport" } @@ -118,7 +118,7 @@ codegen-units = 1 incremental = false build-override = { opt-level = 0 } -# It's not quite worth building depencies with more optimizations yet. Let's keep it here for later. +# It's not quite worth building dependencies with more optimizations yet. Let's keep it here for later. #[profile.dev.package."*"] #opt-level = 2 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e3798d63f34..66b00b0f37a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -9,6 +9,8 @@ for the mundane things, like unhappy code paths. * *use git itself* as reference implementation, and use their test-cases and fixtures where appropriate. At the very least, try to learn from them. + * Run the same test against git whenever feasible to assure git agrees with our implementation. + See `git-glob` for examples. * *use libgit2* test fixtures and cases where appropriate, or learn from them. * **safety first** * handle all errors, never `unwrap()`. If needed, `expect("why")`. @@ -106,6 +108,11 @@ A bunch of notes collected to keep track of what's needed to eventually support * Use `expect(…)` as assertion on Options, providing context on *why* the expectations should hold. Or in other words, answer "This should work _because_…" +## `Options` vs `Context` + +- Use `Options` whenever there is something to configure in terms of branching behaviour. +- Use `Context` when potential optional data is required to perform an operation at all. See `git_config::path::Context` as reference. + ## Examples, Experiments, Porcelain CLI and Plumbing CLI - which does what? ### Plumbing vs Porcelain @@ -143,7 +150,7 @@ by humans. * **Experiments** * quick, potentially one-off programs to learn about an aspect of gitoxide potentially in comparison to other implementations like `libgit2`. * No need for tests of any kind, but it must compile and be idiomatic Rust and `gitoxide`. - * Manual commmand-line parsing is OK + * Manual command-line parsing is OK * no polish * make it compile quickly, so no extras * **Examples** diff --git a/Makefile b/Makefile index a45d05926a9..3987ee4dba4 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ clippy: ## Run cargo clippy on all crates cargo clippy --all --no-default-features --features lean-async --tests check-msrv: ## run cargo msrv to validate the current msrv requirements, similar to what CI does - cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance + cd git-repository && cargo check --package git-repository --no-default-features --features async-network-client,unstable,max-performance check: ## Build all code in suitable configurations cargo check --all @@ -63,12 +63,9 @@ check: ## Build all code in suitable configurations cargo check --no-default-features --features lean cargo check --no-default-features --features lean-async cargo check --no-default-features --features max - cd git-actor && cargo check \ - && cargo check --features local-time-support cd gitoxide-core && cargo check \ && cargo check --features blocking-client \ - && cargo check --features async-client \ - && cargo check --features local-time-support + && cargo check --features async-client cd gitoxide-core && if cargo check --all-features 2>/dev/null; then false; else true; fi cd git-hash && cargo check --all-features \ && cargo check @@ -100,7 +97,6 @@ check: ## Build all code in suitable configurations && cargo check --features rustsha1 \ && cargo check --features fast-sha1 \ && cargo check --features progress \ - && cargo check --features time \ && cargo check --features io-pipe \ && cargo check --features crc32 \ && cargo check --features zlib \ @@ -245,20 +241,20 @@ commit_graphs = \ stress: ## Run various algorithms on big repositories $(MAKE) -j3 $(linux_repo) $(rust_repo) release-lean - time ./target/release/gix --verbose pack verify --re-encode $(linux_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack multi-index -i $(linux_repo)/objects/pack/multi-pack-index create $(linux_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify $(linux_repo)/objects/pack/multi-pack-index - rm -Rf out; mkdir out && time ./target/release/gix --verbose pack index create -p $(linux_repo)/objects/pack/*.pack out/ - time ./target/release/gix --verbose pack verify out/*.idx - - time ./target/release/gix --verbose pack verify --statistics $(rust_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify --algorithm less-memory $(rust_repo)/objects/pack/*.idx - time ./target/release/gix --verbose pack verify --re-encode $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --re-encode $(linux_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack multi-index -i $(linux_repo)/objects/pack/multi-pack-index create $(linux_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify $(linux_repo)/objects/pack/multi-pack-index + rm -Rf out; mkdir out && time ./target/release/gix --verbose no-repo pack index create -p $(linux_repo)/objects/pack/*.pack out/ + time ./target/release/gix --verbose no-repo pack verify out/*.idx + + time ./target/release/gix --verbose no-repo pack verify --statistics $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --algorithm less-memory $(rust_repo)/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack verify --re-encode $(rust_repo)/objects/pack/*.idx # We must ensure there is exactly one pack file for the pack-explode *.idx globs to work. git repack -Ad - time ./target/release/gix --verbose pack explode .git/objects/pack/*.idx + time ./target/release/gix --verbose no-repo pack explode .git/objects/pack/*.idx - rm -Rf delme; mkdir delme && time ./target/release/gix --verbose pack explode .git/objects/pack/*.idx delme/ + rm -Rf delme; mkdir delme && time ./target/release/gix --verbose no-repo pack explode .git/objects/pack/*.idx delme/ $(MAKE) stress-commitgraph $(MAKE) bench-git-config @@ -266,7 +262,7 @@ stress: ## Run various algorithms on big repositories .PHONY: stress-commitgraph stress-commitgraph: release-lean $(commit_graphs) set -x; for path in $(wordlist 2, 999, $^); do \ - time ./target/release/gix --verbose commit-graph verify $$path; \ + time ./target/release/gix --verbose no-repo commit-graph verify $$path; \ done .PHONY: bench-git-config @@ -276,7 +272,7 @@ bench-git-config: check-msrv-on-ci: ## Check the minimal support rust version for currently installed Rust version rustc --version cargo check --package git-repository - cargo check --package git-repository --no-default-features --features async-network-client,unstable,local-time-support,max-performance + cargo check --package git-repository --no-default-features --features async-network-client,unstable,max-performance ##@ Maintenance diff --git a/README.md b/README.md index 9b94d1bc376..31b2c33aa8f 100644 --- a/README.md +++ b/README.md @@ -27,52 +27,53 @@ Please see _'Development Status'_ for a listing of all crates and their capabili * Based on the [git-hours] algorithm. * See the [discussion][git-hours-discussion] for some performance data. * **the `gix` program** _(plumbing)_ - lower level commands for use in automation - * **pack** - * [x] [verify](https://asciinema.org/a/352942) - * [x] [index verify](https://asciinema.org/a/352945) including each object sha1 and statistics - * [x] [explode](https://asciinema.org/a/352951), useful for transforming packs into loose objects for inspection or restoration - * [x] verify written objects (by reading them back from disk) - * [x] [receive](https://asciinema.org/a/359321) - receive a whole pack produced by **pack-send** or _git-upload-pack_, useful for `clone` like operations. - * [x] **create** - create a pack from given objects or tips of the commit graph. - * [ ] **send** - create a pack and send it using the pack protocol to stdout, similar to 'git-upload-pack', - for consumption by **pack-receive** or _git-receive-pack_ - - **multi-index** - * [x] **info** - print information about the file - * [x] **create** - create a multi-index from pack indices - * [x] **verify** - check the file for consistency - * [x] **entries** - list all entries of the file - - **index** - * [x] [create](https://asciinema.org/a/352941) - create an index file by streaming a pack file as done during clone - * [x] support for thin packs (as needed for fetch/pull) - * **commit-graph** - * [x] **verify** - assure that a commit-graph is consistent + * **config** - list the complete git configuration in human-readable form and optionally filter sections by name. + * **exclude** + * [x] **query** - check if path specs are excluded via gits exclusion rules like `.gitignore`. + * **verify** - validate a whole repository, for now only the object database. + * **commit** + * [x] **describe** - identify a commit by its closest tag in its past + * **tree** + * [x] **entries** - list tree entries for a single tree or recursively + * [x] **info** - display tree statistics + * **odb** + * [x] **info** - display odb statistics + * [x] **entries** - display all object ids in the object database * **mailmap** - * [x] **verify** - check entries of a mailmap file for parse errors and display them - * **repository** - * **exclude** - * [x] **query** - check if path specs are excluded via gits exclusion rules like `.gitignore`. - * **verify** - validate a whole repository, for now only the object database. - * **commit** - * [x] **describe** - identify a commit by its closest tag in its past - * **tree** - * [x] **entries** - list tree entries for a single tree or recursively - * [x] **info** - display tree statistics - * **odb** - * [x] **info** - display odb statistics - * [x] **entries** - display all object ids in the object database - * **mailmap** - * [x] **entries** - display all entries of the aggregated mailmap git would use for substitution - * **revision** - * [ ] **explain** - show what would be done while parsing a revision specification like `HEAD~1` - * **index** - * [x] **entries** - show detailed entry information for human or machine consumption (via JSON) - * [x] **verify** - check the index for consistency - * [x] **info** - display general information about the index itself, with detailed extension information by default - * [x] detailed information about the TREE extension - * [ ] …other extensions details aren't implemented yet - * [x] **checkout-exclusive** - a predecessor of `git worktree`, providing flexible options to evaluate checkout performance from an index and/or an object database. - * **remote** - * [ref-list](https://asciinema.org/a/359320) - list all (or given) references from a remote at the given URL + * [x] **entries** - display all entries of the aggregated mailmap git would use for substitution + * **revision** + * [ ] **explain** - show what would be done while parsing a revision specification like `HEAD~1` + * **free** - no git repository necessary + * **pack** + * [x] [verify](https://asciinema.org/a/352942) + * [x] [index verify](https://asciinema.org/a/352945) including each object sha1 and statistics + * [x] [explode](https://asciinema.org/a/352951), useful for transforming packs into loose objects for inspection or restoration + * [x] verify written objects (by reading them back from disk) + * [x] [receive](https://asciinema.org/a/359321) - receive a whole pack produced by **pack-send** or _git-upload-pack_, useful for `clone` like operations. + * [x] **create** - create a pack from given objects or tips of the commit graph. + * [ ] **send** - create a pack and send it using the pack protocol to stdout, similar to 'git-upload-pack', + for consumption by **pack-receive** or _git-receive-pack_ + - **multi-index** + * [x] **info** - print information about the file + * [x] **create** - create a multi-index from pack indices + * [x] **verify** - check the file for consistency + * [x] **entries** - list all entries of the file + - **index** + * [x] [create](https://asciinema.org/a/352941) - create an index file by streaming a pack file as done during clone + * [x] support for thin packs (as needed for fetch/pull) + * **commit-graph** + * [x] **verify** - assure that a commit-graph is consistent + * **mailmap** + * [x] **verify** - check entries of a mailmap file for parse errors and display them + * **index** + * [x] **entries** - show detailed entry information for human or machine consumption (via JSON) + * [x] **verify** - check the index for consistency + * [x] **info** - display general information about the index itself, with detailed extension information by default + * [x] detailed information about the TREE extension + * [ ] …other extensions details aren't implemented yet + * [x] **checkout-exclusive** - a predecessor of `git worktree`, providing flexible options to evaluate checkout performance from an index and/or an object database. + * **remote** + * [ref-list](https://asciinema.org/a/359320) - list all (or given) references from a remote at the given URL [skim]: https://github.com/lotabout/skim [git-hours]: https://github.com/kimmobrunfeldt/git-hours/blob/8aaeee237cb9d9028e7a2592a25ad8468b1f45e4/index.js#L114-L143 @@ -93,18 +94,25 @@ Follow linked crate name for detailed status. Please note that all crates follow ### Stabilization Candidates Crates that seem feature complete and need to see some more use before they can be released as 1.0. +Documentation is complete and was reviewed at least once. * [git-mailmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-mailmap) * [git-chunk](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-chunk) +* [git-ref](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-ref) +* [git-config](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-config) +* [git-glob](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-glob) ### Initial Development -* **usable** + +These crates may be missing some features and thus are somewhat incomplete, but what's there +is usable to some extend. + +* **usable** _(with rough but complete docs, possibly incomplete functionality)_ * [git-actor](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-actor) * [git-hash](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-hash) * [git-object](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-object) * [git-validate](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-validate) * [git-url](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-url) - * [git-glob](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-glob) * [git-packetline](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-packetline) * [git-transport](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-transport) * [git-protocol](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-protocol) @@ -113,30 +121,28 @@ Crates that seem feature complete and need to see some more use before they can * [git-commitgraph](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-commitgraph) * [git-diff](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-diff) * [git-traverse](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-traverse) - * [git-config](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-config) * [git-features](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-features) * [git-credentials](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-credentials) * [git-sec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-sec) * [git-quote](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-quote) - * [git-ref](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-ref) * [git-discover](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-discover) * [git-path](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-path) * [git-repository](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-repository) + * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) + * [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * `gitoxide-core` -* **very early** +* **very early** _(possibly without any documentation and many rough edges)_ * [git-index](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-index) * [git-worktree](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-worktree) * [git-bitmap](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-bitmap) - * [git-attributes](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-attributes) * [git-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-revision) * [git-date](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-date) -* **idea** +* **idea** _(just a name placeholder)_ * [git-note](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-note) * [git-filter](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-filter) * [git-lfs](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-lfs) * [git-rebase](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-rebase) * [git-sequencer](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-sequencer) - * [git-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-pathspec) * [git-submodule](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-submodule) * [git-tui](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-tui) * [git-tix](https://github.com/Byron/gitoxide/blob/main/crate-status.md#git-tix) diff --git a/cargo-smart-release/src/context.rs b/cargo-smart-release/src/context.rs index 9cdf5c1de27..ec5569e64ee 100644 --- a/cargo-smart-release/src/context.rs +++ b/cargo-smart-release/src/context.rs @@ -52,7 +52,7 @@ impl Context { .parent() .expect("parent of a file is always present") .strip_prefix(&self.root) - .expect("workspace members are releative to the root directory"); + .expect("workspace members are relative to the root directory"); if dir.as_os_str().is_empty() { None diff --git a/cargo-smart-release/tests/changelog/parse.rs b/cargo-smart-release/tests/changelog/parse.rs index a5d5425e049..39c3535a2ba 100644 --- a/cargo-smart-release/tests/changelog/parse.rs +++ b/cargo-smart-release/tests/changelog/parse.rs @@ -111,7 +111,7 @@ fn known_and_unknown_sections_are_sorted() { markdown: "- initial release\n\n".into() }, Segment::User { - markdown: "### Something inbetween\n\nintermezzo\n".into() + markdown: "### Something in between\n\nintermezzo\n".into() }, ] }, diff --git a/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md b/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md index 7b8dfa06a3f..f491aa53cc9 100644 --- a/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md +++ b/cargo-smart-release/tests/fixtures/changelog/parse/unknown-known-unknown-known-unsorted.md @@ -4,7 +4,7 @@ Hello, this is a changelog. - initial release -### Something inbetween +### Something in between intermezzo diff --git a/crate-status.md b/crate-status.md index 7b67713a973..fc6e7485ab9 100644 --- a/crate-status.md +++ b/crate-status.md @@ -231,7 +231,7 @@ Check out the [performance discussion][git-traverse-performance] as well. * [ ] check for match ### git-pathspec -* [ ] parse +* [x] parse * [ ] check for match ### git-note @@ -239,7 +239,7 @@ Check out the [performance discussion][git-traverse-performance] as well. A mechanism to associate metadata with any object, and keep revisions of it using git itself. * [ ] CRUD for git notes -* + ### git-discover * [x] check if a git directory is a git repository @@ -388,26 +388,33 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-tempfile/REA See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README.md). ### git-config -* [ ] read - * line-wise parsing with decent error messages +* [x] read + * zero-copy parsing with event emission * [x] decode value * [x] boolean * [x] integer * [x] color * [ ] ANSI code output for terminal colors * [x] path (incl. resolution) + * [ ] date + * [ ] [permission][https://github.com/git/git/blob/71a8fab31b70c417e8f5b5f716581f89955a7082/setup.c#L1526:L1526] * [x] include - * **includeIf** - * [x] `gitdir`, `gitdir/i`, `onbranch` - * [ ] `hasconfig` -* [x] write + * **includeIf** + * [x] `gitdir`, `gitdir/i`, and `onbranch` + * [ ] `hasconfig` +* [x] access values and sections by name and sub-section +* [x] edit configuration in memory, non-destructively + * cross-platform newline handling +* [x] write files back for lossless round-trips. * keep comments and whitespace, and only change lines that are affected by actual changes, to allow truly non-destructive editing -* [ ] `Config` type which integrates multiple files into one interface to support system, user and repository levels for config files +* [x] cascaded loading of various configuration files into one + * [x] load from environment variables + * [ ] load from well-known sources for global configuration + * [ ] load repository configuration with all known sources * [x] API documentation * [x] Some examples ### git-repository - * [x] utilities for applications to make long running operations interruptible gracefully and to support timeouts in servers. * [ ] handle `core.repositoryFormatVersion` and extensions * [x] support for unicode-precomposition of command-line arguments (needs explicit use in parent application) @@ -427,16 +434,15 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * [ ] make [git-notes](https://git-scm.com/docs/git-notes) accessible * [x] tree entries * **diffs/changes** - * [x] tree with tree + * [x] tree with working tree * [ ] tree with index - * [ ] index with working tree * [x] initialize - * [ ] Proper configuration depending on platform (e.g. ignorecase, filemode, …) + * [x] Proper configuration depending on platform (e.g. ignorecase, filemode, …) * **Id** * [x] short hashes with detection of ambiguity. * **Commit** * [x] `describe()` like functionality - * [x] create new commit + * [x] create new commit from tree * **Objects** * [x] lookup * [x] peel to object kind @@ -446,23 +452,32 @@ See its [README.md](https://github.com/Byron/gitoxide/blob/main/git-lock/README. * **references** * [x] peel to end * [x] ref-log access - * [ ] clone + * [ ] clone from remote * [ ] shallow - * [ ] namespaces support - * [ ] sparse checkout support * [ ] execute hooks - * [ ] .gitignore handling - * [ ] checkout/stage conversions clean + smudge as in .gitattributes * **refs** * [ ] run transaction hooks and handle special repository states like quarantine * [ ] support for different backends like `files` and `reftable` - * **worktrees** - * [x] open a repository with worktrees - * [x] read locked state - * [ ] obtain 'prunable' information - * [x] proper handling of worktree related refs - * [ ] create, move, remove, and repair + * **main or linked worktree** + * [ ] add files with `.gitignore` handling + * [ ] checkout with conversions like clean + smudge as in `.gitattributes` + * [ ] _diff_ index with working tree + * [ ] sparse checkout support * [ ] read per-worktree config if `extensions.worktreeConfig` is enabled. + * **index** + * [ ] tree from index + * [ ] index from tree + * **worktrees** + * [x] open a repository with worktrees + * [x] read locked state + * [ ] obtain 'prunable' information + * [x] proper handling of worktree related refs + * [ ] create, move, remove, and repair + * **config** + * [x] read the primitive types `boolean`, `integer`, `string` + * [x] read and interpolate trusted paths + * [x] low-level API for more elaborate access to all details of `git-config` files + * [ ] a way to make changes to individual configuration files * [ ] remotes with push and pull * [x] mailmap * [x] object replacements (`git replace`) diff --git a/deny.toml b/deny.toml index 7e1f78a1534..e07e6200dfc 100644 --- a/deny.toml +++ b/deny.toml @@ -32,7 +32,7 @@ ignore = [ ] [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" -# List of explictly allowed licenses +# List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ diff --git a/etc/check-package-size.sh b/etc/check-package-size.sh index 8a73946e988..0a84a066738 100755 --- a/etc/check-package-size.sh +++ b/etc/check-package-size.sh @@ -17,18 +17,18 @@ function indent () { echo "in root: gitoxide CLI" (enter cargo-smart-release && indent cargo diet -n --package-size-limit 95KB) (enter git-actor && indent cargo diet -n --package-size-limit 5KB) -(enter git-pathspec && indent cargo diet -n --package-size-limit 5KB) +(enter git-pathspec && indent cargo diet -n --package-size-limit 25KB) (enter git-path && indent cargo diet -n --package-size-limit 15KB) (enter git-attributes && indent cargo diet -n --package-size-limit 15KB) -(enter git-discover && indent cargo diet -n --package-size-limit 15KB) +(enter git-discover && indent cargo diet -n --package-size-limit 20KB) (enter git-index && indent cargo diet -n --package-size-limit 30KB) (enter git-worktree && indent cargo diet -n --package-size-limit 30KB) (enter git-quote && indent cargo diet -n --package-size-limit 5KB) -(enter git-revision && indent cargo diet -n --package-size-limit 20KB) +(enter git-revision && indent cargo diet -n --package-size-limit 25KB) (enter git-bitmap && indent cargo diet -n --package-size-limit 5KB) (enter git-tempfile && indent cargo diet -n --package-size-limit 25KB) (enter git-lock && indent cargo diet -n --package-size-limit 15KB) -(enter git-config && indent cargo diet -n --package-size-limit 80KB) +(enter git-config && indent cargo diet -n --package-size-limit 110KB) (enter git-hash && indent cargo diet -n --package-size-limit 20KB) (enter git-chunk && indent cargo diet -n --package-size-limit 10KB) (enter git-rebase && indent cargo diet -n --package-size-limit 5KB) @@ -45,13 +45,13 @@ echo "in root: gitoxide CLI" (enter git-note && indent cargo diet -n --package-size-limit 5KB) (enter git-sec && indent cargo diet -n --package-size-limit 10KB) (enter git-tix && indent cargo diet -n --package-size-limit 5KB) -(enter git-credentials && indent cargo diet -n --package-size-limit 5KB) +(enter git-credentials && indent cargo diet -n --package-size-limit 10KB) (enter git-object && indent cargo diet -n --package-size-limit 25KB) (enter git-commitgraph && indent cargo diet -n --package-size-limit 25KB) (enter git-pack && indent cargo diet -n --package-size-limit 115KB) (enter git-odb && indent cargo diet -n --package-size-limit 120KB) (enter git-protocol && indent cargo diet -n --package-size-limit 50KB) (enter git-packetline && indent cargo diet -n --package-size-limit 35KB) -(enter git-repository && indent cargo diet -n --package-size-limit 105KB) +(enter git-repository && indent cargo diet -n --package-size-limit 120KB) (enter git-transport && indent cargo diet -n --package-size-limit 50KB) -(enter gitoxide-core && indent cargo diet -n --package-size-limit 70KB) +(enter gitoxide-core && indent cargo diet -n --package-size-limit 80KB) diff --git a/etc/discovery/odb.md b/etc/discovery/odb.md index bab727505f8..bcaaeea9491 100644 --- a/etc/discovery/odb.md +++ b/etc/discovery/odb.md @@ -153,7 +153,7 @@ Solutions aren't always mutually exclusive despite the form of presentation sugg | | | 3. catch error, force a pack refresh, repeat | can work in conjunction with similar shortcomings of loose reference database | needs mutability, burden on the API user; | | | | | 4. writers force an update of the process-wide pool of packs after creating new packs and before updating references with the new objects | | high implementation complexity; assumes complete control of one process over git repository, excluding running git-maintenance; new readers aren't allowed for a while until the new pack is placed causing some moments of unresponsiveness/waiting | | | **pack** | ~~5. race when creating/altering more than a pack at a time~~ | 1. ignore | | a chance for occasional object misses | all of them | -| | | 2. retry more than one time | greatly reduced likelyhood of object misses | | | +| | | 2. retry more than one time | greatly reduced likelihood of object misses | | | | **pack** | **6.too many (small) packs (i.e. due to pack-receive) reduce lookup performance** | 1. explode pack into loose objects (and deal with them separately) | can run in parallel (but is typically bound by max IOP/s) | might take a while if many objects are contained in the pack due to file IOP/s; needs recompresssion and looses delta compression; risk of too many small objects | | | | | 2. combine multiple packs into one | keep all benefits of packs; very fast if pack-to-pack copy is used; can run in parallel (but is typically bound by max IOP/s) | combining with big packs takes has to write a lot of data; can be costly if pack delta compression is used | | | | | 3. Just-in-time maintenance after writes | tuned to run just at the right time to run just as much as needed | an implementation isn't trivial as there must only be one maintenance operation per repository at a time, so some queue should be made available to not skip maintenance just because one is running already. | | @@ -238,7 +238,7 @@ for applications that don't need it, like CLIs. #### Loose References Writing loose references isn't actually atomic, so readers may observe some references in an old and some in a new state. This isn't always a breaking issue like it is -the case for packs, the progam can still operate and is likely to produce correct (enough) outcomes. +the case for packs, the program can still operate and is likely to produce correct (enough) outcomes. Mitigations are possible with careful programming on the API user's side or by using the `ref-table` database instead. @@ -273,7 +273,7 @@ refresh to the user in case they fetched or pulled in the meantime, to refresh t **Drawbacks** The program could benefit of using 1.2 instead of 1.1 which could cause exhaustion of file handles despite the user having no interest in evaluating all available objects, -but ideally that is possible without loosing performance during multi-threading. +but ideally that is possible without losing performance during multi-threading. ### Professional git-hosting mono-repo server with git-maintenance tasks and just-in-time replication @@ -381,7 +381,7 @@ The default favors speed and using all available cores, but savvy users can run - not an issue as there isn't enough traffic here * **9.2** loose object database - too many loose objects reduce overall performance - just-in-time maintenance * **10** - disk full - display early warnings in the front-end to every user to get it fixed - - This solution is implemented on application side (and not in `gitoxide`), it's intersting enough to mention though for systems that operate themselves. + - This solution is implemented on application side (and not in `gitoxide`), it's interesting enough to mention though for systems that operate themselves. - One could also imagine that it tries to spend the nights aggressively compression repositories, some low-hanging fruits there. * **10** - write failure - fail connection - write failures aren't specifically handled but result in typical Rust error behaviour probably alongside error reporting on the respective channels of the git-transport sideband. diff --git a/experiments/diffing/Cargo.toml b/experiments/diffing/Cargo.toml index 477ff1704ae..fede7fca83d 100644 --- a/experiments/diffing/Cargo.toml +++ b/experiments/diffing/Cargo.toml @@ -10,6 +10,6 @@ publish = false [dependencies] anyhow = "1" git-repository = { version = "^0.20.0", path = "../../git-repository", features = ["unstable"] } -git-features-for-config = { package = "git-features", version = "^0.21.0", path = "../../git-features", features = ["cache-efficiency-debug"] } +git-features-for-config = { package = "git-features", version = "^0.22.0", path = "../../git-features", features = ["cache-efficiency-debug"] } git2 = "0.14" rayon = "1.5.0" diff --git a/git-actor/CHANGELOG.md b/git-actor/CHANGELOG.md index 04810103cb3..b08ea0cd7ee 100644 --- a/git-actor/CHANGELOG.md +++ b/git-actor/CHANGELOG.md @@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.11.0 (2022-07-22) + +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.10.1 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +47,7 @@ A maintenance release without user-facing changes. - - 3 commits contributed to the release over the course of 5 calendar days. + - 4 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -27,6 +61,7 @@ A maintenance release without user-facing changes. * **[#427](https://github.com/Byron/gitoxide/issues/427)** - Replace `Time` with `git-date::Time`. ([`59b3ff8`](https://github.com/Byron/gitoxide/commit/59b3ff8a7e028962917cf3b2930b5b7e5156c302)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) diff --git a/git-actor/Cargo.toml b/git-actor/Cargo.toml index f11a3ff4267..4458f663c36 100644 --- a/git-actor/Cargo.toml +++ b/git-actor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-actor" -version = "0.10.1" +version = "0.11.0" description = "A way to identify git actors" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" @@ -15,12 +15,9 @@ doctest = false ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde1 = ["serde", "bstr/serde1", "git-date/serde1"] -## Make `Signature` initializers use the local time (with UTC offset) available. -local-time-support = ["git-features/time"] - [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", optional = true } -git-date = { version = "^0.0.1", path = "../git-date" } +git-features = { version = "^0.22.0", path = "../git-features", optional = true } +git-date = { version = "^0.0.2", path = "../git-date" } quick-error = "2.0.0" btoi = "0.4.2" diff --git a/git-actor/src/signature/mod.rs b/git-actor/src/signature/mod.rs index f928da713d9..b8ec635ed9b 100644 --- a/git-actor/src/signature/mod.rs +++ b/git-actor/src/signature/mod.rs @@ -124,78 +124,6 @@ mod write { } } -mod init { - use bstr::BString; - - use crate::{Signature, Time}; - - impl Signature { - /// Return an actor identified `name` and `email` at the current local time, that is a time with a timezone offset from - /// UTC based on the hosts configuration. - #[cfg(feature = "local-time-support")] - pub fn now_local( - name: impl Into, - email: impl Into, - ) -> Result { - let offset = git_features::time::tz::current_utc_offset()?; - Ok(Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32, - offset_in_seconds: offset, - sign: offset.into(), - }, - }) - } - - /// Return an actor identified `name` and `email` at the current local time, or UTC time if the current time zone could - /// not be obtained. - #[cfg(feature = "local-time-support")] - pub fn now_local_or_utc(name: impl Into, email: impl Into) -> Self { - let offset = git_features::time::tz::current_utc_offset().unwrap_or(0); - Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32, - offset_in_seconds: offset, - sign: offset.into(), - }, - } - } - - /// Return an actor identified by `name` and `email` at the current time in UTC. - /// - /// This would be most useful for bot users, otherwise the [`now_local()`][Signature::now_local()] method should be preferred. - pub fn now_utc(name: impl Into, email: impl Into) -> Self { - let utc_offset = 0; - Signature { - name: name.into(), - email: email.into(), - time: Time { - seconds_since_unix_epoch: seconds_since_unix_epoch(), - offset_in_seconds: utc_offset, - sign: utc_offset.into(), - }, - } - } - } - - fn seconds_since_unix_epoch() -> u32 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("the system time doesn't run backwards that much") - .as_secs() as u32 - } -} - /// mod decode; pub use decode::decode; diff --git a/git-attributes/CHANGELOG.md b/git-attributes/CHANGELOG.md index 64cdb52faf9..3cc3d280df2 100644 --- a/git-attributes/CHANGELOG.md +++ b/git-attributes/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +38,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 16 calendar days. + - 3 commits contributed to the release over the course of 16 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +50,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - branch start, upgrade to compact_str v0.4 ([`b2f56d5`](https://github.com/Byron/gitoxide/commit/b2f56d5a279dae745d9c2c80ebe599c00e72c0d7))
diff --git a/git-attributes/Cargo.toml b/git-attributes/Cargo.toml index 4c5c2d64f71..16e22eefc3a 100644 --- a/git-attributes/Cargo.toml +++ b/git-attributes/Cargo.toml @@ -18,14 +18,14 @@ serde1 = ["serde", "bstr/serde1", "git-glob/serde1", "compact_str/serde"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features" } +git-path = { version = "^0.4.0", path = "../git-path" } git-quote = { version = "^0.2.0", path = "../git-quote" } -git-glob = { version = "^0.3.0", path = "../git-glob" } +git-glob = { version = "^0.3.1", path = "../git-glob" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} unicode-bom = "1.1.4" -quick-error = "2.0.0" +thiserror = "1.0.26" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} compact_str = "0.4" diff --git a/git-attributes/src/assignment.rs b/git-attributes/src/assignment.rs new file mode 100644 index 00000000000..e1d7263f768 --- /dev/null +++ b/git-attributes/src/assignment.rs @@ -0,0 +1,28 @@ +use crate::{Assignment, AssignmentRef, NameRef, StateRef}; + +impl<'a> AssignmentRef<'a> { + pub(crate) fn new(name: NameRef<'a>, state: StateRef<'a>) -> AssignmentRef<'a> { + AssignmentRef { name, state } + } + + /// Turn this reference into its owned counterpart. + pub fn to_owned(self) -> Assignment { + self.into() + } +} + +impl<'a> From> for Assignment { + fn from(a: AssignmentRef<'a>) -> Self { + Assignment { + name: a.name.to_owned(), + state: a.state.to_owned(), + } + } +} + +impl<'a> Assignment { + /// Provide a ref type to this owned instance. + pub fn as_ref(&'a self) -> AssignmentRef<'a> { + AssignmentRef::new(self.name.as_ref(), self.state.as_ref()) + } +} diff --git a/git-attributes/src/lib.rs b/git-attributes/src/lib.rs index ab396eae4b2..6fb14870f67 100644 --- a/git-attributes/src/lib.rs +++ b/git-attributes/src/lib.rs @@ -1,10 +1,12 @@ +//! Parse `.gitattribute` and `.gitignore` files and provide utilities to match against them. +//! //! ## Feature Flags #![cfg_attr( feature = "document-features", cfg_attr(doc, doc = ::document_features::document_features!()) )] #![forbid(unsafe_code)] -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, missing_docs)] use std::path::PathBuf; @@ -12,10 +14,25 @@ use bstr::{BStr, BString}; use compact_str::CompactString; pub use git_glob as glob; +mod assignment; +/// +pub mod name; +mod state; + +mod match_group; +pub use match_group::{Attributes, Ignore, Match, Pattern}; + +/// +pub mod parse; +/// Parse attribute assignments line by line from `bytes`. +pub fn parse(bytes: &[u8]) -> parse::Lines<'_> { + parse::Lines::new(bytes) +} + /// The state an attribute can be in, referencing the value. /// /// Note that this doesn't contain the name. -#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum StateRef<'a> { /// The attribute is listed, or has the special value 'true' @@ -42,26 +59,44 @@ pub enum State { Unset, /// The attribute is set to the given value, which followed the `=` sign. /// Note that values can be empty. - Value(CompactString), + Value(CompactString), // TODO: use `kstring`, maybe it gets a binary string soon, needs binary, too, no UTF8 is required for attr values /// The attribute isn't mentioned with a given path or is explicitly set to `Unspecified` using the `!` sign. Unspecified, } +/// Represents a validated attribute name +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] +pub struct Name(pub(crate) CompactString); + +/// Holds a validated attribute name as a reference +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct NameRef<'a>(&'a str); + /// Name an attribute and describe it's assigned state. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Assignment { - /// The name of the attribute. - pub name: CompactString, + /// The validated name of the attribute. + pub name: Name, /// The state of the attribute. pub state: State, } +/// Holds validated attribute data as a reference +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd)] +pub struct AssignmentRef<'a> { + /// The name of the attribute. + pub name: NameRef<'a>, + /// The state of the attribute. + pub state: StateRef<'a>, +} + /// A grouping of lists of patterns while possibly keeping associated to their base path. /// /// Pattern lists with base path are queryable relative to that base, otherwise they are relative to the repository root. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct MatchGroup { +pub struct MatchGroup { /// A list of pattern lists, each representing a patterns from a file or specified by hand, in the order they were /// specified in. /// @@ -74,7 +109,7 @@ pub struct MatchGroup { /// Knowing their base which is relative to a source directory, it will ignore all path to match against /// that don't also start with said base. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] -pub struct PatternList { +pub struct PatternList { /// Patterns and their associated data in the order they were loaded in or specified, /// the line number in its source file or its sequence number (_`(pattern, value, line_number)`_). /// @@ -90,18 +125,13 @@ pub struct PatternList { pub base: Option, } +/// An association of a pattern with its value, along with a sequence number providing a sort order in relation to its peers. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct PatternMapping { + /// The pattern itself, like `/target/*` pub pattern: git_glob::Pattern, + /// The value associated with the pattern. pub value: T, + /// Typically the line number in the file the pattern was parsed from. pub sequence_number: usize, } - -mod match_group; -pub use match_group::{Attributes, Ignore, Match, Pattern}; - -pub mod parse; - -pub fn parse(buf: &[u8]) -> parse::Lines<'_> { - parse::Lines::new(buf) -} diff --git a/git-attributes/src/match_group.rs b/git-attributes/src/match_group.rs index de3b6d56880..7732762d0af 100644 --- a/git-attributes/src/match_group.rs +++ b/git-attributes/src/match_group.rs @@ -1,38 +1,20 @@ +use crate::{Assignment, MatchGroup, PatternList, PatternMapping}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; use std::{ ffi::OsString, io::Read, path::{Path, PathBuf}, }; -use bstr::{BStr, BString, ByteSlice, ByteVec}; - -use crate::{Assignment, MatchGroup, PatternList, PatternMapping, State, StateRef}; - -impl<'a> From> for State { - fn from(s: StateRef<'a>) -> Self { - match s { - StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), - StateRef::Set => State::Set, - StateRef::Unset => State::Unset, - StateRef::Unspecified => State::Unspecified, - } - } -} - -fn attrs_to_assignments<'a>( - attrs: impl Iterator), crate::parse::Error>>, -) -> Result, crate::parse::Error> { - attrs - .map(|res| { - res.map(|(name, state)| Assignment { - name: name.to_str().expect("no illformed unicode").into(), - state: state.into(), - }) - }) - .collect() +fn into_owned_assignments<'a>( + attrs: impl Iterator, crate::name::Error>>, +) -> Result, crate::name::Error> { + attrs.map(|res| res.map(|attr| attr.to_owned())).collect() } -/// A marker trait to identify the type of a description. +/// A trait to convert bytes into patterns and their associated value. +/// +/// This is used for `gitattributes` which have a value, and `gitignore` which don't. pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Default { /// The value associated with a pattern. type Value: PartialEq + Eq + std::fmt::Debug + std::hash::Hash + Ord + PartialOrd + Clone; @@ -40,10 +22,11 @@ pub trait Pattern: Clone + PartialEq + Eq + std::fmt::Debug + std::hash::Hash + /// Parse all patterns in `bytes` line by line, ignoring lines with errors, and collect them. fn bytes_to_patterns(bytes: &[u8]) -> Vec>; - fn use_pattern(pattern: &git_glob::Pattern) -> bool; + /// Returns true if the given pattern may be used for matching. + fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool; } -/// Identify ignore patterns. +/// An implementation of the [`Pattern`] trait for ignore patterns. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Ignore; @@ -60,7 +43,7 @@ impl Pattern for Ignore { .collect() } - fn use_pattern(_pattern: &git_glob::Pattern) -> bool { + fn may_use_glob_pattern(_pattern: &git_glob::Pattern) -> bool { true } } @@ -69,10 +52,10 @@ impl Pattern for Ignore { #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub enum Value { MacroAttributes(Vec), - Attributes(Vec), + Assignments(Vec), } -/// Identify patterns with attributes. +/// An implementation of the [`Pattern`] trait for attributes. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Default)] pub struct Attributes; @@ -82,19 +65,19 @@ impl Pattern for Attributes { fn bytes_to_patterns(bytes: &[u8]) -> Vec> { crate::parse(bytes) .filter_map(Result::ok) - .filter_map(|(pattern_kind, attrs, line_number)| { + .filter_map(|(pattern_kind, assignments, line_number)| { let (pattern, value) = match pattern_kind { crate::parse::Kind::Macro(macro_name) => ( git_glob::Pattern { - text: macro_name, + text: macro_name.as_str().into(), mode: git_glob::pattern::Mode::all(), first_wildcard_pos: None, }, - Value::MacroAttributes(attrs_to_assignments(attrs).ok()?), + Value::MacroAttributes(into_owned_assignments(assignments).ok()?), ), crate::parse::Kind::Pattern(p) => ( (!p.is_negative()).then(|| p)?, - Value::Attributes(attrs_to_assignments(attrs).ok()?), + Value::Assignments(into_owned_assignments(assignments).ok()?), ), }; PatternMapping { @@ -107,7 +90,7 @@ impl Pattern for Attributes { .collect() } - fn use_pattern(pattern: &git_glob::Pattern) -> bool { + fn may_use_glob_pattern(pattern: &git_glob::Pattern) -> bool { pattern.mode != git_glob::pattern::Mode::all() } } @@ -115,6 +98,7 @@ impl Pattern for Attributes { /// Describes a matching value within a [`MatchGroup`]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] pub struct Match<'a, T> { + /// The glob pattern itself, like `/target/*`. pub pattern: &'a git_glob::Pattern, /// The value associated with the pattern. pub value: &'a T, @@ -199,6 +183,8 @@ impl MatchGroup { Ok(self.patterns.len() != previous_len) } + /// Add patterns as parsed from `bytes`, providing their `source` path and possibly their `root` path, the path they + /// are relative to. This also means that `source` is contained within `root` if `root` is provided. pub fn add_patterns_buffer(&mut self, bytes: &[u8], source: impl Into, root: Option<&Path>) { self.patterns .push(PatternList::::from_bytes(bytes, source.into(), root)); @@ -248,6 +234,9 @@ where base, } } + + /// Create a pattern list from the `source` file, which may be located underneath `root`, while optionally + /// following symlinks with `follow_symlinks`, providing `buf` to temporarily store the data contained in the file. pub fn from_file( source: impl Into, root: Option<&Path>, @@ -263,6 +252,9 @@ impl PatternList where T: Pattern, { + /// Return a match if a pattern matches `relative_path`, providing a pre-computed `basename_pos` which is the + /// starting position of the basename of `relative_path`. `is_dir` is true if `relative_path` is a directory. + /// `case` specifies whether cases should be folded during matching or not. pub fn pattern_matching_relative_path( &self, relative_path: &BStr, @@ -275,7 +267,7 @@ where self.patterns .iter() .rev() - .filter(|pm| T::use_pattern(&pm.pattern)) + .filter(|pm| T::may_use_glob_pattern(&pm.pattern)) .find_map( |PatternMapping { pattern, @@ -294,6 +286,8 @@ where ) } + /// Like [`pattern_matching_relative_path()`][Self::pattern_matching_relative_path()], but returns an index to the pattern + /// that matched `relative_path`, instead of the match itself. pub fn pattern_idx_matching_relative_path( &self, relative_path: &BStr, @@ -307,7 +301,7 @@ where .iter() .enumerate() .rev() - .filter(|(_, pm)| T::use_pattern(&pm.pattern)) + .filter(|(_, pm)| T::may_use_glob_pattern(&pm.pattern)) .find_map(|(idx, pm)| { pm.pattern .matches_repo_relative_path(relative_path, basename_start_pos, is_dir, case) diff --git a/git-attributes/src/name.rs b/git-attributes/src/name.rs new file mode 100644 index 00000000000..bc213f88ed9 --- /dev/null +++ b/git-attributes/src/name.rs @@ -0,0 +1,46 @@ +use crate::{Name, NameRef}; +use bstr::BString; + +impl<'a> NameRef<'a> { + /// Turn this ref into its owned counterpart. + pub fn to_owned(self) -> Name { + Name(self.0.into()) + } + + /// Return the inner `str`. + pub fn as_str(&self) -> &str { + self.0 + } +} + +impl AsRef for NameRef<'_> { + fn as_ref(&self) -> &str { + self.0 + } +} + +impl<'a> Name { + /// Provide our ref-type. + pub fn as_ref(&'a self) -> NameRef<'a> { + NameRef(self.0.as_ref()) + } + + /// Return the inner `str`. + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl AsRef for Name { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +/// The error returned by [`parse::Iter`][crate::parse::Iter]. +#[derive(Debug, thiserror::Error)] +#[error("Attribute has non-ascii characters or starts with '-': {attribute}")] +pub struct Error { + /// The attribute that failed to parse. + pub attribute: BString, +} diff --git a/git-attributes/src/parse/attribute.rs b/git-attributes/src/parse/attribute.rs index 064e78a4a17..148eb760539 100644 --- a/git-attributes/src/parse/attribute.rs +++ b/git-attributes/src/parse/attribute.rs @@ -1,101 +1,89 @@ +use crate::{name, AssignmentRef, Name, NameRef, StateRef}; +use bstr::{BStr, ByteSlice}; use std::borrow::Cow; -use bstr::{BStr, BString, ByteSlice}; - +/// The kind of attribute that was parsed. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub enum Kind { /// A pattern to match paths against Pattern(git_glob::Pattern), /// The name of the macro to define, always a valid attribute name - // TODO: turn it into its own type for maximum safety - Macro(BString), + Macro(Name), } mod error { use bstr::BString; - use quick_error::quick_error; - - quick_error! { - #[derive(Debug)] - pub enum Error { - PatternNegation { line_number: usize, line: BString } { - display("Line {} has a negative pattern, for literal characters use \\!: {}", line_number, line) - } - AttributeName { line_number: usize, attribute: BString } { - display("Attribute in line {} has non-ascii characters or starts with '-': {}", line_number, attribute) - } - MacroName { line_number: usize, macro_name: BString } { - display("Macro in line {} has non-ascii characters or starts with '-': {}", line_number, macro_name) - } - Unquote(err: git_quote::ansi_c::undo::Error) { - display("Could not unquote attributes line") - from() - source(err) - } - } + /// The error returned by [`parse::Lines`][crate::parse::Lines]. + #[derive(thiserror::Error, Debug)] + #[allow(missing_docs)] + pub enum Error { + #[error("Line {line_number} has a negative pattern, for literal characters use \\!: {line}")] + PatternNegation { line_number: usize, line: BString }, + #[error("Attribute in line {line_number} has non-ascii characters or starts with '-': {attribute}")] + AttributeName { line_number: usize, attribute: BString }, + #[error("Macro in line {line_number} has non-ascii characters or starts with '-': {macro_name}")] + MacroName { line_number: usize, macro_name: BString }, + #[error("Could not unquote attributes line")] + Unquote(#[from] git_quote::ansi_c::undo::Error), } } pub use error::Error; +/// An iterator over attribute assignments, parsed line by line. pub struct Lines<'a> { lines: bstr::Lines<'a>, line_no: usize, } +/// An iterator over attribute assignments in a single line. pub struct Iter<'a> { attrs: bstr::Fields<'a>, - line_no: usize, } impl<'a> Iter<'a> { - pub fn new(attrs: &'a BStr, line_no: usize) -> Self { - Iter { - attrs: attrs.fields(), - line_no, - } + /// Create a new instance to parse attribute assignments from `input`. + pub fn new(input: &'a BStr) -> Self { + Iter { attrs: input.fields() } } - fn parse_attr(&self, attr: &'a [u8]) -> Result<(&'a BStr, crate::StateRef<'a>), Error> { + fn parse_attr(&self, attr: &'a [u8]) -> Result, name::Error> { let mut tokens = attr.splitn(2, |b| *b == b'='); let attr = tokens.next().expect("attr itself").as_bstr(); let possibly_value = tokens.next(); let (attr, state) = if attr.first() == Some(&b'-') { - (&attr[1..], crate::StateRef::Unset) + (&attr[1..], StateRef::Unset) } else if attr.first() == Some(&b'!') { - (&attr[1..], crate::StateRef::Unspecified) + (&attr[1..], StateRef::Unspecified) } else { ( attr, possibly_value - .map(|v| crate::StateRef::Value(v.as_bstr())) - .unwrap_or(crate::StateRef::Set), + .map(|v| StateRef::Value(v.as_bstr())) + .unwrap_or(StateRef::Set), ) }; - Ok((check_attr(attr, self.line_no)?, state)) + Ok(AssignmentRef::new(check_attr(attr)?, state)) } } -fn check_attr(attr: &BStr, line_number: usize) -> Result<&BStr, Error> { +fn check_attr(attr: &BStr) -> Result, name::Error> { fn attr_valid(attr: &BStr) -> bool { if attr.first() == Some(&b'-') { return false; } - attr.bytes().all(|b| { - matches!(b, - b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9') - }) + attr.bytes() + .all(|b| matches!(b, b'-' | b'.' | b'_' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')) } - attr_valid(attr).then(|| attr).ok_or_else(|| Error::AttributeName { - line_number, - attribute: attr.into(), - }) + attr_valid(attr) + .then(|| NameRef(attr.to_str().expect("no illformed utf8"))) + .ok_or_else(|| name::Error { attribute: attr.into() }) } impl<'a> Iterator for Iter<'a> { - type Item = Result<(&'a BStr, crate::StateRef<'a>), Error>; + type Item = Result, name::Error>; fn next(&mut self) -> Option { let attr = self.attrs.next().filter(|a| !a.is_empty())?; @@ -104,10 +92,11 @@ impl<'a> Iterator for Iter<'a> { } impl<'a> Lines<'a> { - pub fn new(buf: &'a [u8]) -> Self { - let bom = unicode_bom::Bom::from(buf); + /// Create a new instance to parse all attributes in all lines of the input `bytes`. + pub fn new(bytes: &'a [u8]) -> Self { + let bom = unicode_bom::Bom::from(bytes); Lines { - lines: buf[bom.len()..].lines(), + lines: bytes[bom.len()..].lines(), line_no: 0, } } @@ -153,14 +142,11 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, }; let kind_res = match line.strip_prefix(b"[attr]") { - Some(macro_name) => check_attr(macro_name.into(), line_number) - .map(|m| Kind::Macro(m.into())) - .map_err(|err| match err { - Error::AttributeName { line_number, attribute } => Error::MacroName { - line_number, - macro_name: attribute, - }, - _ => unreachable!("BUG: check_attr() must only return attribute errors"), + Some(macro_name) => check_attr(macro_name.into()) + .map(|name| Kind::Macro(name.to_owned())) + .map_err(|err| Error::MacroName { + line_number, + macro_name: err.attribute, }), None => { let pattern = git_glob::Pattern::from_bytes(line.as_ref())?; @@ -178,7 +164,7 @@ fn parse_line(line: &BStr, line_number: usize) -> Option, Ok(kind) => kind, Err(err) => return Some(Err(err)), }; - Ok((kind, Iter::new(attrs, line_number), line_number)).into() + Ok((kind, Iter::new(attrs), line_number)).into() } const BLANKS: &[u8] = b" \t\r"; diff --git a/git-attributes/src/parse/ignore.rs b/git-attributes/src/parse/ignore.rs index 71c29904ba2..d9d9e2038c0 100644 --- a/git-attributes/src/parse/ignore.rs +++ b/git-attributes/src/parse/ignore.rs @@ -1,11 +1,13 @@ use bstr::ByteSlice; +/// An iterator over line-wise ignore patterns parsed from a buffer. pub struct Lines<'a> { lines: bstr::Lines<'a>, line_no: usize, } impl<'a> Lines<'a> { + /// Create a new instance from `buf` to parse ignore patterns from. pub fn new(buf: &'a [u8]) -> Self { let bom = unicode_bom::Bom::from(buf); Lines { diff --git a/git-attributes/src/parse/mod.rs b/git-attributes/src/parse/mod.rs index 5bbd86f5bd4..82cacc8ed02 100644 --- a/git-attributes/src/parse/mod.rs +++ b/git-attributes/src/parse/mod.rs @@ -1,8 +1,10 @@ +/// pub mod ignore; mod attribute; pub use attribute::{Error, Iter, Kind, Lines}; -pub fn ignore(buf: &[u8]) -> ignore::Lines<'_> { - ignore::Lines::new(buf) +/// Parse git ignore patterns, line by line, from `bytes`. +pub fn ignore(bytes: &[u8]) -> ignore::Lines<'_> { + ignore::Lines::new(bytes) } diff --git a/git-attributes/src/state.rs b/git-attributes/src/state.rs new file mode 100644 index 00000000000..3ae275492e6 --- /dev/null +++ b/git-attributes/src/state.rs @@ -0,0 +1,32 @@ +use crate::{State, StateRef}; +use bstr::ByteSlice; + +impl<'a> StateRef<'a> { + /// Turn ourselves into our owned counterpart. + pub fn to_owned(self) -> State { + self.into() + } +} + +impl<'a> State { + /// Turn ourselves into our ref-type. + pub fn as_ref(&'a self) -> StateRef<'a> { + match self { + State::Value(v) => StateRef::Value(v.as_bytes().as_bstr()), + State::Set => StateRef::Set, + State::Unset => StateRef::Unset, + State::Unspecified => StateRef::Unspecified, + } + } +} + +impl<'a> From> for State { + fn from(s: StateRef<'a>) -> Self { + match s { + StateRef::Value(v) => State::Value(v.to_str().expect("no illformed unicode").into()), + StateRef::Set => State::Set, + StateRef::Unset => State::Unset, + StateRef::Unspecified => State::Unspecified, + } + } +} diff --git a/git-attributes/tests/parse/attribute.rs b/git-attributes/tests/parse/attribute.rs index c4306c70cd0..1c6b9ca90a3 100644 --- a/git-attributes/tests/parse/attribute.rs +++ b/git-attributes/tests/parse/attribute.rs @@ -1,4 +1,4 @@ -use bstr::{BStr, ByteSlice}; +use bstr::{BString, ByteSlice}; use git_attributes::{parse, StateRef}; use git_glob::pattern::Mode; use git_testtools::fixture_bytes; @@ -119,16 +119,28 @@ fn invalid_escapes_in_quotes_are_an_error() { #[test] fn custom_macros_can_be_differentiated() { - assert_eq!( - line(r#"[attr]foo bar -baz"#), - (macro_(r"foo"), vec![set("bar"), unset("baz")], 1) - ); + let output = line(r#"[attr]foo bar -baz"#); + match output.0 { + parse::Kind::Pattern(_) => unreachable!(), + parse::Kind::Macro(name) => { + assert_eq!( + (name.as_str(), output.1, output.2), + (r"foo", vec![set("bar"), unset("baz")], 1) + ); + } + } - assert_eq!( - line(r#""[attr]foo" bar -baz"#), - (macro_(r"foo"), vec![set("bar"), unset("baz")], 1), - "it works after unquoting even, making it harder to denote a file name with [attr] prefix" - ); + let output = line(r#""[attr]foo" bar -baz"#); + match output.0 { + parse::Kind::Pattern(_) => unreachable!(), + parse::Kind::Macro(name) => { + assert_eq!( + (name.as_str(), output.1, output.2), + (r"foo", vec![set("bar"), unset("baz")], 1), + "it works after unquoting even, making it harder to denote a file name with [attr] prefix" + ); + } + } } #[test] @@ -249,22 +261,22 @@ fn trailing_whitespace_in_attributes_is_ignored() { ); } -type ExpandedAttribute<'a> = (parse::Kind, Vec<(&'a BStr, git_attributes::StateRef<'a>)>, usize); +type ExpandedAttribute<'a> = (parse::Kind, Vec<(BString, git_attributes::StateRef<'a>)>, usize); -fn set(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Set) +fn set(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Set) } -fn unset(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Unset) +fn unset(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Unset) } -fn unspecified(attr: &str) -> (&BStr, StateRef) { - (attr.as_bytes().as_bstr(), StateRef::Unspecified) +fn unspecified(attr: &str) -> (BString, StateRef) { + (attr.into(), StateRef::Unspecified) } -fn value<'a, 'b>(attr: &'a str, value: &'b str) -> (&'a BStr, StateRef<'b>) { - (attr.as_bytes().as_bstr(), StateRef::Value(value.as_bytes().as_bstr())) +fn value<'a, 'b>(attr: &'a str, value: &'b str) -> (BString, StateRef<'b>) { + (attr.into(), StateRef::Value(value.as_bytes().as_bstr())) } fn pattern(name: &str, flags: git_glob::pattern::Mode, first_wildcard_pos: Option) -> parse::Kind { @@ -275,10 +287,6 @@ fn pattern(name: &str, flags: git_glob::pattern::Mode, first_wildcard_pos: Optio }) } -fn macro_(name: &str) -> parse::Kind { - parse::Kind::Macro(name.into()) -} - fn try_line(input: &str) -> Result { let mut lines = git_attributes::parse(input.as_bytes()); let res = expand(lines.next().unwrap())?; @@ -298,6 +306,12 @@ fn expand( input: Result<(parse::Kind, parse::Iter<'_>, usize), parse::Error>, ) -> Result, parse::Error> { let (pattern, attrs, line_no) = input?; - let attrs = attrs.collect::, _>>()?; + let attrs = attrs + .map(|r| r.map(|attr| (attr.name.as_str().into(), attr.state))) + .collect::, _>>() + .map_err(|e| parse::Error::AttributeName { + attribute: e.attribute, + line_number: line_no, + })?; Ok((pattern, attrs, line_no)) } diff --git a/git-bitmap/src/ewah.rs b/git-bitmap/src/ewah.rs index 9b42ffda2c5..3cd4502e4ab 100644 --- a/git-bitmap/src/ewah.rs +++ b/git-bitmap/src/ewah.rs @@ -21,7 +21,7 @@ pub fn decode(data: &[u8]) -> Result<(Vec, &[u8]), decode::Error> { let (len, data) = decode::u32(data).ok_or(Error::Corrupt("eof reading chunk length"))?; let len = len as usize; - // NOTE: git does this by copying all bytes first, and then it will change the endianess in a separate loop. + // NOTE: git does this by copying all bytes first, and then it will change the endianness in a separate loop. // Maybe it's faster, but we can't do it without unsafe. Let's leave it to the optimizer and maybe // one day somebody will find out that it's worth it to use unsafe here. let (mut bits, data) = decode::split_at_pos(data, len * std::mem::size_of::()) diff --git a/git-commitgraph/CHANGELOG.md b/git-commitgraph/CHANGELOG.md index f707d9c999b..afc356a7b66 100644 --- a/git-commitgraph/CHANGELOG.md +++ b/git-commitgraph/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.8.0 (2022-07-22) A maintenance release without user-facing changes. @@ -13,8 +13,8 @@ A maintenance release without user-facing changes. - - 4 commits contributed to the release over the course of 59 calendar days. - - 70 days passed between releases. + - 10 commits contributed to the release over the course of 99 calendar days. + - 110 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#384](https://github.com/Byron/gitoxide/issues/384) @@ -29,6 +29,12 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) + - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) diff --git a/git-commitgraph/Cargo.toml b/git-commitgraph/Cargo.toml index cb7f7d80b43..c457a182d97 100644 --- a/git-commitgraph/Cargo.toml +++ b/git-commitgraph/Cargo.toml @@ -17,8 +17,8 @@ doctest = false serde1 = ["serde", "git-hash/serde1", "bstr/serde1"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["rustsha1"] } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-chunk = { version = "^0.3.0", path = "../git-chunk" } bstr = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/git-config/CHANGELOG.md b/git-config/CHANGELOG.md index 1bce4477ff0..1fe15e2ddba 100644 --- a/git-config/CHANGELOG.md +++ b/git-config/CHANGELOG.md @@ -5,6 +5,565 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.6.0 (2022-07-22) + + + + + + +### New Features + + - following includes is now non-fatal by default + Otherwise it would be relatively easy to fail gitoxide startup, + and we want to be closer to the behaviour in git which ignores + most of the errors. + - `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. + - `File` now compares actual content, ignoring whitespace and comments. + - `File::new_environment_overrides()` to easily instantiate overrides from the environment. + - `File::new_globals()` can instantiate non-local configuration with zero-configuration. + - `Source::storage_location()` to know where files should be located. + - `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. + This can be useful if the value was obtained using `raw_value_mut()`. + - `Source::is_in_repository()` to find out if a source is in the repository. + - `parse::key` to parse a `remote.origin.url`-like key to identify a value + - Add `File::detect_newline_style()`, which does at it says. + - `File::frontmatter()` and `File::sections_and_postmatter()`. + - `parse::Event::to_bstr_lossy()` to get a glimpse at event content. + - `File::append()` can append one file to another rather losslessly. + The loss happens as we, maybe for the wrong reasons, automatically + insert newlines where needed which can only be done while we still know + the file boundaries. + - `file::Section::meta()` to access a section's metadata. + - `File::sections()` to obtain an iterator over all sections, in order. + - place spaces around `key = value` pairs, or whatever is used in the source configuration. + - proper escaping of value bytes to allow round-tripping after mutation + - whitespace in newly pushed keys is derived from first section value. + That way, newly added key-value pairs look like they should assuming + all keys have the same indentation as the first key in the section. + + If there is no key, then the default whitespace will be double-tabs + like what's commmon in git. + - `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` + - whitespace in mutable sections can be finely controlled, and is derived from existing sections + - `parse::Header::new(…)` with sub-section name validation + - Add `parse::(Event|section::Header|Comment)::write_to(…)`. + Now it's possible to serialize these types in a streaming fashion and + without arbitrarily enforcing UTF-8 on it + - `serde1` feature to add limited serde support + +### Bug Fixes + + - maintain insertion order of includes on per-section basis at least. + Note that git inserts values right after the include directive, + 'splitting' the section, but we don't do that and insert new values + after the section. Probably no issue in practice while keeping + our implementation simple. + - maintain newline format depending on what's present or use platform default. + Previously implicit newlines when adding new sections or keys to + sections was always `\n` which isn't correct on windows. + + Now the newline style is detected and used according to what's present, + or in the lack of content, defaults to what's correct for the platform. + - validate incoming conifguration keys when interpreting envirnoment variables. + - `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. + - `file::MutableSection::remove()` now actually removes keys _and_ values. + - `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. + - value normalization (via `value::normalize()` handles escape sequences. + The latter ones are `\n`, `\t` and `\b` which are the only supported + ones in values of git-config files. + - stable sort order for `File::sections_by_name_with_header()` + - count newlines (for error display) in multi-line values as well + - auto-normalize string values to support quote removal in case of strings. + Related to https://github.com/starship/starship/pull/3883 . + +### Other + + - :Events::from_bytes()` with `filter` support. + +### Changed (BREAKING) + + - add `File::resolve_includes()` and move its error type to `file::includes`. + - add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` + - remove `File::from_env_paths()`. + It's replaced by its more comfortable `new_globals()`. + - untangle `file::init::…` `Option` and `Error` types. + This moves types to where they belong which is more specific instead + of having a catch-all `Error` and `Options` type. + - rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + - rename `file::SectionBody` to `file::section::Body`. + - Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. + - create `resolve_includes` options to make space for more options when loading paths. + - rename `path::Options` into `path::Context`. + It's not an option if it's required context to perform a certain + operation. + - All accessors in `File` are now using `impl AsRef` where possible for added comfort. + - Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. + - Rename `Mutable*` into `$1Mut` for consistency. + - conform APIs of `file::MutableValue` and `file::MutableMultiValue`. + There are more renames and removals than worth mentioning here given the + current adoption of the crate. + - rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. + The corresponding getter was renamed as well to `leading_whitespace()`. + - Enforce `parse::section::Header::new()` by making its fields private. + - Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. + Now `File` can be serialized in a streaming fashion and without the + possibility for UTF8 conversion issues. + + Note that `Display` is still imlpemented with the usual caveats. + - remove `Integer::to_bstring()` as well as some `TryFrom` impls. + Note that it can still display itself like before via + `std::fmt::Display`. + - remove `Boolean::to_bstring()` along with a few `From` impls. + These were superfluous and aren't useful in practice. + Note that serialization is still implemented via `Display`. + - allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. + - `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. + - remove `String` type in favor of referring to the `File::string()` method. + The wrapper had no effect whatsoever except for adding complexity. + - Simplify `Boolean` to be a wrapper around `bool`. + Previously it tried hard not to degenerate information, making it a + complicated type. + + However, in practice nobody cares about the exact makeup of the boolean, + and there is no need to serialize a boolean faithfully either. + + Instead, those who want to set a value just set any value as a string, + no need for type safety there, and we take care of escaping values + properly on write. + - Use bitflags for `color::Attribute` instead of `Vec` of enums. + This is less wasteful and sufficient for git, so it should be sufficient + for us, especially since attributes are indeed a set and declaring + one twice has no effect. + - simplify `Color` API. + For now we only parse and serialize for display, but more uses are + enabled when needed and trivially. + - remove `parse::Events::from_path` and `File::at` + The latter has been replaced with `File::from_path_with_buf(…)` and + is a low-level way to load just a single config file, purposefully + uncomfortable as it will not resolve includes. + + The initialization API will need some time to stabilize. + - Slim down API surface of `parse::Events`. + It's more of a 'dumb' structure now than before, merely present + to facilitate typical parsing than something special on its own. + - remove `File::new()` method in favor of `File::default()`. + - rename `parse::event::List` to `parse::Events` + - rename `parse::State` to `parse::event::List` + - move `value::*` into the crate root, except for `Error` and `normalize_*()`. + - rename `value::parse::Error` to `value::Error`. + - rename `value::TrueVariant` to `value::boolean::True` + - rename `IntegerSuffix` to `integer::Suffix` + - rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. + - Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` + - rename `parse::ParsedComment` into `parse::Comment` + - rename `parse::Section*` related types. + These are now located in `section::*`. + - rename `parse::Parser` to `parse::State`. + Furthermore, make `State` the entry point for all parsing, removing + all free-standing functions that returned a `State`. + - rename `parser` module to `parse` + - rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module + - move `Path` from `values` to `value` module + - Move `Boolean` and `String` from `values` into `value` module + - move `values::Integer` into `value` module + - move `Color` to own `value` module + - remove `values::Bytes` - use `values::String` instead. + Note that these values are always normalized and it's only possible + to get a raw values using the `raw_value()` API. + +### New Features (BREAKING) + + - Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - add `_filter()` versions to most access methods. + That way it's possible to filter values by their origin. + + Note that the `remove_section()` methods now return the entire + removed section, not just the body, which yields more information + than before including section metadata. + - section names are now validated. + - filtering supportort for `parse::Events`. + That way it's possible to construct Files which are not destined to be + written back as they only keep events necessary for value access, + greatly reducing allocations. + - change mostily internal uses of [u8] to BString/BStr + - Path-interpolation makes `home-dir` configurable. + That way the caller has full control over how the environment is used, + which also allows more fine-grained control over which config files + can be included. + +### Bug Fixes (BREAKING) + + - Simplify specifying keys when mutating config values. + - `File::rename_section()` with validation of input arguments. + - improve normalization; assure no extra copies are made on query. + We now return our own content, rather than the originals with their + lifetimes, meaning we bind lifetimes of returned values to our own + `File` instance. This allows them to be referenced more often, and + smarter normalization assures we don't copy in the simple cases + either. + + More tests were added as well. + This is breaking as lifetime changes can cause distruptions, and + `values?_as()` was removed as well as it's somewhat duplicate + to higher-level APIs and it wasn't tested at all. + - Remove `git-config` test utilities from `git-path`. + +### Other (BREAKING) + + - `File::raw_multi_value()` to `File::raw_values()` + - `File::raw_multi_value_mut()` to `File::raw_values_mut()` + - `File::multi_value()` to `File::values()`. + The latter is better in line with `string()/strings()` + +### Commit Statistics + + + + - 314 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 93 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 19 times to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - final documentation review + adjustments prior to release candidate ([`06b86e0`](https://github.com/Byron/gitoxide/commit/06b86e05dd9a712d26456b43c8da0a11870f08df)) + - refactor ([`4dc6594`](https://github.com/Byron/gitoxide/commit/4dc6594686478d9d6cd09e2ba02048624c3577e7)) + - exclude particular assertion which fails on the linux CI. ([`5e0f889`](https://github.com/Byron/gitoxide/commit/5e0f889c1edb862d698a2d344a61f12ab3b6ade7)) + - first sketch of using configuration and environment variables for author/committer ([`330d0a1`](https://github.com/Byron/gitoxide/commit/330d0a19d54aabac868b76ef6281fffdbdcde53c)) + - remove `Permissions` as there is no need for that here. ([`1954ef0`](https://github.com/Byron/gitoxide/commit/1954ef096a58aedb9f568a01e439d5a5cb46c40d)) + - following includes is now non-fatal by default ([`1bc96bf`](https://github.com/Byron/gitoxide/commit/1bc96bf378d198b012efce9ec9e5b244a91f62bc)) + - Allow to skip non-existing input paths without error ([`989603e`](https://github.com/Byron/gitoxide/commit/989603efcdf0064e2bb7d48100391cabc810204d)) + - `File::from_git_dir()` as comfortable way to instantiate most complete git configuration. ([`f9ce1b5`](https://github.com/Byron/gitoxide/commit/f9ce1b5411f1ac788f71060ecf785dda9dfd87bf)) + - Add a way to load multiple configuration files without allocating a read buffer ([`acb4520`](https://github.com/Byron/gitoxide/commit/acb4520a88ab083640c80a7f23a56a2ca3cda335)) + - refactor ([`ec21e95`](https://github.com/Byron/gitoxide/commit/ec21e95f4d9ffac771410947923f27187e88321a)) + - move `Env` test utility into `git-testtools` ([`bd3f4d0`](https://github.com/Byron/gitoxide/commit/bd3f4d014dd7df7a1e25defa8eea7253eec1560a)) + - refactor ([`b073e29`](https://github.com/Byron/gitoxide/commit/b073e2930bed60ccedadd1709cfaa8889e02ffe3)) + - another failing tests that can't be fixed without a refactor ([`e4d8fd7`](https://github.com/Byron/gitoxide/commit/e4d8fd72f1f648a29e56e487827f2328bfc08d03)) + - an attempt to hack newline handling into place for windows newlines ([`dac1463`](https://github.com/Byron/gitoxide/commit/dac146343a0fbe96b6c0990f4fd4e976e0359a7e)) + - Serialize lossily-read configuration files correctly anyway. ([`cfda0c3`](https://github.com/Byron/gitoxide/commit/cfda0c335d759cae0b23cef51f7b85a5f4b11e82)) + - multi-path include test ([`3d89a46`](https://github.com/Byron/gitoxide/commit/3d89a46bf88b1fb5b4aa5da9fd12c7e310be3f9d)) + - refactor ([`8a7fb15`](https://github.com/Byron/gitoxide/commit/8a7fb15f78ce16d5caedd7656e8aa98e72f248a6)) + - fix windows tests ([`fbcf40e`](https://github.com/Byron/gitoxide/commit/fbcf40e16b8fc1ff97dbed2bc22b64bd44a8b99d)) + - finally proper whitespace handling in all the right places for perfect roundtripping to/from string ([`97e5ede`](https://github.com/Byron/gitoxide/commit/97e5ededb0390c1b4f296a35903433de9c519821)) + - serializations maintains some invariants about whitespace where possible. ([`ee10dd5`](https://github.com/Byron/gitoxide/commit/ee10dd5a8ae0dabfee21c1ce146e92c3c9635e8a)) + - refactor ([`9c248ee`](https://github.com/Byron/gitoxide/commit/9c248eeb015495f910f48ce5df3c8fcce905dba7)) + - `File` now compares actual content, ignoring whitespace and comments. ([`14a68a6`](https://github.com/Byron/gitoxide/commit/14a68a6a78a09f8ae56e30e3b7501de66ef31fdc)) + - maintain insertion order of includes on per-section basis at least. ([`6c1588f`](https://github.com/Byron/gitoxide/commit/6c1588fd1a2fa80fd866787cbf4bcc6e5b51abe6)) + - allow insertion of sections while preserving order ([`f5580a3`](https://github.com/Byron/gitoxide/commit/f5580a3635289d96e662aab00e60d801c4e34e1c)) + - a test showing that include ordering isn't correct compared to the including config. ([`4e47df5`](https://github.com/Byron/gitoxide/commit/4e47df5332810f6e46ab682a68e870220ba3a6fb)) + - add `File::resolve_includes()` and move its error type to `file::includes`. ([`17c83d5`](https://github.com/Byron/gitoxide/commit/17c83d55f8942788aac5eb1bea22a48daa045bf4)) + - add `File::from_bytes_owned()` and remove `File::from_path_with_buf()` ([`5221676`](https://github.com/Byron/gitoxide/commit/5221676e28f2b6cc1a7ef1bdd5654b880965f38c)) + - make it necessary to deal with the possibility of no-input in `from_paths_metadata()` . ([`612645f`](https://github.com/Byron/gitoxide/commit/612645f74ffc49229ccd783361b4d455e2284ac0)) + - Don't fail on empty input on the comfort level ([`61ecaca`](https://github.com/Byron/gitoxide/commit/61ecaca43fb871eaff5cf94a8e7f9cc9413a5a77)) + - `File::new_environment_overrides()` to easily instantiate overrides from the environment. ([`7dadfd8`](https://github.com/Byron/gitoxide/commit/7dadfd82494d47e36d3f570988eaf3c6b628977f)) + - prepare for supporting comfortable version of environment overrides ([`45c964a`](https://github.com/Byron/gitoxide/commit/45c964a3f581dc7d3090bbbe26f188d553783fb3)) + - remove `File::from_env_paths()`. ([`98d45c2`](https://github.com/Byron/gitoxide/commit/98d45c2f59863fdee033b38e757cec09593f6892)) + - `File::new_globals()` can instantiate non-local configuration with zero-configuration. ([`146eeb0`](https://github.com/Byron/gitoxide/commit/146eeb064822839bc46fd37a247a1b9a84f64e40)) + - Classify `Source` in accordance for what git actually does. ([`97374e4`](https://github.com/Byron/gitoxide/commit/97374e4d867e82d7be04da2eaa6ef553e0d9a7ff)) + - `Source::storage_location()` to know where files should be located. ([`e701e05`](https://github.com/Byron/gitoxide/commit/e701e053fd05850973930be0cefe73e8f3604d40)) + - `file::ValueMut::(section|into_section_mut)()` to go from value to the owning section. ([`fff0884`](https://github.com/Byron/gitoxide/commit/fff088485dd5067976cc93d525903b39aafea76a)) + - `Source::is_in_repository()` to find out if a source is in the repository. ([`f5f2d9b`](https://github.com/Byron/gitoxide/commit/f5f2d9b3fef98d9100d713f9291510fa4aa27867)) + - `parse::key` to parse a `remote.origin.url`-like key to identify a value ([`91e718f`](https://github.com/Byron/gitoxide/commit/91e718f0e116052b64ca436d7c74cea79529e696)) + - maintain newline format depending on what's present or use platform default. ([`f7bd2ca`](https://github.com/Byron/gitoxide/commit/f7bd2caceb87a179288030e0771da2e4ed6bd1e4)) + - prepare for passing through newline ([`3c06f88`](https://github.com/Byron/gitoxide/commit/3c06f8889854860b731735a8ce2bf532366003ef)) + - Add `File::detect_newline_style()`, which does at it says. ([`26147a7`](https://github.com/Byron/gitoxide/commit/26147a7a61a695eda680808ee4aab44a890b2964)) + - fix docs ([`78e85d9`](https://github.com/Byron/gitoxide/commit/78e85d9786a541aa43ad7266e85dc1da5e71a412)) + - a test for lossy File parsing ([`5e8127b`](https://github.com/Byron/gitoxide/commit/5e8127b395bd564129b20a1db2d59d39307a2857)) + - 'lossy' is now inherited by includes processing ([`88c6b18`](https://github.com/Byron/gitoxide/commit/88c6b185b2e51858b140e4378a5b5730b5cb4075)) + - untangle `file::init::…` `Option` and `Error` types. ([`230a523`](https://github.com/Byron/gitoxide/commit/230a523593afcfb8720db965ff56265aaceea772)) + - Support for `lossy` load mode. ([`d003c0f`](https://github.com/Byron/gitoxide/commit/d003c0f139d61e3bd998a0283a9c7af25a60db02)) + - :Events::from_bytes()` with `filter` support. ([`32d5b3c`](https://github.com/Byron/gitoxide/commit/32d5b3c695d868ba93755123a25b276bfbe55e0a)) + - try to fix attributes, once more ([`a50a396`](https://github.com/Byron/gitoxide/commit/a50a3964dbf01982b5a2c9a8ccd469332b6f9ca1)) + - `File::frontmatter()` and `File::sections_and_postmatter()`. ([`0ad1c9a`](https://github.com/Byron/gitoxide/commit/0ad1c9a5280cc172432b5258e0f79898721bac68)) + - add `_filter()` versions to most access methods. ([`1ea26d8`](https://github.com/Byron/gitoxide/commit/1ea26d80f392114349d25ebf88a7b260ee822aa1)) + - even better handling of newlines ([`50c1753`](https://github.com/Byron/gitoxide/commit/50c1753c6389f29279d278fbab1afbd9ded34a76)) + - refactor ([`df94c67`](https://github.com/Byron/gitoxide/commit/df94c6737ba642fff40623f406df0764d5bd3c43)) + - rename `parse::Comment::(comment_tag|comment)` to `::tag|text` and `parse::Section::section_header` to `::header`. ([`3f3ff11`](https://github.com/Byron/gitoxide/commit/3f3ff11a6ebe9775ee5ae7fc0ec18a94b5b46d61)) + - `parse::Event::to_bstr_lossy()` to get a glimpse at event content. ([`fc7e311`](https://github.com/Byron/gitoxide/commit/fc7e311b423c5fffb8240d9d0f917ae7139a6133)) + - finally fix newline behaviour ([`c70e135`](https://github.com/Byron/gitoxide/commit/c70e135ecbbce8c696a6ab542ae20f5b5981dfdf)) + - Be smarter about which newline style to use by guessing it based onprior events ([`25ed92e`](https://github.com/Byron/gitoxide/commit/25ed92e66bf4345f852e7e84741079c61ae896c8)) + - `File::append()` can append one file to another rather losslessly. ([`09966a8`](https://github.com/Byron/gitoxide/commit/09966a8ea4eaa3e0805e04188de86dd1bac9f388)) + - A test to validate frontmatter isn't currently handled correctly when appending ([`4665e87`](https://github.com/Byron/gitoxide/commit/4665e876df4ac6ab9135c10ee69b5408b89b5313)) + - `file::Section::meta()` to access a section's metadata. ([`56ae574`](https://github.com/Byron/gitoxide/commit/56ae5744e8957e617f3a0ebc4d725846b18d93f8)) + - refactor ([`d60025e`](https://github.com/Byron/gitoxide/commit/d60025e317d2b5f34f3569f321845bbb557ba2e7)) + - `File::sections()` to obtain an iterator over all sections, in order. ([`6f97bf0`](https://github.com/Byron/gitoxide/commit/6f97bf0c3e7164855cf5aa53462dbc39c430e03f)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - rename `file::SectionBody` to `file::section::Body`. ([`b672ed7`](https://github.com/Byron/gitoxide/commit/b672ed7667a334be3d45c59f4727f12797b340da)) + - Remove `File::sections_by_name_with_header()` as `::sections_by_name()` now returns entire sections. ([`3bea26d`](https://github.com/Byron/gitoxide/commit/3bea26d7d2a9b5751c6c15e1fa9a924b67e0159e)) + - A way to more easily set interpolation even without following includes. ([`9aa5acd`](https://github.com/Byron/gitoxide/commit/9aa5acdec12a0721543c6bcc39ffe6bd734f9a69)) + - create `resolve_includes` options to make space for more options when loading paths. ([`41b3e62`](https://github.com/Byron/gitoxide/commit/41b3e622ee71943c285eadc518150fc7b6c92361)) + - rename `path::Options` into `path::Context`. ([`cabc8ef`](https://github.com/Byron/gitoxide/commit/cabc8ef0e31c954642525e7693009a7fe4b4c465)) + - try to fix attributes, once more ([`207e483`](https://github.com/Byron/gitoxide/commit/207e483620b29efb029c6ee742c0bb48d54be020)) + - validate incoming conifguration keys when interpreting envirnoment variables. ([`0d07ef1`](https://github.com/Byron/gitoxide/commit/0d07ef1aa4a9e238c20249d4ae2ed19e6740308a)) + - try to fix filter settings, but it doesn't seem to work ([`9750b7a`](https://github.com/Byron/gitoxide/commit/9750b7a1f01d6f0690221c6091b16c51784df0a3)) + - sketch new section and metadata ([`9cb9acb`](https://github.com/Byron/gitoxide/commit/9cb9acb7b7ebada4d6bb3eef199337912ceeaa36)) + - add `Source` type to allow knowing where a particular value is from. ([`c92d5c6`](https://github.com/Byron/gitoxide/commit/c92d5c6a223e377c10c2ca6b822e7eeb9070e12c)) + - `Boolean` can use numbers to indicate true or false, drops support for `one` and `zero`. ([`6b90184`](https://github.com/Byron/gitoxide/commit/6b901843cb18b3d31f8b0b84bb9ebbae279aff19)) + - All accessors in `File` are now using `impl AsRef` where possible for added comfort. ([`3de0cfd`](https://github.com/Byron/gitoxide/commit/3de0cfd81523e4ba7cc362d8625f85ebf8fd9172)) + - Much more comfortable API `file::*Mut` types thanks to `impl Into/AsRef`. ([`3d25fe6`](https://github.com/Byron/gitoxide/commit/3d25fe6c7a52529488fab19c927d64a1bc75838f)) + - Rename `Mutable*` into `$1Mut` for consistency. ([`393b392`](https://github.com/Byron/gitoxide/commit/393b392d515661e5c3e60629319fdab771c3d3f0)) + - `file::MutableSection::remove()` now actually removes keys _and_ values. ([`94dde44`](https://github.com/Byron/gitoxide/commit/94dde44e8dd1a0b8d4e11f2627a3f6b345a15989)) + - many more tests for MutableSection ([`ac843cb`](https://github.com/Byron/gitoxide/commit/ac843cbef4a6322be706b978e6691bc36c5e458f)) + - refactor ([`701266e`](https://github.com/Byron/gitoxide/commit/701266e6e52456c0c1938732c260be19ec8029c9)) + - conform APIs of `file::MutableValue` and `file::MutableMultiValue`. ([`0a7391a`](https://github.com/Byron/gitoxide/commit/0a7391a6575f4035c51a46d34fa20c69e9d078e9)) + - `file::MutableMultiValue` escapes input values and maintains key separator specific whitespace. ([`048b925`](https://github.com/Byron/gitoxide/commit/048b92531eb877a5a128e702504891bf1e31becf)) + - place spaces around `key = value` pairs, or whatever is used in the source configuration. ([`5418bc7`](https://github.com/Byron/gitoxide/commit/5418bc70e67476f8778656f2d577f1f9aa65ffbe)) + - avoid extra copies when setting values and escaping them ([`a7eff01`](https://github.com/Byron/gitoxide/commit/a7eff0166f200a403d4dba320280f20a70e9afc7)) + - refactor ([`15cd1d2`](https://github.com/Byron/gitoxide/commit/15cd1d2ba447ff27819f6cf398d31e96ff11b213)) + - more empty-value tests ([`511985a`](https://github.com/Byron/gitoxide/commit/511985a8084f2a00e0550e5f2a85c93779385a1b)) + - default space is just a single tab, not two ones ([`7e03b83`](https://github.com/Byron/gitoxide/commit/7e03b835bd6f0f5b3f00dbc63e7960ce6364eaef)) + - proper escaping of value bytes to allow round-tripping after mutation ([`8118644`](https://github.com/Byron/gitoxide/commit/8118644625dc25b616e5f33c85f5100d600766e4)) + - refactor ([`afa736a`](https://github.com/Byron/gitoxide/commit/afa736aba385bd52e7f11fd89538aea99787ac9d)) + - a few tests for `MutableValue` showing that it's too buggy right now ([`5e6f9d9`](https://github.com/Byron/gitoxide/commit/5e6f9d909db41926e829e464abc53ef05fbf620b)) + - rename `file::MutableSection::set_leading_space()` to `set_leading_whitespace()`. ([`83a0922`](https://github.com/Byron/gitoxide/commit/83a0922f06081312b79908835dac2b7f4e849bb3)) + - whitespace in newly pushed keys is derived from first section value. ([`9f59356`](https://github.com/Byron/gitoxide/commit/9f59356b4f6a1f5f7f35a62c9fbe4859bf8e8e5f)) + - `File::from_str()` implementation, to support `let config: File = "[core]".parse()?` ([`db1f34d`](https://github.com/Byron/gitoxide/commit/db1f34dfb855058ac08e97d4715876b5db712f61)) + - whitespace in mutable sections can be finely controlled, and is derived from existing sections ([`9157717`](https://github.com/Byron/gitoxide/commit/9157717c2fb143b5decbdf60d18cc2bd99dde775)) + - refactor ([`c88eea8`](https://github.com/Byron/gitoxide/commit/c88eea87d7ece807ca5b1753b47ce89d3ad6a502)) + - refactor ([`a0d6caa`](https://github.com/Byron/gitoxide/commit/a0d6caa243aa293386d4ad164e1604f0e71c2cf3)) + - auto-compute whitespace for sections, even though it probably needs to be better than that ([`ee9ac95`](https://github.com/Byron/gitoxide/commit/ee9ac953180886cc483e1125b7f4e172af92c3ce)) + - validation for Keys and header names ([`59ec7f7`](https://github.com/Byron/gitoxide/commit/59ec7f7bf019d269573f8cc69f6d34b9458b1f1a)) + - Simplify specifying keys when mutating config values. ([`a93a156`](https://github.com/Byron/gitoxide/commit/a93a156655d640ae63ff7c35b0a1f5d67a5ca20f)) + - `File::rename_section()` with validation of input arguments. ([`895ce40`](https://github.com/Byron/gitoxide/commit/895ce40aabbe6d6af5b681a0d0942303fd6549a2)) + - re-add newlines after multi-line values ([`9a2f597`](https://github.com/Byron/gitoxide/commit/9a2f59742cf94643c5b9967b76042bcc7a4e1a71)) + - more header escaping tests ([`12cf005`](https://github.com/Byron/gitoxide/commit/12cf0052d92ee5bee1926f50c879526b5903c175)) + - Enforce `parse::section::Header::new()` by making its fields private. ([`219cf7a`](https://github.com/Byron/gitoxide/commit/219cf7ae0b35b3ac92f97974be52cd022698e01f)) + - `parse::Header::new(…)` with sub-section name validation ([`ae3895c`](https://github.com/Byron/gitoxide/commit/ae3895c7882e0a543a44693faee5f760b49b54d7)) + - section names are now validated. ([`cfd974f`](https://github.com/Byron/gitoxide/commit/cfd974f46d2cbb99e7784a05f5e358fed0d4bcab)) + - prepare for validation of `parse::section::Header` ([`00592f6`](https://github.com/Byron/gitoxide/commit/00592f6b80abe15a32a890ddc2b1fbf6701798d8)) + - basic escaping of subsection names during serialization ([`00d1a9b`](https://github.com/Byron/gitoxide/commit/00d1a9b741845b49d8691262bef6e5c21876567e)) + - refactor ([`9fac8e0`](https://github.com/Byron/gitoxide/commit/9fac8e0066c9b1845d9e06fb30b61ca9e9d64555)) + - new roundtrip test on file level ([`78bb93c`](https://github.com/Byron/gitoxide/commit/78bb93cf35b6a990bac64bbfc56144799ad36243)) + - Add `File::write_to()` and `File::to_bstring()`; remove some `TryFrom` impls. ([`4f6cd8c`](https://github.com/Byron/gitoxide/commit/4f6cd8cf65c2d8698bffe327a19031c342b229a6)) + - remove `Integer::to_bstring()` as well as some `TryFrom` impls. ([`0e392f8`](https://github.com/Byron/gitoxide/commit/0e392f81e99c8c0ff29f41b9b86afd57cd99c245)) + - remove `Boolean::to_bstring()` along with a few `From` impls. ([`b22732a`](https://github.com/Byron/gitoxide/commit/b22732a2ab17213c4a1020859ec41f25ccabfbfc)) + - Add `parse::(Event|section::Header|Comment)::write_to(…)`. ([`d087f12`](https://github.com/Byron/gitoxide/commit/d087f12eec73626eb327eaacef8ebb3836b02381)) + - fix tests on windows ([`3d7fc18`](https://github.com/Byron/gitoxide/commit/3d7fc188914337074775863acc1d6c15f47e913c)) + - value normalization (via `value::normalize()` handles escape sequences. ([`f911707`](https://github.com/Byron/gitoxide/commit/f911707b455ba6f3800b85f667f91e4d56027b91)) + - refactor normalization and more tests ([`cf3bf4a`](https://github.com/Byron/gitoxide/commit/cf3bf4a3bde6cdf20c63ffee1a5ae55a1f4e1742)) + - more escape characters for normalization ([`b92bd58`](https://github.com/Byron/gitoxide/commit/b92bd580de45cb58cd2b3c4af430273e96139c79)) + - review docs of `file::mutating` ([`2d5703e`](https://github.com/Byron/gitoxide/commit/2d5703e5909946e4327e0372097273facaeca759)) + - stable sort order for `File::sections_by_name_with_header()` ([`44dfec0`](https://github.com/Byron/gitoxide/commit/44dfec07480cc2ac6fd01674b748cc03af51fed1)) + - review `file::raw` module ([`6acf4a4`](https://github.com/Byron/gitoxide/commit/6acf4a43fd63c1c5e24b2e21702dc79827e3d11e)) + - don't over-normalize in comfort layer - all values are normalized now ([`b979a3b`](https://github.com/Byron/gitoxide/commit/b979a3b318faada23a6cf073953b13f7828398af)) + - docs for comfort level File API ([`eafc6ce`](https://github.com/Byron/gitoxide/commit/eafc6ce14a9f3d3dbc585e34e465609385f07f69)) + - review and refactor 'File::value' module ([`7aa8a0b`](https://github.com/Byron/gitoxide/commit/7aa8a0b66f3508336e8c20a1a0d2b481e7b9bde8)) + - allocation free `File::sections_by_name()` and `File::sections_by_name_with_header()`. ([`65c520c`](https://github.com/Byron/gitoxide/commit/65c520c4de8187884f87059adf5cef9cbdcd90a2)) + - refactor ([`2abffd6`](https://github.com/Byron/gitoxide/commit/2abffd6f2224edd98f806b5dbd4fc0e1c60019c5)) + - refactor ([`539c2f6`](https://github.com/Byron/gitoxide/commit/539c2f67bede1247478ce75429690c2904915a89)) + - refactor ([`f1668e9`](https://github.com/Byron/gitoxide/commit/f1668e9d9e94f166fa05164612eab9ee26357d12)) + - refactor ([`2599680`](https://github.com/Byron/gitoxide/commit/2599680f7479e18612b4379efbe918139dde2345)) + - refactor ([`879fad5`](https://github.com/Byron/gitoxide/commit/879fad5afdcd90e248934e9c3b973d7bd438d1f9)) + - fix docs ([`b2b82da`](https://github.com/Byron/gitoxide/commit/b2b82da6c6d3c71b249c9ff2055cd98a58f1d988)) + - once again zero-allocation for SectionBodyIter ([`ba69124`](https://github.com/Byron/gitoxide/commit/ba691243778b3eb89452fd1277c50dfe83d0075f)) + - refactor ([`33efef6`](https://github.com/Byron/gitoxide/commit/33efef6de375e399fe33a02e7b6dace1a679ac7e)) + - docs and refactor ([`700d6aa`](https://github.com/Byron/gitoxide/commit/700d6aa34f2604ee72e619afb15c1bb6ce1697f2)) + - `Path::interpolate()` now takes `path::interpolate::Options` instead of three parameters. ([`ac57c44`](https://github.com/Byron/gitoxide/commit/ac57c4479e7b6867e8b8e71f7cf76de759dc64a2)) + - refactor `from_env` ([`c8693f9`](https://github.com/Byron/gitoxide/commit/c8693f9058765671804c93ead1eea1175a94f87c)) + - make fmt ([`a7d7751`](https://github.com/Byron/gitoxide/commit/a7d7751822a1a8ac89930031707af57ad95d9cbd)) + - more doc adjustments ([`95fc20a`](https://github.com/Byron/gitoxide/commit/95fc20a377aeb914d6b527c1d1b8e75d8c42c608)) + - review docs of 'parse' module; refactor ([`a361c7f`](https://github.com/Byron/gitoxide/commit/a361c7ff290cdae071a12351330013ad0043b517)) + - refactor ([`8e84fda`](https://github.com/Byron/gitoxide/commit/8e84fdadfc49ba61f258286acb0a707bfb2a396b)) + - `File::raw_multi_value()` to `File::raw_values()` ([`9cd9933`](https://github.com/Byron/gitoxide/commit/9cd99337333f5ef4b30e0ec9461fc087699576e6)) + - `File::raw_multi_value_mut()` to `File::raw_values_mut()` ([`0076dcf`](https://github.com/Byron/gitoxide/commit/0076dcf9b37f1d633bdad5573b40d34a9fbaba90)) + - `File::multi_value()` to `File::values()`. ([`a8604a2`](https://github.com/Byron/gitoxide/commit/a8604a237782f8d60a185d4730db57bad81424a6)) + - remove `String` type in favor of referring to the `File::string()` method. ([`0915051`](https://github.com/Byron/gitoxide/commit/0915051798dd782b40617a1aa16abd71f6db1175)) + - fix docs ([`8fa7600`](https://github.com/Byron/gitoxide/commit/8fa7600847da6946784466213cea4c32ff9f7f92)) + - refactor ([`b78e3fa`](https://github.com/Byron/gitoxide/commit/b78e3fa792fad4f3e3f9d5c668afccd75bc551e0)) + - change! Add `home_for_user` in `Path::interpolate(…)`. ([`f9e0ef3`](https://github.com/Byron/gitoxide/commit/f9e0ef38e97fbc1e123d310dc696270d496438b6)) + - Simplify `Boolean` to be a wrapper around `bool`. ([`9cadc6f`](https://github.com/Byron/gitoxide/commit/9cadc6f0cbaad0ac23f5469db2f040aecfbfb82c)) + - Use bitflags for `color::Attribute` instead of `Vec` of enums. ([`703922d`](https://github.com/Byron/gitoxide/commit/703922dd4e1e5b27835298217ff4eb8ef1dc57ce)) + - A bitflag version of color attributes ([`23ec673`](https://github.com/Byron/gitoxide/commit/23ec673baaf666fc38fda2f3b1ace9a8cf6816b8)) + - refactor ([`4f21d1e`](https://github.com/Byron/gitoxide/commit/4f21d1ed145bfd0d56d31be73fade25b104bab53)) + - simplify `Color` API. ([`3fc4ac0`](https://github.com/Byron/gitoxide/commit/3fc4ac04f46f869c6e3a94ce4bb8a5737aa0c524)) + - deduplicate ([`c1b9cd4`](https://github.com/Byron/gitoxide/commit/c1b9cd443ec103a01daee8b8226a53f560d62498)) + - first tests for colors specifically; fix space between tokens ([`e2bd055`](https://github.com/Byron/gitoxide/commit/e2bd0557d9ab68a02216c252ab20aaec2e4efd4e)) + - count newlines (for error display) in multi-line values as well ([`1ea919d`](https://github.com/Byron/gitoxide/commit/1ea919d5ff81ab7b01b8201386ef63c7e081b537)) + - zero-copy for section names ([`25b9760`](https://github.com/Byron/gitoxide/commit/25b9760f9a6a79c6e28393f032150e37d5ae831e)) + - prepare for copy-on-write subsections ([`7474997`](https://github.com/Byron/gitoxide/commit/7474997216df2616a034fb9adc0938590f3ab7ed)) + - another normalization case ([`637fe8f`](https://github.com/Byron/gitoxide/commit/637fe8fca2ce36e07ad671a4454da512b709045c)) + - allow backspaces in value parser ([`199e546`](https://github.com/Byron/gitoxide/commit/199e5461cb85b11ce0b9a0e727fab40a49b78456)) + - another failing test pointing at issues with normalization/escaping in parser ([`3c29321`](https://github.com/Byron/gitoxide/commit/3c2932167aa45a89974be79123932bc964fe3ea9)) + - found failing test with complex multi-line value ([`117401d`](https://github.com/Byron/gitoxide/commit/117401ddb9dea1d78b867ddbafe57c2b37ec10f4)) + - review `git-config::File` docs and rename some internal symbols ([`5a8b111`](https://github.com/Byron/gitoxide/commit/5a8b111b9a3bba2c01d7d5e32fc58fd8a64b81ad)) + - more correctness for sub-section parsing ([`910af94`](https://github.com/Byron/gitoxide/commit/910af94fe11bc6e1c270c5512af9124f8a2e0049)) + - reduce top-level docs ([`cdfb13f`](https://github.com/Byron/gitoxide/commit/cdfb13f5984c92c8e7f234e7751b66930291b461)) + - refactor; remove unnecessary docs ([`c95e0b9`](https://github.com/Byron/gitoxide/commit/c95e0b9331282e029ef6188880d11a892ed1b4bf)) + - assure no important docs are missed ([`f5026fb`](https://github.com/Byron/gitoxide/commit/f5026fb3b64bccf26bc8d5a74dbc5e89b98d9959)) + - filtering supportort for `parse::Events`. ([`6ba2f80`](https://github.com/Byron/gitoxide/commit/6ba2f8060768978ad7204e162fb2253ca8843879)) + - deduplicate events instantiation ([`ead757c`](https://github.com/Byron/gitoxide/commit/ead757c2a4b737d2f617cf23c370e2ca5c46b08b)) + - unclutter lifetime declarations ([`e571fdb`](https://github.com/Byron/gitoxide/commit/e571fdb4630ff373ece02efcd963724c05978ede)) + - remove redundant documentation about errors ([`183c7ae`](https://github.com/Byron/gitoxide/commit/183c7ae0d5f44bb468954a7ad18cc02a01d717bc)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + - remove `parse::Events::from_path` and `File::at` ([`14149ee`](https://github.com/Byron/gitoxide/commit/14149eea54e2e8a25ac0ccdb2f6efe624f6eaa22)) + - try to strike a balance between allocations and memory footprint ([`52bd1e7`](https://github.com/Byron/gitoxide/commit/52bd1e7455d2b09811ea0ac5140c3693d3c1e1f7)) + - allocation-free parsing as callback is passed through ([`ed00e22`](https://github.com/Byron/gitoxide/commit/ed00e22cbdfea1d69d1d4c2b829effc26b493185)) + - foundation for allocation free (and smallvec free) parsing ([`307c1af`](https://github.com/Byron/gitoxide/commit/307c1afebfba952a4931a69796686b8a998c4cd9)) + - Slim down API surface of `parse::Events`. ([`73adcee`](https://github.com/Byron/gitoxide/commit/73adceeae12270c0d470d4b7271c1fd6089d5c2d)) + - remove `File::new()` method in favor of `File::default()`. ([`2e47167`](https://github.com/Byron/gitoxide/commit/2e47167e4a963743494b2df6b0c15800cb876dd0)) + - a greatly simplified Events->File conversion ([`c5c4398`](https://github.com/Byron/gitoxide/commit/c5c4398a56d4300c83c5be2ba66664bd11f49d5e)) + - fix docs ([`5022be3`](https://github.com/Byron/gitoxide/commit/5022be3bb7fa54c97e5110f74aaded9e2f1b6ca5)) + - about 30% faster parsing due to doing no less allocations for section events ([`050d0f0`](https://github.com/Byron/gitoxide/commit/050d0f0dee9a64597855e85417460f6e84672b02)) + - allocation-free fuzzing, with optimized footprints ([`2e149b9`](https://github.com/Byron/gitoxide/commit/2e149b982ec57689c161924dd1d0b22c4fcb681f)) + - allocation-free sections ([`d3a0c53`](https://github.com/Byron/gitoxide/commit/d3a0c53864ccc9f8d2851d06f0154b9e8f9bcda7)) + - allocation-free frontmatter ([`6c3f326`](https://github.com/Byron/gitoxide/commit/6c3f3264911042e88afa0819414eb543a3626d11)) + - remove last duplicate of top-level parse function ([`cd7a21f`](https://github.com/Byron/gitoxide/commit/cd7a21f8381385833f5353925dc57c05c07e718d)) + - workaround lack of GAT! ([`4fb327c`](https://github.com/Byron/gitoxide/commit/4fb327c247f1c0260cb3a3443d81063b71e87fe4)) + - remove duplication of top-level parser ([`0f5c99b`](https://github.com/Byron/gitoxide/commit/0f5c99bffdb61e4665e83472275c5c8b0383650b)) + - a minimally invasive sketch of a parse Delegate ([`5958ffb`](https://github.com/Byron/gitoxide/commit/5958ffbfec7724c1a47be8db210df03cf54c9374)) + - fix docs ([`2186456`](https://github.com/Byron/gitoxide/commit/218645618429258e48cb0fdb2bbfba3daa32ee2d)) + - fix fuzz crash in parser ([`86e1a76`](https://github.com/Byron/gitoxide/commit/86e1a76484be50f83d06d6c8a176107f8cb3dea6)) + - rename `parse::event::List` to `parse::Events` ([`ea67650`](https://github.com/Byron/gitoxide/commit/ea6765093b5475912ba1aa81d4440cbf5dd49fb6)) + - rename `parse::State` to `parse::event::List` ([`89f5fca`](https://github.com/Byron/gitoxide/commit/89f5fca843d999c5bea35fb3fe2a03dc3588f74e)) + - update fuzz instructions and make it work ([`19300d5`](https://github.com/Byron/gitoxide/commit/19300d5f37c201aba921a6bff9760996fec2108e)) + - improve normalization; assure no extra copies are made on query. ([`4a01d98`](https://github.com/Byron/gitoxide/commit/4a01d983f54a7713dea523f6032cbf5bb2b9dde8)) + - refactor; assure `normalize` doesn't copy unnecessarily ([`ce069ca`](https://github.com/Byron/gitoxide/commit/ce069ca0b6b44cd734f4d8b4525916d1ddb0de0b)) + - normalize values in all the right places ([`91ba2dd`](https://github.com/Byron/gitoxide/commit/91ba2ddcd3de63aa22dc6e863b26ce1893a36995)) + - avoid unnecessary clones ([`e684488`](https://github.com/Byron/gitoxide/commit/e68448831a94574ee3ca2fa36788f603c91d57a0)) + - adapt to changes in `git-config` ([`363a826`](https://github.com/Byron/gitoxide/commit/363a826144ad59518b5c1a3dbbc82d04e4fc062d)) + - move `value::*` into the crate root, except for `Error` and `normalize_*()`. ([`3cdb089`](https://github.com/Byron/gitoxide/commit/3cdb0890b71e62cfa92b1ed1760c88cb547ec729)) + - rename `value::parse::Error` to `value::Error`. ([`748d921`](https://github.com/Byron/gitoxide/commit/748d921efd7469d5c19e40ddcb9099e2462e3bbc)) + - rename `value::TrueVariant` to `value::boolean::True` ([`7e8a225`](https://github.com/Byron/gitoxide/commit/7e8a22590297f2f4aab76b53be512353637fb651)) + - rename `IntegerSuffix` to `integer::Suffix` ([`8bcaec0`](https://github.com/Byron/gitoxide/commit/8bcaec0599cf085a73b344f4f53fc023f6e31430)) + - rename `value::Color(Attribute|Value)` to `value::color::Attribute` and `value::color::Name`. ([`d085037`](https://github.com/Byron/gitoxide/commit/d085037ad9c067af7ce3ba3ab6e5d5ddb45b4057)) + - refactor ([`a0f7f44`](https://github.com/Byron/gitoxide/commit/a0f7f44c4fca20d3c9b95a3fafe65cef84c760e7)) + - refactor ([`0845c84`](https://github.com/Byron/gitoxide/commit/0845c84b6f694d97519d5f86a97bca49739df8bf)) + - keep str in value API ([`ef5b48c`](https://github.com/Byron/gitoxide/commit/ef5b48c71e0e78fa602699a2f8ca8563c10455c4)) + - Keep BStr even though str could be used. ([`aeca6cc`](https://github.com/Byron/gitoxide/commit/aeca6cce7b4cfe67b18cd80abb600f1271ad6057)) + - Turn `parse::ParseOrIoError` into `parse::state::from_path::Error` ([`a0f6252`](https://github.com/Byron/gitoxide/commit/a0f6252343a62b0b55eef02888ac00c09100687a)) + - rename `parse::ParsedComment` into `parse::Comment` ([`b6b31e9`](https://github.com/Byron/gitoxide/commit/b6b31e9c8dd8b3dc4860431069bb1cf5eacd1702)) + - Allocation-free hashing for section keys and names ([`44d0061`](https://github.com/Byron/gitoxide/commit/44d00611178a4e2f6a080574c41355a50b79b181)) + - allocation-free case-inequality tests for section keys and names ([`94608db`](https://github.com/Byron/gitoxide/commit/94608db648cd717af43a97785ea842bc75361b7e)) + - rename `parse::Section*` related types. ([`239cbfb`](https://github.com/Byron/gitoxide/commit/239cbfb450a8cddfc5bec1de21f3dc54fab914ce)) + - adjustments required due to changed in `git-config` ([`41bfd3b`](https://github.com/Byron/gitoxide/commit/41bfd3b4122e37370d268608b60cb00a671a8879)) + - rename `parse::Parser` to `parse::State`. ([`60af4c9`](https://github.com/Byron/gitoxide/commit/60af4c9ecb1b99f21df0e8facc33e5f6fc70c424)) + - rename `parser` module to `parse` ([`3724850`](https://github.com/Byron/gitoxide/commit/3724850e0411f1f76e52c6c767fd8cebe8aea0f6)) + - fix docs ([`b05aed1`](https://github.com/Byron/gitoxide/commit/b05aed1cfc15a2e29d7796bad4c9a6d4019f4353)) + - refactor ([`8bd9cd6`](https://github.com/Byron/gitoxide/commit/8bd9cd695d608d05859d8bff4033883e71ce7caa)) + - refactor ([`90dd2ce`](https://github.com/Byron/gitoxide/commit/90dd2cec8ea88980365bfd08a16614d145e87095)) + - fix docs ([`0d1be2b`](https://github.com/Byron/gitoxide/commit/0d1be2b893574f2a9d4ba35ac4f2b3da710d4b03)) + - rename `normalize_cow()` to `normalize()` and move all `normalize*` functions from `values` to the `value` module ([`58b2215`](https://github.com/Byron/gitoxide/commit/58b22152a0295998935abb43563e9096589ef53e)) + - Documentation for feature flags ([`26e4a9c`](https://github.com/Byron/gitoxide/commit/26e4a9c83af7550eab1acaf0256099774be97965)) + - `serde1` feature to add limited serde support ([`5a8f242`](https://github.com/Byron/gitoxide/commit/5a8f242ee98793e2467e7bc9806f8780b9d320ce)) + - remove unused serde feature ([`66a8237`](https://github.com/Byron/gitoxide/commit/66a8237ff284c2cf7f80cc909c7b613b599e1358)) + - move `Path` from `values` to `value` module ([`767bedc`](https://github.com/Byron/gitoxide/commit/767bedccdae1f3e6faf853d59ecf884a06cc3827)) + - Move `Boolean` and `String` from `values` into `value` module ([`6033f3f`](https://github.com/Byron/gitoxide/commit/6033f3f93d2356399a661567353a83a044662699)) + - move `values::Integer` into `value` module ([`d4444e1`](https://github.com/Byron/gitoxide/commit/d4444e18042891b0fe5b9c6e6813fed26df6c560)) + - move `Color` to own `value` module ([`38f3117`](https://github.com/Byron/gitoxide/commit/38f31174e8c117af675cdfbc21926133b821ec38)) + - Make symlink tests so that they test real-path conversion ([`d4fbf2e`](https://github.com/Byron/gitoxide/commit/d4fbf2ea71ee1f285c195dd00bfa4e21bf429922)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + - a test to validate relative includepaths aren't valid for includeIf ([`7d27dd5`](https://github.com/Byron/gitoxide/commit/7d27dd5e3558a22865e0c9159d269577431097f3)) + - reuse the initialized environment for a little speed ([`6001613`](https://github.com/Byron/gitoxide/commit/600161324edc370707613841ce9228320c700bf6)) + - Also test against git baseline ([`adcddb0`](https://github.com/Byron/gitoxide/commit/adcddb0056c14302f0133de251fa07e877b6f509)) + - refactor ([`0229e25`](https://github.com/Byron/gitoxide/commit/0229e2583ed7beccaf59dc0c82893c5b67c285dd)) + - prevent race when calling `git` around `GIT_CONFIG_*` env vars ([`53efbf5`](https://github.com/Byron/gitoxide/commit/53efbf54364c373426a7790c28c74c787670877a)) + - remove duplicate gitdir tests that don't have a baseline ([`5c71394`](https://github.com/Byron/gitoxide/commit/5c713946b1f35675bacb27bd5392addf25010942)) + - remove unmotivated forward-slash conversion ([`3af09e5`](https://github.com/Byron/gitoxide/commit/3af09e5800648df87cdaf22191dd4d1dc4b278a3)) + - improved slash/backslash handling on windows ([`a3b7828`](https://github.com/Byron/gitoxide/commit/a3b7828e8bf9d90775f10b0d996fc7ad82f92466)) + - fix build warnings on windows ([`9d48b2f`](https://github.com/Byron/gitoxide/commit/9d48b2f51777de37cc996ad54261f2d20f417901)) + - fix windows test ([`a922f0a`](https://github.com/Byron/gitoxide/commit/a922f0a817d290ef4a539bbf99238a4f96d443f9)) + - refactor ([`d76aee2`](https://github.com/Byron/gitoxide/commit/d76aee22498cb980ab0b53295a2e51af04a8cb7c)) + - conforming subsection parsing handling backslashes like git ([`6366148`](https://github.com/Byron/gitoxide/commit/6366148f538ee03314dd866e083157de810d4ad4)) + - Only copy pattern if required ([`b3a752a`](https://github.com/Byron/gitoxide/commit/b3a752a0a873cf9d685e1893c8d35255d7f7323a)) + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) + - thanks fuzzy ([`15a379a`](https://github.com/Byron/gitoxide/commit/15a379a85d59d83f3a0512b9e9fbff1774c9f561)) + - thanks clippy ([`15fee74`](https://github.com/Byron/gitoxide/commit/15fee74fdfb5fc84349ac103cd5727332f3d2230)) + - thanks clippy ([`0b05be8`](https://github.com/Byron/gitoxide/commit/0b05be850d629124f027af993e316b9018912337)) + - thanks clippy ([`693e304`](https://github.com/Byron/gitoxide/commit/693e304a2c38130ed936d5e4544faaa858665872)) + - fix git-config/tests/.gitattributes ([`a741766`](https://github.com/Byron/gitoxide/commit/a7417664ca1e41936f9de8cf066e13aeaf9b0d75)) + - Merge branch 'config-metadata' ([`453e9bc`](https://github.com/Byron/gitoxide/commit/453e9bca8f4af12e49222c7e3a46d6222580c7b2)) + - forced checkin to fix strange crlf issue ([`5d0a5c0`](https://github.com/Byron/gitoxide/commit/5d0a5c0712fbd8fcc00aff54563c83281afc9476)) + - thanks clippy ([`e5ba0f5`](https://github.com/Byron/gitoxide/commit/e5ba0f532bf9bfee46d2dab24e6a6503df4d239d)) + - thanks clippy ([`00bfbca`](https://github.com/Byron/gitoxide/commit/00bfbca21e2361008c2e81b54424a9c6f09e76e9)) + - thanks clippy ([`09e2374`](https://github.com/Byron/gitoxide/commit/09e23743035b9d4463f438378aed54677c03311f)) + - thanks clippy ([`e842633`](https://github.com/Byron/gitoxide/commit/e84263362fe0631935379a0b4e8d8b1fcf6ac81b)) + - thanks clippy ([`3ca8027`](https://github.com/Byron/gitoxide/commit/3ca8027e07a835e84a704688778cfb82c956643b)) + - make fmt ([`aa9fdb0`](https://github.com/Byron/gitoxide/commit/aa9fdb0febfb29f906eb81e4378f07ef01b03e05)) + - thanks clippy ([`c9a2390`](https://github.com/Byron/gitoxide/commit/c9a239095511ae95fb5efbbc9207293641b623f7)) + - thanks clippy ([`badd00c`](https://github.com/Byron/gitoxide/commit/badd00c402b59994614e653b28bb3e6c5b70d9d1)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - thanks clippy ([`b246f0a`](https://github.com/Byron/gitoxide/commit/b246f0ade5aa42413cc387470b35df357b1136bc)) + - thanks clippy ([`08441de`](https://github.com/Byron/gitoxide/commit/08441def5d1738bbf13b68979f2d1ff7ff3b4153)) + - thanks clippy ([`8b29dda`](https://github.com/Byron/gitoxide/commit/8b29ddaa627048b9ca130b52221709a575f50d3a)) + - thanks clippy ([`cff6e01`](https://github.com/Byron/gitoxide/commit/cff6e018a8f0c3b6c78425f99a204d29d72a65aa)) + - thanks clippy ([`f7be3b0`](https://github.com/Byron/gitoxide/commit/f7be3b0f79bf19faf5a3b68032f764c0b7a12d7e)) + - thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) + - Allow backslashes in subsections ([`6f4f325`](https://github.com/Byron/gitoxide/commit/6f4f325a42656800c8c76c8eae075508c31657be)) + - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) + - thanks clippy ([`9b6a67b`](https://github.com/Byron/gitoxide/commit/9b6a67bf369fcf51c6a3289784c3ef8ab366bee7)) + - remove `values::Bytes` - use `values::String` instead. ([`aa630ad`](https://github.com/Byron/gitoxide/commit/aa630ad6ec2c6306d3307d5c77e272cb24b00ddd)) + - change mostily internal uses of [u8] to BString/BStr ([`311d4b4`](https://github.com/Byron/gitoxide/commit/311d4b447daf8d4364670382a20901468748d34d)) + - Definitely don't unconditionally convert to forward slashes ([`146eb0c`](https://github.com/Byron/gitoxide/commit/146eb0c831ce0a96e215b1ec6499a86bbf5902c9)) + - avoid panics and provide errors instead of just not matching ([`a0f842c`](https://github.com/Byron/gitoxide/commit/a0f842c7f449a6a7aedc2742f7fc4f74a12fdd17)) + - try to fix git-config tests on windows even harder ([`16778d4`](https://github.com/Byron/gitoxide/commit/16778d478d6941ab86571de0bd99aaab816ffe67)) + - try once more to get failing tests under control on windows ([`c26c2e9`](https://github.com/Byron/gitoxide/commit/c26c2e962aa6a93c0e06b900dc719f9cd92f6137)) + - thanks clippy ([`27b2dde`](https://github.com/Byron/gitoxide/commit/27b2dde9a299aca112347f988fa21d797f64552b)) + - fix test with brute force; take some notes for later ([`2eda529`](https://github.com/Byron/gitoxide/commit/2eda5296ad9ee58756d564225e98e64a800f46d7)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Take GitEnv by ref. ([`937d7ee`](https://github.com/Byron/gitoxide/commit/937d7eea84e92467fcc8a6a72c78fe6c060dd95d)) + - remove leftover debug printing ([`7d1cf34`](https://github.com/Byron/gitoxide/commit/7d1cf34e4535721db97f566734f68014ebc7c3e8)) + - auto-normalize string values to support quote removal in case of strings. ([`1e71e71`](https://github.com/Byron/gitoxide/commit/1e71e71c984289f0d7e0a39379ee6728918b7dc5)) + - refactor ([`1d6ba9b`](https://github.com/Byron/gitoxide/commit/1d6ba9bd719ad01ce22573cabd8022ddb675c5fc)) + - avoid unwrap() more as the test code matures ([`c2d7e80`](https://github.com/Byron/gitoxide/commit/c2d7e800abe022f5a2663176f0f6b3ac90eacf0e)) + - refactor ([`b5c0b30`](https://github.com/Byron/gitoxide/commit/b5c0b3011d2c0e63c933be42753aea65b88ca569)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - make '..' related tests work ([`5f11318`](https://github.com/Byron/gitoxide/commit/5f11318dc55b8dd8da016a4053cc4ad34b13fa97)) + - find a few cases that aren't according to spec by failing (and ignored) tests ([`f0e6ea9`](https://github.com/Byron/gitoxide/commit/f0e6ea9086ebfa134044568114bb578120eb5da9)) + - refactor ([`62e5396`](https://github.com/Byron/gitoxide/commit/62e5396ac9221f13437c87f06715c98989981785)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Invoke git only when necessary ([`556c7cf`](https://github.com/Byron/gitoxide/commit/556c7cff5f813e885598b4bd858c6c22cedf688b)) + - Also use git_path::realpath() in other places that used canonicalize before ([`08af648`](https://github.com/Byron/gitoxide/commit/08af648923c226a0330f0025784c42914d4fea7f)) + - Our own git_path::realpath doesn't have the questionmark? issue on windows ([`cfe196b`](https://github.com/Byron/gitoxide/commit/cfe196b23051e639cb1332f88f1ec917608fbbe1)) + - fix windows tests ([`47f10fe`](https://github.com/Byron/gitoxide/commit/47f10feb2b143b9b429237cf6a4a7424c2b9ab13)) + - more debugging for windows failures ([`e0a72e6`](https://github.com/Byron/gitoxide/commit/e0a72e65e4bbe76755aea1a905d69d74d01f543a)) + - no need for serial anymore ([`34bb715`](https://github.com/Byron/gitoxide/commit/34bb7152ca5992fc35be5f51016565a568916e7c)) + - Make a note to be sure we use the home-dir correctly in git-repository; avoid `dirs` crate ([`0e8cf19`](https://github.com/Byron/gitoxide/commit/0e8cf19d7f742f9400afa4863d302ba18a452adc)) + - finally all tests work without the need for dirs::home_dir() ([`180ce99`](https://github.com/Byron/gitoxide/commit/180ce99a016c17641990eb41b6bbe3b2407ab271)) + - refactor ([`00ba5d8`](https://github.com/Byron/gitoxide/commit/00ba5d8a53aae1c4adbb379c076651756e1af68d)) + - refactor ([`0eb7ced`](https://github.com/Byron/gitoxide/commit/0eb7ced6ec49fe6303659bdcab29952c5cea41bd)) + - Path-interpolation makes `home-dir` configurable. ([`edd2267`](https://github.com/Byron/gitoxide/commit/edd226719cd04a480274cb7d983b6d5d8bfdbb13)) + - refactor ([`aab9865`](https://github.com/Byron/gitoxide/commit/aab98656ee5c4abf65f79d403c1f0cb36fd0ee88)) + - Change last test to new simplified symlink setup ([`a40e3c9`](https://github.com/Byron/gitoxide/commit/a40e3c999baf203c92d0e5e53ee61c0032e32e51)) + - refactor ([`67677b0`](https://github.com/Byron/gitoxide/commit/67677b0edfa1faa0c011a225d41d78dbde3c5f15)) + - assure the IDE doesn't confuse a module with a test ([`7be0b05`](https://github.com/Byron/gitoxide/commit/7be0b05ff3a5bbea9d9712e4d13ee08cf9979861)) + - refactor ([`1203a14`](https://github.com/Byron/gitoxide/commit/1203a14eba79d335137c96d4ee573739df30b067)) + - refactor ([`a721efe`](https://github.com/Byron/gitoxide/commit/a721efecd36984064b4b31c715bbe011df2538ad)) + - refactor ([`2c8c6e5`](https://github.com/Byron/gitoxide/commit/2c8c6e53fd4681289c9fa2308735c779ed4eace5)) + - refactor ([`eb0ace1`](https://github.com/Byron/gitoxide/commit/eb0ace14a92899002749d6dbd99dac3a35d73c25)) + - refactor ([`8f8f873`](https://github.com/Byron/gitoxide/commit/8f8f873ae711eb5ae62f192f6731653f2bb7ff4b)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) + - Remove `git-config` test utilities from `git-path`. ([`c9933c0`](https://github.com/Byron/gitoxide/commit/c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce)) + - Add repo_dir to EnvOverwrite. ([`ed5c442`](https://github.com/Byron/gitoxide/commit/ed5c442cc4f0c546834f2e0e9dc553a221b6985d)) + - Use EnvOverwrite struct. ([`f2e124f`](https://github.com/Byron/gitoxide/commit/f2e124f60f8f9a0d517fddb029d795fa91bcda5a)) + - tempdir lives long enough for sure. ([`a41002f`](https://github.com/Byron/gitoxide/commit/a41002fe4004485fac429d904bc4e8b6842eaf3c)) + - Disable symlink tests on windows. ([`8de6b3d`](https://github.com/Byron/gitoxide/commit/8de6b3d42c89c741195e4add273a2d1e7b48fad9)) +
+ ## 0.5.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +575,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 7 commits contributed to the release over the course of 22 calendar days. + - 41 commits contributed to the release over the course of 22 calendar days. - 22 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#436](https://github.com/Byron/gitoxide/issues/436) @@ -30,11 +589,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#436](https://github.com/Byron/gitoxide/issues/436)** - Remove outdated examples ([`cb9529e`](https://github.com/Byron/gitoxide/commit/cb9529e18b222b9fd9f8c1bb0dba8038a6ea1d4b)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) + - make fmt ([`cd4f727`](https://github.com/Byron/gitoxide/commit/cd4f7279678678fa6f2e55d4e7681a2075f1d6cf)) + - Temp ignore symlink tests. ([`ec40b94`](https://github.com/Byron/gitoxide/commit/ec40b94bffda14b7b991dd57cd36d893f1f6962b)) + - fmt. ([`82ea726`](https://github.com/Byron/gitoxide/commit/82ea7261cfb75a01992489aa7631e2e6d807be06)) + - Use `dirs::home_dir()` ([`5767a50`](https://github.com/Byron/gitoxide/commit/5767a505f2f2cc3515eb604e39da673fa2e09454)) + - Try fix windows home. ([`393758e`](https://github.com/Byron/gitoxide/commit/393758e14a1b5ff14301f153807fe45623d9f973)) + - Add more tests. ([`db1204d`](https://github.com/Byron/gitoxide/commit/db1204d74b16ff7e905fb5b2d91d9ecb109bca07)) + - Add debug output. ([`52db5e8`](https://github.com/Byron/gitoxide/commit/52db5e8894c5033ec3d58894a7cf17b4f29e03f4)) + - Tests like git: https://github.com/git/git/blob/master/t/t1305-config-include.sh ([`c3a0454`](https://github.com/Byron/gitoxide/commit/c3a04548b08b6972ea0999b0030017d1a6002de2)) + - Start extracting gitdir tests cont. ([`22e5cbe`](https://github.com/Byron/gitoxide/commit/22e5cbece0206da6cf8890a831fd82847526396a)) - remove `pwd` crate dependency in favor of using libc directly ([`4adfa11`](https://github.com/Byron/gitoxide/commit/4adfa11d70cf78bed541fa59707e8a5082dda245)) - Drop non-existent config paths before parsing ([`475d6fa`](https://github.com/Byron/gitoxide/commit/475d6fab2467ad0499db7df2d4c99f74e43682fc)) + - Start extracting gitdir tests. ([`5aaf7ba`](https://github.com/Byron/gitoxide/commit/5aaf7ba93857f1e5570f64f4a9539cd3d547b81d)) + - thanks clippy ([`cfa577f`](https://github.com/Byron/gitoxide/commit/cfa577f84c45c7fbed27e6d59ef361f9ac5c2614)) + - refactor ([`da23958`](https://github.com/Byron/gitoxide/commit/da239580fca76011f91a45ae502af88c67d429a4)) + - Finalize onbranch tests; remove mixed ones in favor of specific cases ([`26680c4`](https://github.com/Byron/gitoxide/commit/26680c48951a82d5119f54c57b4e7045d2c20649)) + - refactor ([`11c417f`](https://github.com/Byron/gitoxide/commit/11c417fdc03331db2c4a778bc3e8038ffd0aff89)) + - More tests for branch matching prior to making tests clearer ([`31e6db8`](https://github.com/Byron/gitoxide/commit/31e6db8cdc959549a6c2754692d2471103ada64f)) + - Basic test-setup for more specialized tests ([`b4374d2`](https://github.com/Byron/gitoxide/commit/b4374d21882eca637ddbb80cdde1dac7bc68560e)) + - refactor ([`04da720`](https://github.com/Byron/gitoxide/commit/04da7207a7e44175dc96e4ea850274b2cc5a6d84)) + - Fix including .. path. ([`8891fea`](https://github.com/Byron/gitoxide/commit/8891feac0341960a6339ee86c671fc80c3133b4e)) + - Fix case-insensitive. ([`ca05802`](https://github.com/Byron/gitoxide/commit/ca058024e1e19818261fea39099c893d666928dc)) + - Fix \\ test. ([`ab555b5`](https://github.com/Byron/gitoxide/commit/ab555b557f4bd68b491a552a14cd4549c6a625bc)) + - fix tests on windows ([`bb3b4f0`](https://github.com/Byron/gitoxide/commit/bb3b4f013c862a4c017c65075919e1df59cc1986)) + - refactor ([`e1ba36f`](https://github.com/Byron/gitoxide/commit/e1ba36fab772417d9b60bf89cc49b45fbb7252f9)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) + - refactor ([`e47fb41`](https://github.com/Byron/gitoxide/commit/e47fb412a136d087c79710e7490d3e1c97d1f955)) + - refactor ([`56eadc8`](https://github.com/Byron/gitoxide/commit/56eadc8b565b2f8a272080bc8814d6665b3f1205)) + - refactor ([`0ccd8ae`](https://github.com/Byron/gitoxide/commit/0ccd8ae0ab01cdb5ae33dd79f486edfcee2b176a)) + - Try fix windows test. ([`e2e94db`](https://github.com/Byron/gitoxide/commit/e2e94db2cee237168d5c56db5c5e94a8b4317991)) + - Refactor include sequence test. ([`b4e657e`](https://github.com/Byron/gitoxide/commit/b4e657ed02cf062b1c2cb1f6c15abdf5d777c177)) + - Extract include_paths. ([`c078671`](https://github.com/Byron/gitoxide/commit/c0786717c4979810002365a68d31abbf21d90f2d)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) - Make `realpath()` easier to use by introducing `realpath_opt()`. ([`266d437`](https://github.com/Byron/gitoxide/commit/266d4379e9132fd7dd21e6c8fccb36e125069d6e)) + - Adjust test structure to mirror the new code structure. ([`984b58e`](https://github.com/Byron/gitoxide/commit/984b58ee1dac58fe0dfd0b80f990ca37d323cad7)) + - Refact. ([`d5d81bc`](https://github.com/Byron/gitoxide/commit/d5d81bc16116b4c58f628e0e5c66d5d0a59b7816)) + - Read include and incideIf sections in correct order. ([`a4a7ebd`](https://github.com/Byron/gitoxide/commit/a4a7ebdb6fcb5f6183917719d6c93f54eea72e85)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - Merge branch 'davidkna-discover-x-fs' ([`9abaeda`](https://github.com/Byron/gitoxide/commit/9abaeda2d22e2dbb1db1632c6eb637f1458d06e1)) diff --git a/git-config/Cargo.toml b/git-config/Cargo.toml index 6a918a43991..8bc71da6630 100644 --- a/git-config/Cargo.toml +++ b/git-config/Cargo.toml @@ -15,11 +15,11 @@ include = ["src/**/*", "LICENSE-*", "README.md", "CHANGELOG.md"] serde1 = ["serde", "bstr/serde1", "git-sec/serde1", "git-ref/serde1", "git-glob/serde1"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features"} -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features"} +git-path = { version = "^0.4.0", path = "../git-path" } git-sec = { version = "^0.3.0", path = "../git-sec" } git-ref = { version = "^0.15.0", path = "../git-ref" } -git-glob = { version = "0.3.0", path = "../git-glob" } +git-glob = { version = "^0.3.1", path = "../git-glob" } nom = { version = "7", default_features = false, features = [ "std" ] } memchr = "2" diff --git a/git-config/src/file/access/comfort.rs b/git-config/src/file/access/comfort.rs index af734c7649f..0f15288a724 100644 --- a/git-config/src/file/access/comfort.rs +++ b/git-config/src/file/access/comfort.rs @@ -2,11 +2,11 @@ use std::{borrow::Cow, convert::TryFrom}; use bstr::BStr; -use crate::{value, File}; +use crate::{file::MetadataFilter, value, File}; /// Comfortable API for accessing values impl<'event> File<'event> { - /// Like [`value()`][File::value()], but returning an `None` if the string wasn't found. + /// Like [`value()`][File::value()], but returning `None` if the string wasn't found. /// /// As strings perform no conversions, this will never fail. pub fn string( @@ -15,25 +15,49 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key).ok() + self.string_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`string()`][File::string()], but the section containing the returned value must pass `filter` as well. + pub fn string_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter).ok() } /// Like [`value()`][File::value()], but returning `None` if the path wasn't found. /// /// Note that this path is not vetted and should only point to resources which can't be used - /// to pose a security risk. + /// to pose a security risk. Prefer using [`path_filter()`][File::path_filter()] instead. /// /// As paths perform no conversions, this will never fail. - // TODO: add `secure_path()` or similar to make use of our knowledge of the trust associated with each configuration - // file, maybe even remove the insecure version to force every caller to ask themselves if the resource can - // be used securely or not. pub fn path( &self, section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key) + self.path_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`path()`][File::path()], but the section containing the returned value must pass `filter` as well. + /// + /// This should be the preferred way of accessing paths as those from untrusted + /// locations can be + /// + /// As paths perform no conversions, this will never fail. + pub fn path_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter) .ok() .map(crate::Path::from) } @@ -45,7 +69,18 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - self.raw_value(section_name, subsection_name, key) + self.boolean_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`boolean()`][File::boolean()], but the section containing the returned value must pass `filter` as well. + pub fn boolean_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + self.raw_value_filter(section_name, subsection_name, key, filter) .ok() .map(|v| crate::Boolean::try_from(v).map(|b| b.into())) } @@ -57,7 +92,18 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option> { - let int = self.raw_value(section_name, subsection_name, key).ok()?; + self.integer_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Like [`integer()`][File::integer()], but the section containing the returned value must pass `filter` as well. + pub fn integer_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option> { + let int = self.raw_value_filter(section_name, subsection_name, key, filter).ok()?; Some(crate::Integer::try_from(int.as_ref()).and_then(|b| { b.to_decimal() .ok_or_else(|| value::Error::new("Integer overflow", int.into_owned())) @@ -74,6 +120,17 @@ impl<'event> File<'event> { self.raw_values(section_name, subsection_name, key).ok() } + /// Similar to [`strings(…)`][File::strings()], but all values are in sections that passed `filter`. + pub fn strings_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option>> { + self.raw_values_filter(section_name, subsection_name, key, filter).ok() + } + /// Similar to [`values(…)`][File::values()] but returning integers if at least one of them was found /// and if none of them overflows. pub fn integers( @@ -82,16 +139,30 @@ impl<'event> File<'event> { subsection_name: Option<&str>, key: impl AsRef, ) -> Option, value::Error>> { - self.raw_values(section_name, subsection_name, key).ok().map(|values| { - values - .into_iter() - .map(|v| { - crate::Integer::try_from(v.as_ref()).and_then(|int| { - int.to_decimal() - .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + self.integers_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Similar to [`integers(…)`][File::integers()] but all integers are in sections that passed `filter` + /// and that are not overflowing. + pub fn integers_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, + ) -> Option, value::Error>> { + self.raw_values_filter(section_name, subsection_name, key, filter) + .ok() + .map(|values| { + values + .into_iter() + .map(|v| { + crate::Integer::try_from(v.as_ref()).and_then(|int| { + int.to_decimal() + .ok_or_else(|| value::Error::new("Integer overflow", v.into_owned())) + }) }) - }) - .collect() - }) + .collect() + }) } } diff --git a/git-config/src/file/access/mod.rs b/git-config/src/file/access/mod.rs index 1640081a16e..d602b5f8be2 100644 --- a/git-config/src/file/access/mod.rs +++ b/git-config/src/file/access/mod.rs @@ -2,4 +2,3 @@ mod comfort; mod mutate; mod raw; mod read_only; -mod write; diff --git a/git-config/src/file/access/mutate.rs b/git-config/src/file/access/mutate.rs index 509e6ca75f3..7810f7c32a3 100644 --- a/git-config/src/file/access/mutate.rs +++ b/git-config/src/file/access/mutate.rs @@ -1,30 +1,54 @@ use std::borrow::Cow; +use git_features::threading::OwnShared; + use crate::{ - file::{rename_section, SectionBody, SectionMut}, + file::{self, rename_section, write::ends_with_newline, MetadataFilter, SectionId, SectionMut}, lookup, - parse::section, + parse::{section, Event, FrontMatterEvents}, File, }; /// Mutating low-level access methods. impl<'event> File<'event> { - /// Returns an mutable section with a given name and optional subsection. + /// Returns an mutable section with a given `name` and optional `subsection_name`. pub fn section_mut<'a>( &'a mut self, - section_name: impl AsRef, + name: impl AsRef, subsection_name: Option<&str>, ) -> Result, lookup::existing::Error> { let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? .rev() .next() .expect("BUG: Section lookup vec was empty"); - Ok(SectionMut::new( - self.sections - .get_mut(&id) - .expect("BUG: Section did not have id from lookup"), - )) + let nl = self.detect_newline_style_smallvec(); + Ok(self + .sections + .get_mut(&id) + .expect("BUG: Section did not have id from lookup") + .to_mut(nl)) + } + + /// Returns the last found mutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_mut_filter<'a>( + &'a mut self, + name: impl AsRef, + subsection_name: Option<&str>, + filter: &mut MetadataFilter, + ) -> Result>, lookup::existing::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find(|id| { + let s = &self.sections[id]; + filter(s.meta()) + }); + let nl = self.detect_newline_style_smallvec(); + Ok(id.and_then(move |id| self.sections.get_mut(&id).map(move |s| s.to_mut(nl)))) } /// Adds a new section. If a subsection name was provided, then @@ -39,8 +63,10 @@ impl<'event> File<'event> { /// # use git_config::File; /// # use std::convert::TryFrom; /// let mut git_config = git_config::File::default(); - /// let _section = git_config.new_section("hello", Some("world".into())); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n"); + /// let section = git_config.new_section("hello", Some("world".into()))?; + /// let nl = section.newline().to_owned(); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}")); + /// # Ok::<(), Box>(()) /// ``` /// /// Creating a new empty section and adding values to it: @@ -53,23 +79,26 @@ impl<'event> File<'event> { /// let mut git_config = git_config::File::default(); /// let mut section = git_config.new_section("hello", Some("world".into()))?; /// section.push(section::Key::try_from("a")?, "b"); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n"); + /// let nl = section.newline().to_owned(); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}")); /// let _section = git_config.new_section("core", None); - /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n\ta = b\n[core]\n"); + /// assert_eq!(git_config.to_string(), format!("[hello \"world\"]{nl}\ta = b{nl}[core]{nl}")); /// # Ok::<(), Box>(()) /// ``` pub fn new_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, + name: impl Into>, + subsection: impl Into>>, ) -> Result, section::header::Error> { - let mut section = self.push_section(section_name, subsection_name, SectionBody::default())?; + let id = self.push_section_internal(file::Section::new(name, subsection, OwnShared::clone(&self.meta))?); + let nl = self.detect_newline_style_smallvec(); + let mut section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); section.push_newline(); Ok(section) } - /// Removes the section, returning the events it had, if any. If multiple - /// sections have the same name, then the last one is returned. Note that + /// Removes the section with `name` and `subsection_name` , returning it if there was a matching section. + /// If multiple sections have the same name, then the last one is returned. Note that /// later sections with the same name have precedent over earlier ones. /// /// # Examples @@ -84,7 +113,7 @@ impl<'event> File<'event> { /// some-value = 4 /// "#)?; /// - /// let events = git_config.remove_section("hello", Some("world".into())); + /// let section = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), ""); /// # Ok::<(), Box>(()) /// ``` @@ -101,17 +130,17 @@ impl<'event> File<'event> { /// some-value = 5 /// "#)?; /// - /// let events = git_config.remove_section("hello", Some("world".into())); + /// let section = git_config.remove_section("hello", Some("world".into())); /// assert_eq!(git_config.to_string(), "[hello \"world\"]\n some-value = 4\n"); /// # Ok::<(), Box>(()) /// ``` pub fn remove_section<'a>( &mut self, - section_name: &str, + name: &str, subsection_name: impl Into>, - ) -> Option> { + ) -> Option> { let id = self - .section_ids_by_name_and_subname(section_name, subsection_name.into()) + .section_ids_by_name_and_subname(name, subsection_name.into()) .ok()? .rev() .next()?; @@ -124,32 +153,137 @@ impl<'event> File<'event> { self.sections.remove(&id) } + /// Removes the section with `name` and `subsection_name` that passed `filter`, returning the removed section + /// if at least one section matched the `filter`. + /// If multiple sections have the same name, then the last one is returned. Note that + /// later sections with the same name have precedent over earlier ones. + pub fn remove_section_filter<'a>( + &mut self, + name: &str, + subsection_name: impl Into>, + filter: &mut MetadataFilter, + ) -> Option> { + let id = self + .section_ids_by_name_and_subname(name, subsection_name.into()) + .ok()? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta()))?; + self.section_order.remove( + self.section_order + .iter() + .position(|v| *v == id) + .expect("known section id"), + ); + self.sections.remove(&id) + } + /// Adds the provided section to the config, returning a mutable reference /// to it for immediate editing. + /// Note that its meta-data will remain as is. pub fn push_section( &mut self, - section_name: impl Into>, - subsection_name: impl Into>>, - section: SectionBody<'event>, + section: file::Section<'event>, ) -> Result, section::header::Error> { - Ok(self.push_section_internal(section::Header::new(section_name, subsection_name)?, section)) + let id = self.push_section_internal(section); + let nl = self.detect_newline_style_smallvec(); + let section = self.sections.get_mut(&id).expect("each id yields a section").to_mut(nl); + Ok(section) } - /// Renames a section, modifying the last matching section. + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// to use `new_name` and `new_subsection_name`. pub fn rename_section<'a>( &mut self, - section_name: impl AsRef, + name: impl AsRef, subsection_name: impl Into>, - new_section_name: impl Into>, + new_name: impl Into>, new_subsection_name: impl Into>>, ) -> Result<(), rename_section::Error> { let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name.into())? + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? .rev() .next() .expect("list of sections were empty, which violates invariant"); - let header = self.section_headers.get_mut(&id).expect("known section-id"); - *header = section::Header::new(new_section_name, new_subsection_name)?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; + Ok(()) + } + + /// Renames the section with `name` and `subsection_name`, modifying the last matching section + /// that also passes `filter` to use `new_name` and `new_subsection_name`. + /// + /// Note that the otherwise unused [`lookup::existing::Error::KeyMissing`] variant is used to indicate + /// that the `filter` rejected all candidates, leading to no section being renamed after all. + pub fn rename_section_filter<'a>( + &mut self, + name: impl AsRef, + subsection_name: impl Into>, + new_name: impl Into>, + new_subsection_name: impl Into>>, + filter: &mut MetadataFilter, + ) -> Result<(), rename_section::Error> { + let id = self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name.into())? + .rev() + .find(|id| filter(self.sections.get(id).expect("each id has a section").meta())) + .ok_or(rename_section::Error::Lookup(lookup::existing::Error::KeyMissing))?; + let section = self.sections.get_mut(&id).expect("known section-id"); + section.header = section::Header::new(new_name, new_subsection_name)?; Ok(()) } + + /// Append another File to the end of ourselves, without losing any information. + pub fn append(&mut self, other: Self) -> &mut Self { + self.append_or_insert(other, None) + } + + /// Append another File to the end of ourselves, without losing any information. + pub(crate) fn append_or_insert(&mut self, mut other: Self, mut insert_after: Option) -> &mut Self { + let nl = self.detect_newline_style_smallvec(); + fn extend_and_assure_newline<'a>( + lhs: &mut FrontMatterEvents<'a>, + rhs: FrontMatterEvents<'a>, + nl: &impl AsRef<[u8]>, + ) { + if !ends_with_newline(lhs.as_ref(), nl, true) + && !rhs.first().map_or(true, |e| e.to_bstr_lossy().starts_with(nl.as_ref())) + { + lhs.push(Event::Newline(Cow::Owned(nl.as_ref().into()))) + } + lhs.extend(rhs); + } + let our_last_section_before_append = + insert_after.or_else(|| (self.section_id_counter != 0).then(|| SectionId(self.section_id_counter - 1))); + + for id in std::mem::take(&mut other.section_order) { + let section = other.sections.remove(&id).expect("present"); + + let new_id = match insert_after { + Some(id) => { + let new_id = self.insert_section_after(section, id); + insert_after = Some(new_id); + new_id + } + None => self.push_section_internal(section), + }; + + if let Some(post_matter) = other.frontmatter_post_section.remove(&id) { + self.frontmatter_post_section.insert(new_id, post_matter); + } + } + + if other.frontmatter_events.is_empty() { + return self; + } + + match our_last_section_before_append { + Some(last_id) => extend_and_assure_newline( + self.frontmatter_post_section.entry(last_id).or_default(), + other.frontmatter_events, + &nl, + ), + None => extend_and_assure_newline(&mut self.frontmatter_events, other.frontmatter_events, &nl), + } + self + } } diff --git a/git-config/src/file/access/raw.rs b/git-config/src/file/access/raw.rs index 983a90fcb4d..6a6d90a3838 100644 --- a/git-config/src/file/access/raw.rs +++ b/git-config/src/file/access/raw.rs @@ -1,9 +1,10 @@ use std::{borrow::Cow, collections::HashMap}; use bstr::BStr; +use smallvec::ToSmallVec; use crate::{ - file::{mutable::multi_value::EntryData, Index, MultiValueMut, SectionMut, Size, ValueMut}, + file::{mutable::multi_value::EntryData, Index, MetadataFilter, MultiValueMut, Size, ValueMut}, lookup, parse::{section, Event}, File, @@ -23,11 +24,30 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, + ) -> Result, lookup::existing::Error> { + self.raw_value_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns an uninterpreted value given a section, an optional subsection + /// and key, if it passes the `filter`. + /// + /// Consider [`Self::raw_values()`] if you want to get all values of + /// a multivar instead. + pub fn raw_value_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = key.as_ref(); for section_id in section_ids.rev() { - if let Some(v) = self.sections.get(§ion_id).expect("known section id").value(key) { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + if let Some(v) = section.value(key) { return Ok(v); } } @@ -45,6 +65,21 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, + ) -> Result, lookup::existing::Error> { + self.raw_value_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns a mutable reference to an uninterpreted value given a section, + /// an optional subsection and key, and if it passes `filter`. + /// + /// Consider [`Self::raw_values_mut`] if you want to get mutable + /// references to all values of a multivar instead. + pub fn raw_value_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let mut section_ids = self .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? @@ -55,14 +90,11 @@ impl<'event> File<'event> { let mut index = 0; let mut size = 0; let mut found_key = false; - for (i, event) in self - .sections - .get(§ion_id) - .expect("known section id") - .as_ref() - .iter() - .enumerate() - { + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { found_key = true; @@ -88,8 +120,9 @@ impl<'event> File<'event> { } drop(section_ids); + let nl = self.detect_newline_style().to_smallvec(); return Ok(ValueMut { - section: SectionMut::new(self.sections.get_mut(§ion_id).expect("known section-id")), + section: self.sections.get_mut(§ion_id).expect("known section-id").to_mut(nl), key, index: Index(index), size: Size(size), @@ -100,7 +133,10 @@ impl<'event> File<'event> { } /// Returns all uninterpreted values given a section, an optional subsection - /// and key. + /// ain order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case.nd key. /// /// # Examples /// @@ -139,12 +175,31 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&str>, key: impl AsRef, + ) -> Result>, lookup::existing::Error> { + self.raw_values_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns all uninterpreted values given a section, an optional subsection + /// and key, if the value passes `filter`, in order of occurrence. + /// + /// The ordering means that the last of the returned values is the one that would be the + /// value used in the single-value case. + pub fn raw_values_filter( + &self, + section_name: impl AsRef, + subsection_name: Option<&str>, + key: impl AsRef, + filter: &mut MetadataFilter, ) -> Result>, lookup::existing::Error> { let mut values = Vec::new(); let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = key.as_ref(); for section_id in section_ids { - values.extend(self.sections.get(§ion_id).expect("known section id").values(key)); + let section = self.sections.get(§ion_id).expect("known section id"); + if !filter(section.meta()) { + continue; + } + values.extend(section.values(key)); } if values.is_empty() { @@ -209,6 +264,18 @@ impl<'event> File<'event> { section_name: impl AsRef, subsection_name: Option<&'lookup str>, key: &'lookup str, + ) -> Result, lookup::existing::Error> { + self.raw_values_mut_filter(section_name, subsection_name, key, &mut |_| true) + } + + /// Returns mutable references to all uninterpreted values given a section, + /// an optional subsection and key, if their sections pass `filter`. + pub fn raw_values_mut_filter<'lookup>( + &mut self, + section_name: impl AsRef, + subsection_name: Option<&'lookup str>, + key: &'lookup str, + filter: &mut MetadataFilter, ) -> Result, lookup::existing::Error> { let section_ids = self.section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)?; let key = section::Key(Cow::::Borrowed(key.into())); @@ -220,14 +287,11 @@ impl<'event> File<'event> { let mut expect_value = false; let mut offset_list = Vec::new(); let mut offset_index = 0; - for (i, event) in self - .sections - .get(§ion_id) - .expect("known section-id") - .as_ref() - .iter() - .enumerate() - { + let section = self.sections.get(§ion_id).expect("known section-id"); + if !filter(section.meta()) { + continue; + } + for (i, event) in section.as_ref().iter().enumerate() { match event { Event::SectionKey(event_key) if *event_key == key => { expect_value = true; diff --git a/git-config/src/file/access/read_only.rs b/git-config/src/file/access/read_only.rs index 49b1fc059c4..4915cb3f8f1 100644 --- a/git-config/src/file/access/read_only.rs +++ b/git-config/src/file/access/read_only.rs @@ -1,8 +1,19 @@ -use std::{borrow::Cow, convert::TryFrom}; +use std::{borrow::Cow, convert::TryFrom, iter::FromIterator}; use bstr::BStr; +use git_features::threading::OwnShared; +use smallvec::SmallVec; -use crate::{file::SectionBody, lookup, parse::section, File}; +use crate::{ + file, + file::{ + write::{extract_newline, platform_newline}, + Metadata, MetadataFilter, + }, + lookup, + parse::Event, + File, +}; /// Read-only low-level access methods, as it requires generics for converting into /// custom values defined in this crate like [`Integer`][crate::Integer] and @@ -117,24 +128,40 @@ impl<'event> File<'event> { .map_err(lookup::Error::FailedConversion) } - /// Returns the last found immutable section with a given name and optional subsection name. + /// Returns the last found immutable section with a given `name` and optional `subsection_name`. pub fn section( - &mut self, - section_name: impl AsRef, + &self, + name: impl AsRef, subsection_name: Option<&str>, - ) -> Result<&SectionBody<'event>, lookup::existing::Error> { - let id = self - .section_ids_by_name_and_subname(section_name.as_ref(), subsection_name)? - .rev() - .next() - .expect("BUG: Section lookup vec was empty"); + ) -> Result<&file::Section<'event>, lookup::existing::Error> { Ok(self - .sections - .get(&id) - .expect("BUG: Section did not have id from lookup")) + .section_filter(name, subsection_name, &mut |_| true)? + .expect("section present as we take all")) } - /// Gets all sections that match the provided name, ignoring any subsections. + /// Returns the last found immutable section with a given `name` and optional `subsection_name`, that matches `filter`. + /// + /// If there are sections matching `section_name` and `subsection_name` but the `filter` rejects all of them, `Ok(None)` + /// is returned. + pub fn section_filter<'a>( + &'a self, + name: impl AsRef, + subsection_name: Option<&str>, + filter: &mut MetadataFilter, + ) -> Result>, lookup::existing::Error> { + Ok(self + .section_ids_by_name_and_subname(name.as_ref(), subsection_name)? + .rev() + .find_map({ + let sections = &self.sections; + move |id| { + let s = §ions[&id]; + filter(s.meta()).then(|| s) + } + })) + } + + /// Gets all sections that match the provided `name`, ignoring any subsections. /// /// # Examples /// @@ -169,11 +196,8 @@ impl<'event> File<'event> { /// # Ok::<(), Box>(()) /// ``` #[must_use] - pub fn sections_by_name<'a>( - &'a self, - section_name: &'a str, - ) -> Option> + '_> { - self.section_ids_by_name(section_name).ok().map(move |ids| { + pub fn sections_by_name<'a>(&'a self, name: &'a str) -> Option> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { ids.map(move |id| { self.sections .get(&id) @@ -182,66 +206,20 @@ impl<'event> File<'event> { }) } - /// Get all sections that match the `section_name`, returning all matching section header along with their body. - /// - /// `None` is returned if there is no section with `section_name`. - /// - /// # Example - /// - /// Provided the following config: - /// ```plain - /// [url "ssh://git@github.com/"] - /// insteadOf = https://github.com/ - /// [url "ssh://git@bitbucket.org"] - /// insteadOf = https://bitbucket.org/ - /// ``` - /// Calling this method will yield all section bodies and their header: - /// - /// ```rust - /// use git_config::File; - /// use git_config::parse::section; - /// use std::borrow::Cow; - /// use std::convert::TryFrom; - /// use nom::AsBytes; - /// - /// let input = r#" - /// [url "ssh://git@github.com/"] - /// insteadOf = https://github.com/ - /// [url "ssh://git@bitbucket.org"] - /// insteadOf = https://bitbucket.org/ - /// "#; - /// let config = git_config::File::try_from(input)?; - /// let url = config.sections_by_name_with_header("url"); - /// assert_eq!(url.map_or(0, |s| s.count()), 2); - /// - /// for (i, (header, body)) in config.sections_by_name_with_header("url").unwrap().enumerate() { - /// let url = header.subsection_name().unwrap(); - /// let instead_of = body.value("insteadOf").unwrap(); - /// - /// if i == 0 { - /// assert_eq!(instead_of.as_ref(), "https://github.com/"); - /// assert_eq!(url, "ssh://git@github.com/"); - /// } else { - /// assert_eq!(instead_of.as_ref(), "https://bitbucket.org/"); - /// assert_eq!(url, "ssh://git@bitbucket.org"); - /// } - /// } - /// # Ok::<(), Box>(()) - /// ``` - pub fn sections_by_name_with_header<'a>( + /// Gets all sections that match the provided `name`, ignoring any subsections, and pass the `filter`. + #[must_use] + pub fn sections_by_name_and_filter<'a>( &'a self, - section_name: &'a str, - ) -> Option, &SectionBody<'event>)> + '_> { - self.section_ids_by_name(section_name).ok().map(move |ids| { - ids.map(move |id| { - ( - self.section_headers - .get(&id) - .expect("section doesn't have a section header??"), - self.sections - .get(&id) - .expect("section doesn't have id from from lookup"), - ) + name: &'a str, + filter: &'a mut MetadataFilter, + ) -> Option> + '_> { + self.section_ids_by_name(name).ok().map(move |ids| { + ids.filter_map(move |id| { + let s = self + .sections + .get(&id) + .expect("section doesn't have id from from lookup"); + filter(s.meta()).then(|| s) }) }) } @@ -256,9 +234,68 @@ impl<'event> File<'event> { } /// Returns if there are no entries in the config. This will return true - /// if there are only empty sections or comments. + /// if there are only empty sections, with whitespace and comments not being considered + /// void. #[must_use] - pub fn is_empty(&self) -> bool { - self.sections.values().all(SectionBody::is_empty) + pub fn is_void(&self) -> bool { + self.sections.values().all(|s| s.body.is_void()) + } + + /// Return the file's metadata to guide filtering of all values upon retrieval. + /// + /// This is the metadata the file was instantiated with for use in all newly created sections. + pub fn meta(&self) -> &Metadata { + &*self.meta + } + + /// Similar to [`meta()`][File::meta()], but with shared ownership. + pub fn meta_owned(&self) -> OwnShared { + OwnShared::clone(&self.meta) + } + + /// Return an iterator over all sections, in order of occurrence in the file itself. + pub fn sections(&self) -> impl Iterator> + '_ { + self.section_order.iter().map(move |id| &self.sections[id]) + } + + /// Return an iterator over all sections along with non-section events that are placed right after them, + /// in order of occurrence in the file itself. + /// + /// This allows to reproduce the look of sections perfectly when serializing them with + /// [`write_to()`][file::Section::write_to()]. + pub fn sections_and_postmatter(&self) -> impl Iterator, Vec<&Event<'event>>)> { + self.section_order.iter().map(move |id| { + let s = &self.sections[id]; + let pm: Vec<_> = self + .frontmatter_post_section + .get(id) + .map(|events| events.iter().collect()) + .unwrap_or_default(); + (s, pm) + }) + } + + /// Return all events which are in front of the first of our sections, or `None` if there are none. + pub fn frontmatter(&self) -> Option>> { + (!self.frontmatter_events.is_empty()).then(|| self.frontmatter_events.iter()) + } + + /// Return the newline characters that have been detected in this config file or the default ones + /// for the current platform. + /// + /// Note that the first found newline is the one we use in the assumption of consistency. + pub fn detect_newline_style(&self) -> &BStr { + self.frontmatter_events + .iter() + .find_map(extract_newline) + .or_else(|| { + self.sections() + .find_map(|s| s.body.as_ref().iter().find_map(extract_newline)) + }) + .unwrap_or_else(|| platform_newline()) + } + + pub(crate) fn detect_newline_style_smallvec(&self) -> SmallVec<[u8; 2]> { + SmallVec::from_iter(self.detect_newline_style().iter().copied()) } } diff --git a/git-config/src/file/access/write.rs b/git-config/src/file/access/write.rs deleted file mode 100644 index c986f220bd5..00000000000 --- a/git-config/src/file/access/write.rs +++ /dev/null @@ -1,36 +0,0 @@ -use bstr::BString; - -use crate::File; - -impl File<'_> { - /// Serialize this type into a `BString` for convenience. - /// - /// Note that `to_string()` can also be used, but might not be lossless. - #[must_use] - pub fn to_bstring(&self) -> BString { - let mut buf = Vec::new(); - self.write_to(&mut buf).expect("io error impossible"); - buf.into() - } - - /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly - /// as it was parsed. - pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { - for event in self.frontmatter_events.as_ref() { - event.write_to(&mut out)?; - } - - for section_id in &self.section_order { - self.section_headers - .get(section_id) - .expect("known section-id") - .write_to(&mut out)?; - - for event in self.sections.get(section_id).expect("known section-id").as_ref() { - event.write_to(&mut out)?; - } - } - - Ok(()) - } -} diff --git a/git-config/src/file/impls.rs b/git-config/src/file/impls.rs index bf756d948f6..c26df5fb81a 100644 --- a/git-config/src/file/impls.rs +++ b/git-config/src/file/impls.rs @@ -1,14 +1,21 @@ -use std::{convert::TryFrom, fmt::Display, str::FromStr}; +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; -use bstr::{BStr, BString}; +use bstr::{BStr, BString, ByteVec}; -use crate::{file::SectionBody, parse, File}; +use crate::{ + file::Metadata, + parse, + parse::{section, Event}, + value::normalize, + File, +}; impl FromStr for File<'static> { type Err = parse::Error; fn from_str(s: &str) -> Result { - parse::Events::from_bytes_owned(s.as_bytes(), None).map(File::from) + parse::Events::from_bytes_owned(s.as_bytes(), None) + .map(|events| File::from_parse_events_no_includes(events, Metadata::api())) } } @@ -18,7 +25,7 @@ impl<'a> TryFrom<&'a str> for File<'a> { /// Convenience constructor. Attempts to parse the provided string into a /// [`File`]. See [`Events::from_str()`][crate::parse::Events::from_str()] for more information. fn try_from(s: &'a str) -> Result, Self::Error> { - parse::Events::from_str(s).map(Self::from) + parse::Events::from_str(s).map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) } } @@ -28,22 +35,8 @@ impl<'a> TryFrom<&'a BStr> for File<'a> { /// Convenience constructor. Attempts to parse the provided byte string into /// a [`File`]. See [`Events::from_bytes()`][parse::Events::from_bytes()] for more information. fn try_from(value: &'a BStr) -> Result, Self::Error> { - parse::Events::from_bytes(value).map(File::from) - } -} - -impl<'a> From> for File<'a> { - fn from(events: parse::Events<'a>) -> Self { - let mut this = File { - frontmatter_events: events.frontmatter, - ..Default::default() - }; - - for section in events.sections { - this.push_section_internal(section.section_header, SectionBody(section.events)); - } - - this + parse::Events::from_bytes(value, None) + .map(|events| Self::from_parse_events_no_includes(events, Metadata::api())) } } @@ -58,3 +51,61 @@ impl Display for File<'_> { Display::fmt(&self.to_bstring(), f) } } + +impl PartialEq for File<'_> { + fn eq(&self, other: &Self) -> bool { + fn find_key<'a>(mut it: impl Iterator>) -> Option<&'a section::Key<'a>> { + it.find_map(|e| match e { + Event::SectionKey(k) => Some(k), + _ => None, + }) + } + fn collect_value<'a>(it: impl Iterator>) -> Cow<'a, BStr> { + let mut partial_value = BString::default(); + let mut value = None; + + for event in it { + match event { + Event::SectionKey(_) => break, + Event::Value(v) => { + value = v.clone().into(); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + value.map(normalize).unwrap_or_default() + } + if self.section_order.len() != other.section_order.len() { + return false; + } + + for (lhs, rhs) in self + .section_order + .iter() + .zip(&other.section_order) + .map(|(lhs, rhs)| (&self.sections[lhs], &other.sections[rhs])) + { + if !(lhs.header.name == rhs.header.name && lhs.header.subsection_name == rhs.header.subsection_name) { + return false; + } + + let (mut lhs, mut rhs) = (lhs.body.0.iter(), rhs.body.0.iter()); + while let (Some(lhs_key), Some(rhs_key)) = (find_key(&mut lhs), find_key(&mut rhs)) { + if lhs_key != rhs_key { + return false; + } + if collect_value(&mut lhs) != collect_value(&mut rhs) { + return false; + } + } + } + true + } +} diff --git a/git-config/src/file/includes/mod.rs b/git-config/src/file/includes/mod.rs new file mode 100644 index 00000000000..e95327f02ba --- /dev/null +++ b/git-config/src/file/includes/mod.rs @@ -0,0 +1,319 @@ +use std::{ + borrow::Cow, + path::{Path, PathBuf}, +}; + +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use git_features::threading::OwnShared; +use git_ref::Category; + +use crate::{ + file, + file::{includes, init, Metadata, SectionId}, + path, File, +}; + +impl File<'static> { + /// Traverse all `include` and `includeIf` directives found in this instance and follow them, loading the + /// referenced files from their location and adding their content right past the value that included them. + /// + /// # Limitations + /// + /// - Note that this method is _not idempotent_ and calling it multiple times will resolve includes multiple + /// times. It's recommended use is as part of a multi-step bootstrapping which needs fine-grained control, + /// and unless that's given one should prefer one of the other ways of initialization that resolve includes + /// at the right time. + /// - included values are added after the _section_ that included them, not directly after the value. This is + /// a deviation from how git does it, as it technically adds new value right after the include path itself, + /// technically 'splitting' the section. This can only make a difference if the `include` section also has values + /// which later overwrite portions of the included file, which seems unusual as these would be related to `includes`. + /// We can fix this by 'splitting' the include section if needed so the included sections are put into the right place. + pub fn resolve_includes(&mut self, options: init::Options<'_>) -> Result<(), Error> { + if options.includes.max_depth == 0 { + return Ok(()); + } + let mut buf = Vec::new(); + resolve(self, &mut buf, options) + } +} + +pub(crate) fn resolve(config: &mut File<'static>, buf: &mut Vec, options: init::Options<'_>) -> Result<(), Error> { + resolve_includes_recursive(config, 0, buf, options) +} + +fn resolve_includes_recursive( + target_config: &mut File<'static>, + depth: u8, + buf: &mut Vec, + options: init::Options<'_>, +) -> Result<(), Error> { + if depth == options.includes.max_depth { + return if options.includes.err_on_max_depth_exceeded { + Err(Error::IncludeDepthExceeded { + max_depth: options.includes.max_depth, + }) + } else { + Ok(()) + }; + } + + let mut section_ids_and_include_paths = Vec::new(); + for (id, section) in target_config + .section_order + .iter() + .map(|id| (*id, &target_config.sections[id])) + { + let header = §ion.header; + let header_name = header.name.as_ref(); + if header_name == "include" && header.subsection_name.is_none() { + detach_include_paths(&mut section_ids_and_include_paths, section, id) + } else if header_name == "includeIf" { + if let Some(condition) = &header.subsection_name { + let target_config_path = section.meta.path.as_deref(); + if include_condition_match(condition.as_ref(), target_config_path, options.includes)? { + detach_include_paths(&mut section_ids_and_include_paths, section, id) + } + } + } + } + + append_followed_includes_recursively(section_ids_and_include_paths, target_config, depth, options, buf) +} + +fn append_followed_includes_recursively( + section_ids_and_include_paths: Vec<(SectionId, crate::Path<'_>)>, + target_config: &mut File<'static>, + depth: u8, + options: init::Options<'_>, + buf: &mut Vec, +) -> Result<(), Error> { + for (section_id, config_path) in section_ids_and_include_paths { + let meta = OwnShared::clone(&target_config.sections[§ion_id].meta); + let target_config_path = meta.path.as_deref(); + let config_path = match resolve_path(config_path, target_config_path, options.includes)? { + Some(p) => p, + None => continue, + }; + if !config_path.is_file() { + continue; + } + + buf.clear(); + std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; + let config_meta = Metadata { + path: Some(config_path), + trust: meta.trust, + level: meta.level + 1, + source: meta.source, + }; + let no_follow_options = init::Options { + includes: includes::Options::no_follow(), + ..options + }; + + let mut include_config = + File::from_bytes_owned(buf, config_meta, no_follow_options).map_err(|err| match err { + init::Error::Parse(err) => Error::Parse(err), + init::Error::Interpolate(err) => Error::Interpolate(err), + init::Error::Includes(_) => unreachable!("BUG: {:?} not possible due to no-follow options", err), + })?; + resolve_includes_recursive(&mut include_config, depth + 1, buf, options)?; + + target_config.append_or_insert(include_config, Some(section_id)); + } + Ok(()) +} + +fn detach_include_paths( + include_paths: &mut Vec<(SectionId, crate::Path<'static>)>, + section: &file::Section<'_>, + id: SectionId, +) { + include_paths.extend( + section + .body + .values("path") + .into_iter() + .map(|path| (id, crate::Path::from(Cow::Owned(path.into_owned())))), + ) +} + +fn include_condition_match( + condition: &BStr, + target_config_path: Option<&Path>, + options: Options<'_>, +) -> Result { + let mut tokens = condition.splitn(2, |b| *b == b':'); + let (prefix, condition) = match (tokens.next(), tokens.next()) { + (Some(a), Some(b)) => (a, b), + _ => return Ok(false), + }; + let condition = condition.as_bstr(); + match prefix { + b"gitdir" => gitdir_matches( + condition, + target_config_path, + options, + git_glob::wildmatch::Mode::empty(), + ), + b"gitdir/i" => gitdir_matches( + condition, + target_config_path, + options, + git_glob::wildmatch::Mode::IGNORE_CASE, + ), + b"onbranch" => Ok(onbranch_matches(condition, options.conditional).is_some()), + _ => Ok(false), + } +} + +fn onbranch_matches( + condition: &BStr, + conditional::Context { branch_name, .. }: conditional::Context<'_>, +) -> Option<()> { + let branch_name = branch_name?; + let (_, branch_name) = branch_name + .category_and_short_name() + .filter(|(cat, _)| *cat == Category::LocalBranch)?; + + let condition = if condition.ends_with(b"/") { + let mut condition: BString = condition.into(); + condition.push_str("**"); + Cow::Owned(condition) + } else { + condition.into() + }; + + git_glob::wildmatch( + condition.as_ref(), + branch_name, + git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, + ) + .then(|| ()) +} + +fn gitdir_matches( + condition_path: &BStr, + target_config_path: Option<&Path>, + Options { + conditional: conditional::Context { git_dir, .. }, + interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, + .. + }: Options<'_>, + wildmatch_mode: git_glob::wildmatch::Mode, +) -> Result { + if !err_on_interpolation_failure && git_dir.is_none() { + return Ok(false); + } + let git_dir = git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(Error::MissingGitDir)?)); + + let mut pattern_path: Cow<'_, _> = { + let path = match check_interpolation_result( + err_on_interpolation_failure, + crate::Path::from(Cow::Borrowed(condition_path)).interpolate(context), + )? { + Some(p) => p, + None => return Ok(false), + }; + git_path::into_bstr(path).into_owned().into() + }; + // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows + if pattern_path != condition_path { + pattern_path = git_path::to_unix_separators_on_windows(pattern_path); + } + + if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(false); + } + let parent_dir = target_config_path + .ok_or(Error::MissingConfigPath)? + .parent() + .expect("config path can never be /"); + let mut joined_path = git_path::to_unix_separators_on_windows(git_path::into_bstr(parent_dir)).into_owned(); + joined_path.push(b'/'); + joined_path.extend_from_slice(relative_pattern_path); + pattern_path = joined_path.into(); + } + + // NOTE: this special handling of leading backslash is needed to do it like git does + if pattern_path.iter().next() != Some(&(std::path::MAIN_SEPARATOR as u8)) + && !git_path::from_bstr(pattern_path.clone()).is_absolute() + { + let mut prefixed = pattern_path.into_owned(); + prefixed.insert_str(0, "**/"); + pattern_path = prefixed.into() + } + if pattern_path.ends_with(b"/") { + let mut suffixed = pattern_path.into_owned(); + suffixed.push_str("**"); + pattern_path = suffixed.into(); + } + + let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; + let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); + if is_match { + return Ok(true); + } + + let expanded_git_dir = git_path::into_bstr(git_path::realpath(git_path::from_byte_slice(&git_dir))?); + Ok(git_glob::wildmatch( + pattern_path.as_bstr(), + expanded_git_dir.as_bstr(), + match_mode, + )) +} + +fn check_interpolation_result( + disable: bool, + res: Result, path::interpolate::Error>, +) -> Result>, path::interpolate::Error> { + if disable { + return res.map(Some); + } + match res { + Ok(good) => Ok(good.into()), + Err(err) => match err { + path::interpolate::Error::Missing { .. } | path::interpolate::Error::UserInterpolationUnsupported => { + Ok(None) + } + path::interpolate::Error::UsernameConversion(_) | path::interpolate::Error::Utf8Conversion { .. } => { + Err(err) + } + }, + } +} + +fn resolve_path( + path: crate::Path<'_>, + target_config_path: Option<&Path>, + includes::Options { + interpolate: context, + err_on_interpolation_failure, + err_on_missing_config_path, + .. + }: includes::Options<'_>, +) -> Result, Error> { + let path = match check_interpolation_result(err_on_interpolation_failure, path.interpolate(context))? { + Some(p) => p, + None => return Ok(None), + }; + let path: PathBuf = if path.is_relative() { + if !err_on_missing_config_path && target_config_path.is_none() { + return Ok(None); + } + target_config_path + .ok_or(Error::MissingConfigPath)? + .parent() + .expect("path is a config file which naturally lives in a directory") + .join(path) + } else { + path.into() + }; + Ok(Some(path)) +} + +mod types; +pub use types::{conditional, Error, Options}; diff --git a/git-config/src/file/includes/types.rs b/git-config/src/file/includes/types.rs new file mode 100644 index 00000000000..9f342255093 --- /dev/null +++ b/git-config/src/file/includes/types.rs @@ -0,0 +1,131 @@ +use crate::{parse, path::interpolate}; + +/// The error returned when following includes. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] + IncludeDepthExceeded { max_depth: u8 }, + #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] + MissingConfigPath, + #[error("The git directory must be provided to support `gitdir:` conditional includes")] + MissingGitDir, + #[error(transparent)] + Realpath(#[from] git_path::realpath::Error), +} + +/// Options to handle includes, like `include.path` or `includeIf..path`, +#[derive(Clone, Copy)] +pub struct Options<'a> { + /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. + pub max_depth: u8, + /// When max depth is exceeded while following nested includes, + /// return an error if true or silently stop following resolve_includes. + /// + /// Setting this value to false allows to read configuration with cycles, + /// which otherwise always results in an error. + pub err_on_max_depth_exceeded: bool, + /// If true, default false, failing to interpolate paths will result in an error. + /// + /// Interpolation also happens if paths in conditional includes can't be interpolated. + pub err_on_interpolation_failure: bool, + /// If true, default true, configuration not originating from a path will cause errors when trying to resolve + /// relative include paths (which would require the including configuration's path). + pub err_on_missing_config_path: bool, + /// Used during path interpolation, both for include paths before trying to read the file, and for + /// paths used in conditional `gitdir` includes. + pub interpolate: interpolate::Context<'a>, + + /// Additional context for conditional includes to work. + pub conditional: conditional::Context<'a>, +} + +impl<'a> Options<'a> { + /// Provide options to never follow include directives at all. + pub fn no_follow() -> Self { + Options { + max_depth: 0, + err_on_max_depth_exceeded: false, + err_on_interpolation_failure: false, + err_on_missing_config_path: false, + interpolate: Default::default(), + conditional: Default::default(), + } + } + /// Provide options to follow includes like git does, provided the required `conditional` and `interpolate` contexts + /// to support `gitdir` and `onbranch` based `includeIf` directives as well as standard `include.path` resolution. + /// Note that the follow-mode is `git`-style, following at most 10 indirections while + /// producing an error if the depth is exceeded. + pub fn follow(interpolate: interpolate::Context<'a>, conditional: conditional::Context<'a>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate, + conditional, + } + } + + /// For use with `follow` type options, cause failure if an include path couldn't be interpolated or the depth limit is exceeded. + pub fn strict(mut self) -> Self { + self.err_on_interpolation_failure = true; + self.err_on_max_depth_exceeded = true; + self.err_on_missing_config_path = true; + self + } + + /// Like [`follow`][Options::follow()], but without information to resolve `includeIf` directories as well as default + /// configuration to allow resolving `~username/` path. `home_dir` is required to resolve `~/` paths if set. + /// Note that `%(prefix)` paths cannot be interpolated with this configuration, use [`follow()`][Options::follow()] + /// instead for complete control. + pub fn follow_without_conditional(home_dir: Option<&'a std::path::Path>) -> Self { + Options { + max_depth: 10, + err_on_max_depth_exceeded: true, + err_on_interpolation_failure: false, + err_on_missing_config_path: true, + interpolate: interpolate::Context { + git_install_dir: None, + home_dir, + home_for_user: Some(interpolate::home_for_user), + }, + conditional: Default::default(), + } + } + + /// Set the context used for interpolation when interpolating paths to include as well as the paths + /// in `gitdir` conditional includes. + pub fn interpolate_with(mut self, context: interpolate::Context<'a>) -> Self { + self.interpolate = context; + self + } +} + +impl Default for Options<'_> { + fn default() -> Self { + Self::no_follow() + } +} + +/// +pub mod conditional { + /// Options to handle conditional includes like `includeIf..path`. + #[derive(Clone, Copy, Default)] + pub struct Context<'a> { + /// The location of the .git directory. If `None`, `gitdir` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.gitdir:…` or `includeIf:gitdir/i…`. + pub git_dir: Option<&'a std::path::Path>, + /// The name of the branch that is currently checked out. If `None`, `onbranch` conditions cause an error. + /// + /// Used for conditional includes, e.g. `includeIf.onbranch:main.…` + pub branch_name: Option<&'a git_ref::FullNameRef>, + } +} diff --git a/git-config/src/file/init/comfort.rs b/git-config/src/file/init/comfort.rs new file mode 100644 index 00000000000..d9352fb615f --- /dev/null +++ b/git-config/src/file/init/comfort.rs @@ -0,0 +1,138 @@ +use std::path::PathBuf; + +use crate::{ + file::{init, Metadata}, + path, source, File, Source, +}; + +/// Easy-instantiation of typical non-repository git configuration files with all configuration defaulting to typical values. +/// +/// ### Limitations +/// +/// Note that `includeIf` conditions in global files will cause failure as the required information +/// to resolve them isn't present without a repository. +/// +/// Also note that relevant information to interpolate paths will be obtained from the environment or other +/// source on unix. +impl File<'static> { + /// Open all global configuration files which involves the following sources: + /// + /// * [system][crate::Source::System] + /// * [git][crate::Source::Git] + /// * [user][crate::Source::User] + /// + /// which excludes repository local configuration, as well as override-configuration from environment variables. + /// + /// Note that the file might [be empty][File::is_void()] in case no configuration file was found. + pub fn from_globals() -> Result, init::from_paths::Error> { + let metas = [source::Kind::System, source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + let path = source + .storage_location(&mut |name| std::env::var_os(name)) + .and_then(|p| p.is_file().then(|| p)) + .map(|p| p.into_owned()); + + Metadata { + path, + source: *source, + level: 0, + trust: git_sec::Trust::Full, + } + .into() + }); + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + File::from_paths_metadata(metas, options).map(Option::unwrap_or_default) + } + + /// Generates a config from `GIT_CONFIG_*` environment variables and return a possibly empty `File`. + /// A typical use of this is to [`append`][File::append()] this configuration to another one with lower + /// precedence to obtain overrides. + /// + /// See [`git-config`'s documentation] for more information on the environment variables in question. + /// + /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT + pub fn from_environment_overrides() -> Result, init::from_env::Error> { + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow_without_conditional(home.as_deref()), + ..Default::default() + }; + + File::from_env(options).map(Option::unwrap_or_default) + } +} + +/// An easy way to provide complete configuration for a repository. +impl File<'static> { + /// This configuration type includes the following sources, in order of precedence: + /// + /// - globals + /// - repository-local by loading `dir`/config + /// - environment + /// + /// Note that `dir` is the `.git` dir to load the configuration from, not the configuration file. + /// + /// Includes will be resolved within limits as some information like the git installation directory is missing to interpolate + /// paths with as well as git repository information like the branch name. + pub fn from_git_dir(dir: impl Into) -> Result, from_git_dir::Error> { + let (mut local, git_dir) = { + let source = Source::Local; + let mut path = dir.into(); + path.push( + source + .storage_location(&mut |n| std::env::var_os(n)) + .expect("location available for local"), + ); + let local = Self::from_path_no_includes(&path, source)?; + path.pop(); + (local, path) + }; + + let home = std::env::var("HOME").ok().map(PathBuf::from); + let options = init::Options { + includes: init::includes::Options::follow( + path::interpolate::Context { + home_dir: home.as_deref(), + ..Default::default() + }, + init::includes::conditional::Context { + git_dir: Some(git_dir.as_ref()), + branch_name: None, + }, + ), + lossy: false, + }; + + let mut globals = Self::from_globals()?; + globals.resolve_includes(options)?; + local.resolve_includes(options)?; + + globals.append(local).append(Self::from_environment_overrides()?); + Ok(globals) + } +} + +/// +pub mod from_git_dir { + use crate::file::init; + + /// The error returned by [`File::from_git_dir()`][crate::File::from_git_dir()]. + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + FromPaths(#[from] init::from_paths::Error), + #[error(transparent)] + FromEnv(#[from] init::from_env::Error), + #[error(transparent)] + Init(#[from] init::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), + } +} diff --git a/git-config/src/file/init/from_env.rs b/git-config/src/file/init/from_env.rs index ab82b6389c3..b721758b55a 100644 --- a/git-config/src/file/init/from_env.rs +++ b/git-config/src/file/init/from_env.rs @@ -1,15 +1,8 @@ -use std::{borrow::Cow, path::PathBuf}; +use std::{borrow::Cow, convert::TryFrom}; -use bstr::BString; +use crate::{file, file::init, parse, parse::section, path::interpolate, File}; -use crate::{ - file::{from_paths, init::resolve_includes}, - parse::section, - path::interpolate, - File, -}; - -/// Represents the errors that may occur when calling [`File::from_env`][crate::File::from_env()]. +/// Represents the errors that may occur when calling [`File::from_env()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { @@ -24,64 +17,22 @@ pub enum Error { #[error(transparent)] PathInterpolationError(#[from] interpolate::Error), #[error(transparent)] - FromPathsError(#[from] from_paths::Error), + Includes(#[from] init::includes::Error), #[error(transparent)] Section(#[from] section::header::Error), + #[error(transparent)] + Key(#[from] section::key::Error), } /// Instantiation from environment variables impl File<'static> { - /// Constructs a `git-config` from the default cascading sequence of global configuration files, - /// excluding any repository-local configuration. + /// Generates a config from `GIT_CONFIG_*` environment variables or returns `Ok(None)` if no configuration was found. + /// See [`git-config`'s documentation] for more information on the environment variables in question. /// - /// See for details. - // TODO: how does this relate to the `fs` module? Have a feeling options should contain instructions on which files to use. - pub fn from_env_paths(options: from_paths::Options<'_>) -> Result, from_paths::Error> { - use std::env; - - let mut paths = vec![]; - - if env::var("GIT_CONFIG_NO_SYSTEM").is_err() { - let git_config_system_path = env::var_os("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into()); - paths.push(PathBuf::from(git_config_system_path)); - } - - if let Some(git_config_global) = env::var_os("GIT_CONFIG_GLOBAL") { - paths.push(PathBuf::from(git_config_global)); - } else { - // Divergence from git-config(1) - // These two are supposed to share the same scope and override - // rather than append according to git-config(1) documentation. - if let Some(xdg_config_home) = env::var_os("XDG_CONFIG_HOME") { - paths.push(PathBuf::from(xdg_config_home).join("git/config")); - } else if let Some(home) = env::var_os("HOME") { - paths.push(PathBuf::from(home).join(".config/git/config")); - } - - if let Some(home) = env::var_os("HOME") { - paths.push(PathBuf::from(home).join(".gitconfig")); - } - } - - if let Some(git_dir) = env::var_os("GIT_DIR") { - paths.push(PathBuf::from(git_dir).join("config")); - } - - // To support more platforms/configurations: - // Drop any possible config locations which aren't present to avoid - // `parser::parse_from_path` failing too early with "not found" before - // it reaches a path which _does_ exist. - let paths = paths.into_iter().filter(|p| p.exists()); - - File::from_paths(paths, options) - } - - /// Generates a config from the environment variables. This is neither - /// zero-copy nor zero-alloc. See [`git-config`'s documentation] on - /// environment variable for more information. + /// With `options` configured, it's possible to resolve `include.path` or `includeIf..path` directives as well. /// /// [`git-config`'s documentation]: https://git-scm.com/docs/git-config#Documentation/git-config.txt-GITCONFIGCOUNT - pub fn from_env(options: from_paths::Options<'_>) -> Result>, Error> { + pub fn from_env(options: init::Options<'_>) -> Result>, Error> { use std::env; let count: usize = match env::var("GIT_CONFIG_COUNT") { Ok(v) => v.parse().map_err(|_| Error::InvalidConfigCount { input: v })?, @@ -92,41 +43,37 @@ impl File<'static> { return Ok(None); } - let mut config = File::default(); + let meta = file::Metadata { + path: None, + source: crate::Source::Env, + level: 0, + trust: git_sec::Trust::Full, + }; + let mut config = File::new(meta); for i in 0..count { let key = env::var(format!("GIT_CONFIG_KEY_{}", i)).map_err(|_| Error::InvalidKeyId { key_id: i })?; let value = env::var_os(format!("GIT_CONFIG_VALUE_{}", i)).ok_or(Error::InvalidValueId { value_id: i })?; - match key.split_once('.') { - Some((section_name, maybe_subsection)) => { - let (subsection, key) = match maybe_subsection.rsplit_once('.') { - Some((subsection, key)) => (Some(subsection), key), - None => (None, maybe_subsection), - }; - - let mut section = match config.section_mut(section_name, subsection) { - Ok(section) => section, - Err(_) => config.new_section( - section_name.to_string(), - subsection.map(|subsection| Cow::Owned(subsection.to_string())), - )?, - }; - - section.push( - section::Key(BString::from(key).into()), - git_path::into_bstr(PathBuf::from(value)).as_ref(), - ); - } - None => { - return Err(Error::InvalidKeyValue { - key_id: i, - key_val: key.to_string(), - }) - } - } + let key = parse::key(&key).ok_or_else(|| Error::InvalidKeyValue { + key_id: i, + key_val: key.to_string(), + })?; + + let mut section = match config.section_mut(key.section_name, key.subsection_name) { + Ok(section) => section, + Err(_) => config.new_section( + key.section_name.to_owned(), + key.subsection_name.map(|subsection| Cow::Owned(subsection.to_owned())), + )?, + }; + + section.push( + section::Key::try_from(key.value_name.to_owned())?, + git_path::os_str_into_bstr(&value).expect("no illformed UTF-8").as_ref(), + ); } let mut buf = Vec::new(); - resolve_includes(&mut config, None, &mut buf, options)?; + init::includes::resolve(&mut config, &mut buf, options)?; Ok(Some(config)) } } diff --git a/git-config/src/file/init/from_paths.rs b/git-config/src/file/init/from_paths.rs index 2a3786351c6..2b4c840989d 100644 --- a/git-config/src/file/init/from_paths.rs +++ b/git-config/src/file/init/from_paths.rs @@ -1,86 +1,94 @@ -use crate::{file::init::resolve_includes, parse, path::interpolate, File}; +use std::collections::BTreeSet; -/// The error returned by [`File::from_paths()`][crate::File::from_paths()] and [`File::from_env_paths()`][crate::File::from_env_paths()]. +use crate::{ + file::{init, init::Options, Metadata}, + File, +}; + +/// The error returned by [`File::from_paths_metadata()`] and [`File::from_path_no_includes()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] - Parse(#[from] parse::Error), - #[error(transparent)] - Interpolate(#[from] interpolate::Error), - #[error("The maximum allowed length {} of the file include chain built by following nested resolve_includes is exceeded", .max_depth)] - IncludeDepthExceeded { max_depth: u8 }, - #[error("Include paths from environment variables must not be relative as no config file paths exists as root")] - MissingConfigPath, - #[error("The git directory must be provided to support `gitdir:` conditional includes")] - MissingGitDir, - #[error(transparent)] - Realpath(#[from] git_path::realpath::Error), + Init(#[from] init::Error), } -/// Options when loading git config using [`File::from_paths()`][crate::File::from_paths()]. -#[derive(Clone, Copy)] -pub struct Options<'a> { - /// Used during path interpolation. - pub interpolate: interpolate::Options<'a>, - /// The maximum allowed length of the file include chain built by following nested resolve_includes where base level is depth = 0. - pub max_depth: u8, - /// When max depth is exceeded while following nested included, return an error if true or silently stop following - /// resolve_includes. - /// - /// Setting this value to false allows to read configuration with cycles, which otherwise always results in an error. - pub error_on_max_depth_exceeded: bool, - /// The location of the .git directory - /// - /// Used for conditional includes, e.g. `gitdir:` or `gitdir/i`. - pub git_dir: Option<&'a std::path::Path>, - /// The name of the branch that is currently checked out +/// Instantiation from one or more paths +impl File<'static> { + /// Load the single file at `path` with `source` without following include directives. /// - /// Used for conditional includes, e.g. `onbranch:` - pub branch_name: Option<&'a git_ref::FullNameRef>, -} + /// Note that the path will be checked for ownership to derive trust. + pub fn from_path_no_includes(path: impl Into, source: crate::Source) -> Result { + let path = path.into(); + let trust = git_sec::Trust::from_path_ownership(&path)?; -impl Default for Options<'_> { - fn default() -> Self { - Options { - interpolate: Default::default(), - max_depth: 10, - error_on_max_depth_exceeded: true, - git_dir: None, - branch_name: None, - } - } -} + let mut buf = Vec::new(); + std::io::copy(&mut std::fs::File::open(&path)?, &mut buf)?; -/// Instantiation from one or more paths -impl File<'static> { - /// Open a single configuration file by reading all data at `path` into `buf` and - /// copying all contents from there, without resolving includes. - pub fn from_path_with_buf(path: &std::path::Path, buf: &mut Vec) -> Result { - buf.clear(); - std::io::copy(&mut std::fs::File::open(path)?, buf)?; - Self::from_bytes(buf) + Ok(File::from_bytes_owned( + &mut buf, + Metadata::from(source).at(path).with(trust), + Default::default(), + )?) } - /// Constructs a `git-config` file from the provided paths in the order provided. - pub fn from_paths( - paths: impl IntoIterator>, + /// Constructs a `git-config` file from the provided metadata, which must include a path to read from or be ignored. + /// Returns `Ok(None)` if there was not a single input path provided, which is a possibility due to + /// [`Metadata::path`] being an `Option`. + /// If an input path doesn't exist, the entire operation will abort. See [`from_paths_metadata_buf()`][Self::from_paths_metadata_buf()] + /// for a more powerful version of this method. + pub fn from_paths_metadata( + path_meta: impl IntoIterator>, options: Options<'_>, - ) -> Result { - let mut target = Self::default(); + ) -> Result, Error> { let mut buf = Vec::with_capacity(512); - for path in paths { - let path = path.as_ref(); - let mut config = Self::from_path_with_buf(path, &mut buf)?; - resolve_includes(&mut config, Some(path), &mut buf, options)?; - target.append(config); - } - Ok(target) + let err_on_nonexisting_paths = true; + Self::from_paths_metadata_buf(path_meta, &mut buf, err_on_nonexisting_paths, options) } - pub(crate) fn from_bytes(input: &[u8]) -> Result { - Ok(parse::Events::from_bytes_owned(input, None)?.into()) + /// Like [from_paths_metadata()][Self::from_paths_metadata()], but will use `buf` to temporarily store the config file + /// contents for parsing instead of allocating an own buffer. + /// + /// If `err_on_nonexisting_paths` is false, instead of aborting with error, we will continue to the next path instead. + pub fn from_paths_metadata_buf( + path_meta: impl IntoIterator>, + buf: &mut Vec, + err_on_non_existing_paths: bool, + options: Options<'_>, + ) -> Result, Error> { + let mut target = None; + let mut seen = BTreeSet::default(); + for (path, mut meta) in path_meta.into_iter().filter_map(|meta| { + let mut meta = meta.into(); + meta.path.take().map(|p| (p, meta)) + }) { + if !seen.insert(path.clone()) { + continue; + } + + buf.clear(); + std::io::copy( + &mut match std::fs::File::open(&path) { + Ok(f) => f, + Err(err) if !err_on_non_existing_paths && err.kind() == std::io::ErrorKind::NotFound => continue, + Err(err) => return Err(err.into()), + }, + buf, + )?; + meta.path = Some(path); + + let config = Self::from_bytes_owned(buf, meta, options)?; + match &mut target { + None => { + target = Some(config); + } + Some(target) => { + target.append(config); + } + } + } + Ok(target) } } diff --git a/git-config/src/file/init/mod.rs b/git-config/src/file/init/mod.rs index 76024ca5e73..d44dece91cd 100644 --- a/git-config/src/file/init/mod.rs +++ b/git-config/src/file/init/mod.rs @@ -1,7 +1,85 @@ +use git_features::threading::OwnShared; + +use crate::{ + file::{includes, section, Metadata}, + parse, File, +}; + +mod types; +pub use types::{Error, Options}; + +mod comfort; /// pub mod from_env; /// pub mod from_paths; -mod resolve_includes; -pub(crate) use resolve_includes::resolve_includes; +impl<'a> File<'a> { + /// Return an empty `File` with the given `meta`-data to be attached to all new sections. + pub fn new(meta: impl Into>) -> Self { + Self { + frontmatter_events: Default::default(), + frontmatter_post_section: Default::default(), + section_lookup_tree: Default::default(), + sections: Default::default(), + section_id_counter: 0, + section_order: Default::default(), + meta: meta.into(), + } + } + + /// Instantiate a new `File` from given `input`, associating each section and their values with + /// `meta`-data, while respecting `options`. + pub fn from_bytes_no_includes( + input: &'a [u8], + meta: impl Into>, + options: Options<'_>, + ) -> Result { + let meta = meta.into(); + Ok(Self::from_parse_events_no_includes( + parse::Events::from_bytes(input, options.to_event_filter())?, + meta, + )) + } + + /// Instantiate a new `File` from given `events`, associating each section and their values with + /// `meta`-data. + pub fn from_parse_events_no_includes( + parse::Events { frontmatter, sections }: parse::Events<'a>, + meta: impl Into>, + ) -> Self { + let meta = meta.into(); + let mut this = File::new(OwnShared::clone(&meta)); + + this.frontmatter_events = frontmatter; + + for section in sections { + this.push_section_internal(crate::file::Section { + header: section.header, + body: section::Body(section.events), + meta: OwnShared::clone(&meta), + }); + } + + this + } +} + +impl File<'static> { + /// Instantiate a new fully-owned `File` from given `input` (later reused as buffer when resolving includes), + /// associating each section and their values with `meta`-data, while respecting `options`, and + /// following includes as configured there. + pub fn from_bytes_owned( + input_and_buf: &mut Vec, + meta: impl Into>, + options: Options<'_>, + ) -> Result { + let mut config = Self::from_parse_events_no_includes( + parse::Events::from_bytes_owned(input_and_buf, options.to_event_filter()).map_err(Error::from)?, + meta, + ); + + includes::resolve(&mut config, input_and_buf, options).map_err(Error::from)?; + Ok(config) + } +} diff --git a/git-config/src/file/init/resolve_includes.rs b/git-config/src/file/init/resolve_includes.rs deleted file mode 100644 index 6d73690c320..00000000000 --- a/git-config/src/file/init/resolve_includes.rs +++ /dev/null @@ -1,238 +0,0 @@ -use std::{ - borrow::Cow, - path::{Path, PathBuf}, -}; - -use bstr::{BStr, BString, ByteSlice, ByteVec}; -use git_ref::Category; - -use crate::{ - file::{ - init::{from_paths, from_paths::Options}, - SectionBodyId, - }, - File, -}; - -pub(crate) fn resolve_includes( - conf: &mut File<'static>, - config_path: Option<&std::path::Path>, - buf: &mut Vec, - options: from_paths::Options<'_>, -) -> Result<(), from_paths::Error> { - resolve_includes_recursive(conf, config_path, 0, buf, options) -} - -fn resolve_includes_recursive( - target_config: &mut File<'static>, - target_config_path: Option<&Path>, - depth: u8, - buf: &mut Vec, - options: from_paths::Options<'_>, -) -> Result<(), from_paths::Error> { - if depth == options.max_depth { - return if options.error_on_max_depth_exceeded { - Err(from_paths::Error::IncludeDepthExceeded { - max_depth: options.max_depth, - }) - } else { - Ok(()) - }; - } - - let mut paths_to_include = Vec::new(); - - let mut incl_section_ids = Vec::new(); - for name in ["include", "includeIf"] { - if let Ok(ids) = target_config.section_ids_by_name(name) { - for id in ids { - incl_section_ids.push(( - id, - target_config - .section_order - .iter() - .position(|&e| e == id) - .expect("section id is from config"), - )); - } - } - } - incl_section_ids.sort_by(|a, b| a.1.cmp(&b.1)); - - let mut include_paths = Vec::new(); - for (id, _) in incl_section_ids { - if let Some(header) = target_config.section_headers.get(&id) { - if header.name.0.as_ref() == "include" && header.subsection_name.is_none() { - extract_include_path(target_config, &mut include_paths, id) - } else if header.name.0.as_ref() == "includeIf" { - if let Some(condition) = &header.subsection_name { - if include_condition_match(condition.as_ref(), target_config_path, options)? { - extract_include_path(target_config, &mut include_paths, id) - } - } - } - } - } - - for path in include_paths { - let path = resolve(path, target_config_path, options)?; - - if path.is_file() { - paths_to_include.push(path); - } - } - - for config_path in paths_to_include { - let mut include_config = File::from_path_with_buf(&config_path, buf)?; - resolve_includes_recursive(&mut include_config, Some(&config_path), depth + 1, buf, options)?; - target_config.append(include_config); - } - Ok(()) -} - -fn extract_include_path( - target_config: &mut File<'_>, - include_paths: &mut Vec>, - id: SectionBodyId, -) { - if let Some(body) = target_config.sections.get(&id) { - let paths = body.values("path"); - let paths = paths - .iter() - .map(|path| crate::Path::from(Cow::Owned(path.as_ref().to_owned()))); - include_paths.extend(paths); - } -} - -fn include_condition_match( - condition: &BStr, - target_config_path: Option<&Path>, - options: from_paths::Options<'_>, -) -> Result { - let mut tokens = condition.splitn(2, |b| *b == b':'); - let (prefix, condition) = match (tokens.next(), tokens.next()) { - (Some(a), Some(b)) => (a, b), - _ => return Ok(false), - }; - let condition = condition.as_bstr(); - match prefix { - b"gitdir" => gitdir_matches( - condition, - target_config_path, - options, - git_glob::wildmatch::Mode::empty(), - ), - b"gitdir/i" => gitdir_matches( - condition, - target_config_path, - options, - git_glob::wildmatch::Mode::IGNORE_CASE, - ), - b"onbranch" => Ok(onbranch_matches(condition, options).is_some()), - _ => Ok(false), - } -} - -fn onbranch_matches(condition: &BStr, options: Options<'_>) -> Option<()> { - let branch_name = options.branch_name?; - let (_, branch_name) = branch_name - .category_and_short_name() - .filter(|(cat, _)| *cat == Category::LocalBranch)?; - - let condition = if condition.ends_with(b"/") { - let mut condition: BString = condition.into(); - condition.push_str("**"); - Cow::Owned(condition) - } else { - condition.into() - }; - - git_glob::wildmatch( - condition.as_ref(), - branch_name, - git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL, - ) - .then(|| ()) -} - -fn gitdir_matches( - condition_path: &BStr, - target_config_path: Option<&Path>, - from_paths::Options { - git_dir, - interpolate: interpolate_options, - .. - }: from_paths::Options<'_>, - wildmatch_mode: git_glob::wildmatch::Mode, -) -> Result { - let git_dir = - git_path::to_unix_separators_on_windows(git_path::into_bstr(git_dir.ok_or(from_paths::Error::MissingGitDir)?)); - - let mut pattern_path: Cow<'_, _> = { - let path = crate::Path::from(Cow::Borrowed(condition_path)).interpolate(interpolate_options)?; - git_path::into_bstr(path).into_owned().into() - }; - // NOTE: yes, only if we do path interpolation will the slashes be forced to unix separators on windows - if pattern_path != condition_path { - pattern_path = git_path::to_unix_separators_on_windows(pattern_path); - } - - if let Some(relative_pattern_path) = pattern_path.strip_prefix(b"./") { - let parent_dir = target_config_path - .ok_or(from_paths::Error::MissingConfigPath)? - .parent() - .expect("config path can never be /"); - let mut joined_path = git_path::to_unix_separators_on_windows(git_path::into_bstr(parent_dir)).into_owned(); - joined_path.push(b'/'); - joined_path.extend_from_slice(relative_pattern_path); - pattern_path = joined_path.into(); - } - - // NOTE: this special handling of leading backslash is needed to do it like git does - if pattern_path.iter().next() != Some(&(std::path::MAIN_SEPARATOR as u8)) - && !git_path::from_bstr(pattern_path.clone()).is_absolute() - { - let mut prefixed = pattern_path.into_owned(); - prefixed.insert_str(0, "**/"); - pattern_path = prefixed.into() - } - if pattern_path.ends_with(b"/") { - let mut suffixed = pattern_path.into_owned(); - suffixed.push_str("**"); - pattern_path = suffixed.into(); - } - - let match_mode = git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL | wildmatch_mode; - let is_match = git_glob::wildmatch(pattern_path.as_bstr(), git_dir.as_bstr(), match_mode); - if is_match { - return Ok(true); - } - - let expanded_git_dir = git_path::into_bstr(git_path::realpath(git_path::from_byte_slice(&git_dir))?); - Ok(git_glob::wildmatch( - pattern_path.as_bstr(), - expanded_git_dir.as_bstr(), - match_mode, - )) -} - -fn resolve( - path: crate::Path<'_>, - target_config_path: Option<&Path>, - from_paths::Options { - interpolate: interpolate_options, - .. - }: from_paths::Options<'_>, -) -> Result { - let path = path.interpolate(interpolate_options)?; - let path: PathBuf = if path.is_relative() { - target_config_path - .ok_or(from_paths::Error::MissingConfigPath)? - .parent() - .expect("path is a config file which naturally lives in a directory") - .join(path) - } else { - path.into() - }; - Ok(path) -} diff --git a/git-config/src/file/init/types.rs b/git-config/src/file/init/types.rs new file mode 100644 index 00000000000..fcb17c0cabb --- /dev/null +++ b/git-config/src/file/init/types.rs @@ -0,0 +1,47 @@ +use crate::{file::init, parse, parse::Event, path::interpolate}; + +/// The error returned by [`File::from_bytes_no_includes()`][crate::File::from_bytes_no_includes()]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Parse(#[from] parse::Error), + #[error(transparent)] + Interpolate(#[from] interpolate::Error), + #[error(transparent)] + Includes(#[from] init::includes::Error), +} + +/// Options when loading git config using [`File::from_paths_metadata()`][crate::File::from_paths_metadata()]. +#[derive(Clone, Copy, Default)] +pub struct Options<'a> { + /// Configure how to follow includes while handling paths. + pub includes: init::includes::Options<'a>, + /// If true, only value-bearing parse events will be kept to reduce memory usage and increase performance. + /// + /// Note that doing so will degenerate [`write_to()`][crate::File::write_to()] and strip it off its comments + /// and additional whitespace entirely, but will otherwise be a valid configuration file. + pub lossy: bool, +} + +impl Options<'_> { + pub(crate) fn to_event_filter(self) -> Option) -> bool> { + if self.lossy { + Some(discard_nonessential_events) + } else { + None + } + } +} + +fn discard_nonessential_events(e: &Event<'_>) -> bool { + match e { + Event::Whitespace(_) | Event::Comment(_) | Event::Newline(_) => false, + Event::SectionHeader(_) + | Event::SectionKey(_) + | Event::KeyValueSeparator + | Event::Value(_) + | Event::ValueNotDone(_) + | Event::ValueDone(_) => true, + } +} diff --git a/git-config/src/file/meta.rs b/git-config/src/file/meta.rs new file mode 100644 index 00000000000..5bb653b846b --- /dev/null +++ b/git-config/src/file/meta.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use crate::{file, file::Metadata, Source}; + +/// Instantiation +impl Metadata { + /// Return metadata indicating the source of a [`File`][crate::File] is from an API user. + pub fn api() -> Self { + file::Metadata { + path: None, + source: Source::Api, + level: 0, + trust: git_sec::Trust::Full, + } + } + + /// Return metadata as derived from the given `path` at `source`, which will also be used to derive the trust level + /// by checking its ownership. + pub fn try_from_path(path: impl Into, source: Source) -> std::io::Result { + let path = path.into(); + git_sec::Trust::from_path_ownership(&path).map(|trust| Metadata { + path: path.into(), + source, + level: 0, + trust, + }) + } + + /// Set the trust level of this instance to the given `trust` and return it. + /// + /// Useful in conjunction with `Metadata::from(source)`. + pub fn with(mut self, trust: git_sec::Trust) -> Self { + self.trust = trust; + self + } + + /// Set the metadata to be located at the given `path`. + pub fn at(mut self, path: impl Into) -> Self { + self.path = Some(path.into()); + self + } +} + +impl Default for Metadata { + fn default() -> Self { + Metadata::api() + } +} + +impl From for Metadata { + fn from(source: Source) -> Self { + file::Metadata { + path: None, + source, + level: 0, + trust: git_sec::Trust::Full, + } + } +} diff --git a/git-config/src/file/mod.rs b/git-config/src/file/mod.rs index 1c78d090fd6..540c1637761 100644 --- a/git-config/src/file/mod.rs +++ b/git-config/src/file/mod.rs @@ -3,20 +3,27 @@ use std::{ borrow::Cow, collections::HashMap, ops::{Add, AddAssign}, + path::PathBuf, }; use bstr::BStr; +use git_features::threading::OwnShared; mod mutable; +pub use mutable::{multi_value::MultiValueMut, section::SectionMut, value::ValueMut}; -pub use mutable::{ - multi_value::MultiValueMut, - section::{SectionBody, SectionBodyIter, SectionMut}, - value::ValueMut, -}; +/// +pub mod init; + +mod access; +mod impls; +/// +pub mod includes; +mod meta; +mod utils; -mod init; -pub use init::{from_env, from_paths}; +/// +pub mod section; /// pub mod rename_section { @@ -31,9 +38,31 @@ pub mod rename_section { } } -mod access; -mod impls; -mod utils; +/// Additional information about a section. +#[derive(Clone, Debug, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct Metadata { + /// The file path of the source, if known. + pub path: Option, + /// Where the section is coming from. + pub source: crate::Source, + /// The levels of indirection of the file, with 0 being a section + /// that was directly loaded, and 1 being an `include.path` of a + /// level 0 file. + pub level: u8, + /// The trust-level for the section this meta-data is associated with. + pub trust: git_sec::Trust, +} + +/// A section in a git-config file, like `[core]` or `[remote "origin"]`, along with all of its keys. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Section<'a> { + header: crate::parse::section::Header<'a>, + body: section::Body<'a>, + meta: OwnShared, +} + +/// A function to filter metadata, returning `true` if the corresponding but omitted value can be used. +pub type MetadataFilter = dyn FnMut(&'_ Metadata) -> bool; /// A strongly typed index into some range. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] @@ -47,7 +76,7 @@ impl Add for Index { } } -/// A stronlgy typed a size. +/// A strongly typed a size. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Clone, Copy)] pub(crate) struct Size(pub(crate) usize); @@ -68,7 +97,7 @@ impl AddAssign for Size { /// words, it's possible that a section may have an ID of 3 but the next section /// has an ID of 5 as 4 was deleted. #[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] -pub(crate) struct SectionBodyId(pub(crate) usize); +pub(crate) struct SectionId(pub(crate) usize); /// All section body ids referred to by a section name. /// @@ -76,11 +105,12 @@ pub(crate) struct SectionBodyId(pub(crate) usize); /// of section ids with the matched section and name, and is used for precedence /// management. #[derive(PartialEq, Eq, Clone, Debug)] -pub(crate) enum SectionBodyIds<'a> { +pub(crate) enum SectionBodyIdsLut<'a> { /// The list of section ids to use for obtaining the section body. - Terminal(Vec), + Terminal(Vec), /// A hashmap from sub-section names to section ids. - NonTerminal(HashMap, Vec>), + NonTerminal(HashMap, Vec>), } #[cfg(test)] mod tests; +mod write; diff --git a/git-config/src/file/mutable/mod.rs b/git-config/src/file/mutable/mod.rs index aae564d96c5..efe896f0210 100644 --- a/git-config/src/file/mutable/mod.rs +++ b/git-config/src/file/mutable/mod.rs @@ -2,7 +2,11 @@ use std::borrow::Cow; use bstr::{BStr, BString, ByteSlice, ByteVec}; -use crate::{file::SectionBody, parse::Event}; +use crate::{file, parse::Event}; + +pub(crate) mod multi_value; +pub(crate) mod section; +pub(crate) mod value; fn escape_value(value: &BStr) -> BString { let starts_with_whitespace = value.get(0).map_or(false, |b| b.is_ascii_whitespace()); @@ -62,10 +66,8 @@ impl<'a> Whitespace<'a> { } out } -} -impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { - fn from(s: &SectionBody<'a>) -> Self { + fn from_body(s: &file::section::Body<'a>) -> Self { let key_pos = s.0.iter() .enumerate() @@ -103,7 +105,3 @@ impl<'a> From<&SectionBody<'a>> for Whitespace<'a> { .unwrap_or_default() } } - -pub(crate) mod multi_value; -pub(crate) mod section; -pub(crate) mod value; diff --git a/git-config/src/file/mutable/multi_value.rs b/git-config/src/file/mutable/multi_value.rs index 096c55ee439..396b49b6a47 100644 --- a/git-config/src/file/mutable/multi_value.rs +++ b/git-config/src/file/mutable/multi_value.rs @@ -1,24 +1,29 @@ -use crate::file::mutable::{escape_value, Whitespace}; -use crate::file::{SectionBody, SectionBodyId}; -use crate::lookup; -use crate::parse::{section, Event}; -use crate::value::{normalize_bstr, normalize_bstring}; +use std::{borrow::Cow, collections::HashMap, ops::DerefMut}; + use bstr::{BStr, BString, ByteVec}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::ops::DerefMut; + +use crate::{ + file::{ + self, + mutable::{escape_value, Whitespace}, + Section, SectionId, + }, + lookup, + parse::{section, Event}, + value::{normalize_bstr, normalize_bstring}, +}; /// Internal data structure for [`MutableMultiValue`] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub(crate) struct EntryData { - pub(crate) section_id: SectionBodyId, + pub(crate) section_id: SectionId, pub(crate) offset_index: usize, } /// An intermediate representation of a mutable multivar obtained from a [`File`][crate::File]. #[derive(PartialEq, Eq, Debug)] pub struct MultiValueMut<'borrow, 'lookup, 'event> { - pub(crate) section: &'borrow mut HashMap>, + pub(crate) section: &'borrow mut HashMap>, pub(crate) key: section::Key<'lookup>, /// Each entry data struct provides sufficient information to index into /// [`Self::offsets`]. This layer of indirection is used for users to index @@ -27,7 +32,7 @@ pub struct MultiValueMut<'borrow, 'lookup, 'event> { /// Each offset represents the size of a event slice and whether or not the /// event slice is significant or not. This is used to index into the /// actual section. - pub(crate) offsets: HashMap>, + pub(crate) offsets: HashMap>, } impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { @@ -103,7 +108,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { MultiValueMut::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(§ion_id).expect("known section id"), + &mut self.section.get_mut(§ion_id).expect("known section id").body, section_id, offset_index, value.into(), @@ -133,7 +138,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), + &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, value.into(), @@ -153,7 +158,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { Self::set_value_inner( &self.key, &mut self.offsets, - self.section.get_mut(section_id).expect("known section id"), + &mut self.section.get_mut(section_id).expect("known section id").body, *section_id, *offset_index, input, @@ -163,14 +168,14 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { fn set_value_inner<'a: 'event>( key: §ion::Key<'lookup>, - offsets: &mut HashMap>, - section: &mut SectionBody<'event>, - section_id: SectionBodyId, + offsets: &mut HashMap>, + section: &mut file::section::Body<'event>, + section_id: SectionId, offset_index: usize, value: &BStr, ) { let (offset, size) = MultiValueMut::index_and_size(offsets, section_id, offset_index); - let whitespace: Whitespace<'_> = (&*section).into(); + let whitespace = Whitespace::from_body(section); let section = section.as_mut(); section.drain(offset..offset + size); @@ -199,6 +204,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section .get_mut(section_id) .expect("known section id") + .body .as_mut() .drain(offset..offset + size); @@ -221,6 +227,7 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { self.section .get_mut(section_id) .expect("known section id") + .body .as_mut() .drain(offset..offset + size); Self::set_offset(&mut self.offsets, *section_id, *offset_index, 0); @@ -229,8 +236,8 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { } fn index_and_size( - offsets: &'lookup HashMap>, - section_id: SectionBodyId, + offsets: &'lookup HashMap>, + section_id: SectionId, offset_index: usize, ) -> (usize, usize) { offsets @@ -244,8 +251,8 @@ impl<'borrow, 'lookup, 'event> MultiValueMut<'borrow, 'lookup, 'event> { // This must be an associated function rather than a method to allow Rust // to split mutable borrows. fn set_offset( - offsets: &mut HashMap>, - section_id: SectionBodyId, + offsets: &mut HashMap>, + section_id: SectionId, offset_index: usize, value: usize, ) { diff --git a/git-config/src/file/mutable/section.rs b/git-config/src/file/mutable/section.rs index eadd2f9637b..fac6f04febd 100644 --- a/git-config/src/file/mutable/section.rs +++ b/git-config/src/file/mutable/section.rs @@ -1,15 +1,16 @@ use std::{ borrow::Cow, - iter::FusedIterator, ops::{Deref, Range}, }; -use bstr::{BStr, BString, ByteVec}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use smallvec::SmallVec; use crate::{ file::{ + self, mutable::{escape_value, Whitespace}, - Index, Size, + Index, Section, Size, }, lookup, parse, parse::{section::Key, Event}, @@ -19,24 +20,26 @@ use crate::{ /// A opaque type that represents a mutable reference to a section. #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct SectionMut<'a, 'event> { - section: &'a mut SectionBody<'event>, + section: &'a mut Section<'event>, implicit_newline: bool, whitespace: Whitespace<'event>, + newline: SmallVec<[u8; 2]>, } /// Mutating methods. impl<'a, 'event> SectionMut<'a, 'event> { /// Adds an entry to the end of this section name `key` and `value`. pub fn push<'b>(&mut self, key: Key<'event>, value: impl Into<&'b BStr>) { + let body = &mut self.section.body.0; if let Some(ws) = &self.whitespace.pre_key { - self.section.0.push(Event::Whitespace(ws.clone())); + body.push(Event::Whitespace(ws.clone())); } - self.section.0.push(Event::SectionKey(key)); - self.section.0.extend(self.whitespace.key_value_separators()); - self.section.0.push(Event::Value(escape_value(value.into()).into())); + body.push(Event::SectionKey(key)); + body.extend(self.whitespace.key_value_separators()); + body.push(Event::Value(escape_value(value.into()).into())); if self.implicit_newline { - self.section.0.push(Event::Newline(BString::from("\n").into())); + body.push(Event::Newline(BString::from(self.newline.to_vec()).into())); } } @@ -45,12 +48,13 @@ impl<'a, 'event> SectionMut<'a, 'event> { pub fn pop(&mut self) -> Option<(Key<'_>, Cow<'event, BStr>)> { let mut values = Vec::new(); // events are popped in reverse order - while let Some(e) = self.section.0.pop() { + let body = &mut self.section.body.0; + while let Some(e) = body.pop() { match e { Event::SectionKey(k) => { // pop leading whitespace - if let Some(Event::Whitespace(_)) = self.section.0.last() { - self.section.0.pop(); + if let Some(Event::Whitespace(_)) = body.last() { + body.pop(); } if values.len() == 1 { @@ -89,6 +93,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { let range_start = value_range.start; let ret = self.remove_internal(value_range); self.section + .body .0 .insert(range_start, Event::Value(escape_value(value.into()).into())); Some(ret) @@ -106,7 +111,15 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Adds a new line event. Note that you don't need to call this unless /// you've disabled implicit newlines. pub fn push_newline(&mut self) { - self.section.0.push(Event::Newline(Cow::Borrowed("\n".into()))); + self.section + .body + .0 + .push(Event::Newline(Cow::Owned(BString::from(self.newline.to_vec())))); + } + + /// Return the newline used when calling [`push_newline()`][Self::push_newline()]. + pub fn newline(&self) -> &BStr { + self.newline.as_slice().as_bstr() } /// Enables or disables automatically adding newline events after adding @@ -155,12 +168,13 @@ impl<'a, 'event> SectionMut<'a, 'event> { // Internal methods that may require exact indices for faster operations. impl<'a, 'event> SectionMut<'a, 'event> { - pub(crate) fn new(section: &'a mut SectionBody<'event>) -> Self { - let whitespace = (&*section).into(); + pub(crate) fn new(section: &'a mut Section<'event>, newline: SmallVec<[u8; 2]>) -> Self { + let whitespace = Whitespace::from_body(§ion.body); Self { section, implicit_newline: true, whitespace, + newline, } } @@ -192,20 +206,21 @@ impl<'a, 'event> SectionMut<'a, 'event> { } pub(crate) fn delete(&mut self, start: Index, end: Index) { - self.section.0.drain(start.0..end.0); + self.section.body.0.drain(start.0..end.0); } pub(crate) fn set_internal(&mut self, index: Index, key: Key<'event>, value: &BStr) -> Size { let mut size = 0; - self.section.0.insert(index.0, Event::Value(escape_value(value).into())); + let body = &mut self.section.body.0; + body.insert(index.0, Event::Value(escape_value(value).into())); size += 1; let sep_events = self.whitespace.key_value_separators(); size += sep_events.len(); - self.section.0.insert_many(index.0, sep_events.into_iter().rev()); + body.insert_many(index.0, sep_events.into_iter().rev()); - self.section.0.insert(index.0, Event::SectionKey(key)); + body.insert(index.0, Event::SectionKey(key)); size += 1; Size(size) @@ -214,6 +229,7 @@ impl<'a, 'event> SectionMut<'a, 'event> { /// Performs the removal, assuming the range is valid. fn remove_internal(&mut self, range: Range) -> Cow<'event, BStr> { self.section + .body .0 .drain(range) .fold(Cow::Owned(BString::default()), |mut acc, e| { @@ -226,195 +242,15 @@ impl<'a, 'event> SectionMut<'a, 'event> { } impl<'event> Deref for SectionMut<'_, 'event> { - type Target = SectionBody<'event>; + type Target = file::Section<'event>; fn deref(&self) -> &Self::Target { self.section } } -/// A opaque type that represents a section body. -#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] -pub struct SectionBody<'event>(pub(crate) parse::section::Events<'event>); - -impl<'event> SectionBody<'event> { - pub(crate) fn as_ref(&self) -> &[Event<'_>] { - &self.0 - } - +impl<'event> file::section::Body<'event> { pub(crate) fn as_mut(&mut self) -> &mut parse::section::Events<'event> { &mut self.0 } - - /// Returns the the range containing the value events for the `key`. - /// If the value is not found, then this returns an empty range. - fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range, Range)> { - let mut value_range = Range::default(); - let mut key_start = None; - for (i, e) in self.0.iter().enumerate().rev() { - match e { - Event::SectionKey(k) => { - if k == key { - key_start = Some(i); - break; - } - value_range = Range::default(); - } - Event::Value(_) => { - (value_range.start, value_range.end) = (i, i); - } - Event::ValueNotDone(_) | Event::ValueDone(_) => { - if value_range.end == 0 { - value_range.end = i - } else { - value_range.start = i - }; - } - _ => (), - } - } - key_start.map(|key_start| { - // value end needs to be offset by one so that the last value's index - // is included in the range - let value_range = value_range.start..value_range.end + 1; - (key_start..value_range.end, value_range) - }) - } -} - -/// Access -impl<'event> SectionBody<'event> { - /// Retrieves the last matching value in a section with the given key, if present. - #[must_use] - pub fn value(&self, key: impl AsRef) -> Option> { - let key = Key::from_str_unchecked(key.as_ref()); - let (_, range) = self.key_and_value_range_by(&key)?; - let mut concatenated = BString::default(); - - for event in &self.0[range] { - match event { - Event::Value(v) => { - return Some(normalize_bstr(v.as_ref())); - } - Event::ValueNotDone(v) => { - concatenated.push_str(v.as_ref()); - } - Event::ValueDone(v) => { - concatenated.push_str(v.as_ref()); - return Some(normalize_bstring(concatenated)); - } - _ => (), - } - } - None - } - - /// Retrieves all values that have the provided key name. This may return - /// an empty vec, which implies there were no values with the provided key. - #[must_use] - pub fn values(&self, key: impl AsRef) -> Vec> { - let key = &Key::from_str_unchecked(key.as_ref()); - let mut values = Vec::new(); - let mut expect_value = false; - let mut concatenated_value = BString::default(); - - for event in &self.0 { - match event { - Event::SectionKey(event_key) if event_key == key => expect_value = true, - Event::Value(v) if expect_value => { - expect_value = false; - values.push(normalize_bstr(v.as_ref())); - } - Event::ValueNotDone(v) if expect_value => { - concatenated_value.push_str(v.as_ref()); - } - Event::ValueDone(v) if expect_value => { - expect_value = false; - concatenated_value.push_str(v.as_ref()); - values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); - } - _ => (), - } - } - - values - } - - /// Returns an iterator visiting all keys in order. - pub fn keys(&self) -> impl Iterator> { - self.0 - .iter() - .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) - } - - /// Returns true if the section containss the provided key. - #[must_use] - pub fn contains_key(&self, key: impl AsRef) -> bool { - let key = &Key::from_str_unchecked(key.as_ref()); - self.0.iter().any(|e| { - matches!(e, - Event::SectionKey(k) if k == key - ) - }) - } - - /// Returns the number of values in the section. - #[must_use] - pub fn num_values(&self) -> usize { - self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() - } - - /// Returns if the section is empty. - /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for - /// another way to determine semantic emptiness. - #[must_use] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -/// An owning iterator of a section body. Created by [`SectionBody::into_iter`], yielding -/// un-normalized (`key`, `value`) pairs. -// TODO: tests -pub struct SectionBodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); - -impl<'event> IntoIterator for SectionBody<'event> { - type Item = (Key<'event>, Cow<'event, BStr>); - - type IntoIter = SectionBodyIter<'event>; - - fn into_iter(self) -> Self::IntoIter { - SectionBodyIter(self.0.into_iter()) - } -} - -impl<'event> Iterator for SectionBodyIter<'event> { - type Item = (Key<'event>, Cow<'event, BStr>); - - fn next(&mut self) -> Option { - let mut key = None; - let mut partial_value = BString::default(); - let mut value = None; - - for event in self.0.by_ref() { - match event { - Event::SectionKey(k) => key = Some(k), - Event::Value(v) => { - value = Some(v); - break; - } - Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), - Event::ValueDone(v) => { - partial_value.push_str(v.as_ref()); - value = Some(partial_value.into()); - break; - } - _ => (), - } - } - - key.zip(value.map(normalize)) - } } - -impl FusedIterator for SectionBodyIter<'_> {} diff --git a/git-config/src/file/mutable/value.rs b/git-config/src/file/mutable/value.rs index 63d39ed1ec0..2bccfd32ab4 100644 --- a/git-config/src/file/mutable/value.rs +++ b/git-config/src/file/mutable/value.rs @@ -3,6 +3,7 @@ use std::borrow::Cow; use bstr::BStr; use crate::{ + file, file::{mutable::section::SectionMut, Index, Size}, lookup, parse::section, @@ -49,4 +50,14 @@ impl<'borrow, 'lookup, 'event> ValueMut<'borrow, 'lookup, 'event> { self.size = Size(0); } } + + /// Return the section containing the value. + pub fn section(&self) -> &file::Section<'event> { + &self.section + } + + /// Convert this value into its owning mutable section. + pub fn into_section_mut(self) -> file::SectionMut<'borrow, 'event> { + self.section + } } diff --git a/git-config/src/file/section/body.rs b/git-config/src/file/section/body.rs new file mode 100644 index 00000000000..875f1c45ed5 --- /dev/null +++ b/git-config/src/file/section/body.rs @@ -0,0 +1,190 @@ +use std::{borrow::Cow, iter::FusedIterator, ops::Range}; + +use bstr::{BStr, BString, ByteVec}; + +use crate::{ + parse::{section::Key, Event}, + value::{normalize, normalize_bstr, normalize_bstring}, +}; + +/// A opaque type that represents a section body. +#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Debug, Default)] +pub struct Body<'event>(pub(crate) crate::parse::section::Events<'event>); + +/// Access +impl<'event> Body<'event> { + /// Retrieves the last matching value in a section with the given key, if present. + #[must_use] + pub fn value(&self, key: impl AsRef) -> Option> { + let key = Key::from_str_unchecked(key.as_ref()); + let (_, range) = self.key_and_value_range_by(&key)?; + let mut concatenated = BString::default(); + + for event in &self.0[range] { + match event { + Event::Value(v) => { + return Some(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) => { + concatenated.push_str(v.as_ref()); + } + Event::ValueDone(v) => { + concatenated.push_str(v.as_ref()); + return Some(normalize_bstring(concatenated)); + } + _ => (), + } + } + None + } + + /// Retrieves all values that have the provided key name. This may return + /// an empty vec, which implies there were no values with the provided key. + #[must_use] + pub fn values(&self, key: impl AsRef) -> Vec> { + let key = &Key::from_str_unchecked(key.as_ref()); + let mut values = Vec::new(); + let mut expect_value = false; + let mut concatenated_value = BString::default(); + + for event in &self.0 { + match event { + Event::SectionKey(event_key) if event_key == key => expect_value = true, + Event::Value(v) if expect_value => { + expect_value = false; + values.push(normalize_bstr(v.as_ref())); + } + Event::ValueNotDone(v) if expect_value => { + concatenated_value.push_str(v.as_ref()); + } + Event::ValueDone(v) if expect_value => { + expect_value = false; + concatenated_value.push_str(v.as_ref()); + values.push(normalize_bstring(std::mem::take(&mut concatenated_value))); + } + _ => (), + } + } + + values + } + + /// Returns an iterator visiting all keys in order. + pub fn keys(&self) -> impl Iterator> { + self.0 + .iter() + .filter_map(|e| if let Event::SectionKey(k) = e { Some(k) } else { None }) + } + + /// Returns true if the section containss the provided key. + #[must_use] + pub fn contains_key(&self, key: impl AsRef) -> bool { + let key = &Key::from_str_unchecked(key.as_ref()); + self.0.iter().any(|e| { + matches!(e, + Event::SectionKey(k) if k == key + ) + }) + } + + /// Returns the number of values in the section. + #[must_use] + pub fn num_values(&self) -> usize { + self.0.iter().filter(|e| matches!(e, Event::SectionKey(_))).count() + } + + /// Returns if the section is empty. + /// Note that this may count whitespace, see [`num_values()`][Self::num_values()] for + /// another way to determine semantic emptiness. + #[must_use] + pub fn is_void(&self) -> bool { + self.0.is_empty() + } +} + +impl<'event> Body<'event> { + pub(crate) fn as_ref(&self) -> &[Event<'_>] { + &self.0 + } + + /// Returns the the range containing the value events for the `key`. + /// If the value is not found, then this returns an empty range. + pub(crate) fn key_and_value_range_by(&self, key: &Key<'_>) -> Option<(Range, Range)> { + let mut value_range = Range::default(); + let mut key_start = None; + for (i, e) in self.0.iter().enumerate().rev() { + match e { + Event::SectionKey(k) => { + if k == key { + key_start = Some(i); + break; + } + value_range = Range::default(); + } + Event::Value(_) => { + (value_range.start, value_range.end) = (i, i); + } + Event::ValueNotDone(_) | Event::ValueDone(_) => { + if value_range.end == 0 { + value_range.end = i + } else { + value_range.start = i + }; + } + _ => (), + } + } + key_start.map(|key_start| { + // value end needs to be offset by one so that the last value's index + // is included in the range + let value_range = value_range.start..value_range.end + 1; + (key_start..value_range.end, value_range) + }) + } +} + +/// An owning iterator of a section body. Created by [`Body::into_iter`], yielding +/// un-normalized (`key`, `value`) pairs. +// TODO: tests +pub struct BodyIter<'event>(smallvec::IntoIter<[Event<'event>; 64]>); + +impl<'event> IntoIterator for Body<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + type IntoIter = BodyIter<'event>; + + fn into_iter(self) -> Self::IntoIter { + BodyIter(self.0.into_iter()) + } +} + +impl<'event> Iterator for BodyIter<'event> { + type Item = (Key<'event>, Cow<'event, BStr>); + + fn next(&mut self) -> Option { + let mut key = None; + let mut partial_value = BString::default(); + let mut value = None; + + for event in self.0.by_ref() { + match event { + Event::SectionKey(k) => key = Some(k), + Event::Value(v) => { + value = Some(v); + break; + } + Event::ValueNotDone(v) => partial_value.push_str(v.as_ref()), + Event::ValueDone(v) => { + partial_value.push_str(v.as_ref()); + value = Some(partial_value.into()); + break; + } + _ => (), + } + } + + key.zip(value.map(normalize)) + } +} + +impl FusedIterator for BodyIter<'_> {} diff --git a/git-config/src/file/section/mod.rs b/git-config/src/file/section/mod.rs new file mode 100644 index 00000000000..10a5233c51a --- /dev/null +++ b/git-config/src/file/section/mod.rs @@ -0,0 +1,135 @@ +use std::{borrow::Cow, ops::Deref}; + +use bstr::{BString, ByteSlice}; +use smallvec::SmallVec; + +use crate::{ + file, + file::{Metadata, Section, SectionMut}, + parse, + parse::{section, Event}, +}; + +pub(crate) mod body; +pub use body::{Body, BodyIter}; +use git_features::threading::OwnShared; + +use crate::file::write::{extract_newline, platform_newline}; + +impl<'a> Deref for Section<'a> { + type Target = Body<'a>; + + fn deref(&self) -> &Self::Target { + &self.body + } +} + +/// Instantiation and conversion +impl<'a> Section<'a> { + /// Create a new section with the given `name` and optional, `subsection`, `meta`-data and an empty body. + pub fn new( + name: impl Into>, + subsection: impl Into>>, + meta: impl Into>, + ) -> Result { + Ok(Section { + header: parse::section::Header::new(name, subsection)?, + body: Default::default(), + meta: meta.into(), + }) + } +} + +/// Access +impl<'a> Section<'a> { + /// Return our header. + pub fn header(&self) -> §ion::Header<'a> { + &self.header + } + + /// Return our body, containing all keys and values. + pub fn body(&self) -> &Body<'a> { + &self.body + } + + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this section mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + self.header.write_to(&mut out)?; + + if self.body.0.is_empty() { + return Ok(()); + } + + let nl = self + .body + .as_ref() + .iter() + .find_map(extract_newline) + .unwrap_or_else(|| platform_newline()); + + if !self + .body + .as_ref() + .iter() + .take_while(|e| !matches!(e, Event::SectionKey(_))) + .any(|e| e.to_bstr_lossy().contains_str(nl)) + { + out.write_all(nl)?; + } + + let mut saw_newline_after_value = true; + let mut in_key_value_pair = false; + for (idx, event) in self.body.as_ref().iter().enumerate() { + match event { + Event::SectionKey(_) => { + if !saw_newline_after_value { + out.write_all(nl)?; + } + saw_newline_after_value = false; + in_key_value_pair = true; + } + Event::Newline(_) if !in_key_value_pair => { + saw_newline_after_value = true; + } + Event::Value(_) | Event::ValueDone(_) => { + in_key_value_pair = false; + } + _ => {} + } + event.write_to(&mut out)?; + if let Event::ValueNotDone(_) = event { + if self + .body + .0 + .get(idx + 1) + .filter(|e| matches!(e, Event::Newline(_))) + .is_none() + { + out.write_all(nl)?; + } + } + } + Ok(()) + } + + /// Return additional information about this sections origin. + pub fn meta(&self) -> &Metadata { + &self.meta + } + + /// Returns a mutable version of this section for adjustment of values. + pub fn to_mut(&mut self, newline: SmallVec<[u8; 2]>) -> SectionMut<'_, 'a> { + SectionMut::new(self, newline) + } +} diff --git a/git-config/src/file/tests.rs b/git-config/src/file/tests.rs index fffc4d2a92e..c218dbaacb8 100644 --- a/git-config/src/file/tests.rs +++ b/git-config/src/file/tests.rs @@ -1,8 +1,16 @@ +use std::collections::HashMap; + +use crate::{ + file::{self, Section, SectionId}, + parse::section, +}; + mod try_from { use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; + use super::{bodies, headers}; use crate::{ - file::{SectionBody, SectionBodyId, SectionBodyIds}, + file::{self, SectionBodyIdsLut, SectionId}, parse::{ section, tests::util::{name_event, newline_event, section_header, value_event}, @@ -14,7 +22,6 @@ mod try_from { #[test] fn empty() { let config = File::try_from("").unwrap(); - assert!(config.section_headers.is_empty()); assert_eq!(config.section_id_counter, 0); assert!(config.section_lookup_tree.is_empty()); assert!(config.sections.is_empty()); @@ -26,16 +33,16 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); + map.insert(SectionId(0), section_header("core", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 1); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], ); tree }; @@ -43,8 +50,8 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), - SectionBody( + SectionId(0), + file::section::Body( vec![ newline_event(), name_event("a"), @@ -60,8 +67,8 @@ mod try_from { ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] @@ -69,18 +76,18 @@ mod try_from { let mut config = File::try_from("[core.sub]\na=b\nc=d").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", (".", "sub"))); + map.insert(SectionId(0), section_header("core", (".", "sub"))); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 1); let expected_lookup_tree = { let mut tree = HashMap::new(); let mut inner_tree = HashMap::new(); - inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionBodyId(0)]); + inner_tree.insert(Cow::Borrowed("sub".into()), vec![SectionId(0)]); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::NonTerminal(inner_tree)], + vec![SectionBodyIdsLut::NonTerminal(inner_tree)], ); tree }; @@ -88,8 +95,8 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), - SectionBody( + SectionId(0), + file::section::Body( vec![ newline_event(), name_event("a"), @@ -105,8 +112,8 @@ mod try_from { ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!(config.section_order.make_contiguous(), &[SectionBodyId(0)]); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0)]); } #[test] @@ -114,21 +121,21 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[other]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); - map.insert(SectionBodyId(1), section_header("other", None)); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("other", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 2); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0)])], ); tree.insert( section::Name(Cow::Borrowed("other".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(1)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(1)])], ); tree }; @@ -136,8 +143,8 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), - SectionBody( + SectionId(0), + file::section::Body( vec![ newline_event(), name_event("a"), @@ -153,16 +160,13 @@ mod try_from { ), ); sections.insert( - SectionBodyId(1), - SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + SectionId(1), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!( - config.section_order.make_contiguous(), - &[SectionBodyId(0), SectionBodyId(1)] - ); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); } #[test] @@ -170,17 +174,17 @@ mod try_from { let mut config = File::try_from("[core]\na=b\nc=d\n[core]e=f").unwrap(); let expected_separators = { let mut map = HashMap::new(); - map.insert(SectionBodyId(0), section_header("core", None)); - map.insert(SectionBodyId(1), section_header("core", None)); + map.insert(SectionId(0), section_header("core", None)); + map.insert(SectionId(1), section_header("core", None)); map }; - assert_eq!(config.section_headers, expected_separators); + assert_eq!(headers(&config.sections), expected_separators); assert_eq!(config.section_id_counter, 2); let expected_lookup_tree = { let mut tree = HashMap::new(); tree.insert( section::Name(Cow::Borrowed("core".into())), - vec![SectionBodyIds::Terminal(vec![SectionBodyId(0), SectionBodyId(1)])], + vec![SectionBodyIdsLut::Terminal(vec![SectionId(0), SectionId(1)])], ); tree }; @@ -188,8 +192,8 @@ mod try_from { let expected_sections = { let mut sections = HashMap::new(); sections.insert( - SectionBodyId(0), - SectionBody( + SectionId(0), + file::section::Body( vec![ newline_event(), name_event("a"), @@ -205,15 +209,20 @@ mod try_from { ), ); sections.insert( - SectionBodyId(1), - SectionBody(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), + SectionId(1), + file::section::Body(vec![name_event("e"), Event::KeyValueSeparator, value_event("f")].into()), ); sections }; - assert_eq!(config.sections, expected_sections); - assert_eq!( - config.section_order.make_contiguous(), - &[SectionBodyId(0), SectionBodyId(1)] - ); + assert_eq!(bodies(&config.sections), expected_sections); + assert_eq!(config.section_order.make_contiguous(), &[SectionId(0), SectionId(1)]); } } + +fn headers<'a>(sections: &HashMap>) -> HashMap> { + sections.iter().map(|(k, v)| (*k, v.header.clone())).collect() +} + +fn bodies<'a>(sections: &HashMap>) -> HashMap> { + sections.iter().map(|(k, v)| (*k, v.body.clone())).collect() +} diff --git a/git-config/src/file/utils.rs b/git-config/src/file/utils.rs index 7642f05ba14..9b8405b3cae 100644 --- a/git-config/src/file/utils.rs +++ b/git-config/src/file/utils.rs @@ -1,9 +1,9 @@ -use std::collections::HashMap; +use std::{cmp::Ordering, collections::HashMap}; use bstr::BStr; use crate::{ - file::{SectionBody, SectionBodyId, SectionBodyIds, SectionMut}, + file::{self, SectionBodyIdsLut, SectionId}, lookup, parse::section, File, @@ -11,21 +11,17 @@ use crate::{ /// Private helper functions impl<'event> File<'event> { - /// Adds a new section to the config file. - pub(crate) fn push_section_internal( - &mut self, - header: section::Header<'event>, - section: SectionBody<'event>, - ) -> SectionMut<'_, 'event> { - let new_section_id = SectionBodyId(self.section_id_counter); - self.section_headers.insert(new_section_id, header.clone()); + /// Adds a new section to the config file, returning the section id of the newly added section. + pub(crate) fn push_section_internal(&mut self, section: file::Section<'event>) -> SectionId { + let new_section_id = SectionId(self.section_id_counter); self.sections.insert(new_section_id, section); - let lookup = self.section_lookup_tree.entry(header.name).or_default(); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); let mut found_node = false; - if let Some(subsection_name) = header.subsection_name { + if let Some(subsection_name) = header.subsection_name.clone() { for node in lookup.iter_mut() { - if let SectionBodyIds::NonTerminal(subsections) = node { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { found_node = true; subsections .entry(subsection_name.clone()) @@ -37,26 +33,82 @@ impl<'event> File<'event> { if !found_node { let mut map = HashMap::new(); map.insert(subsection_name, vec![new_section_id]); - lookup.push(SectionBodyIds::NonTerminal(map)); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); } } else { for node in lookup.iter_mut() { - if let SectionBodyIds::Terminal(vec) = node { + if let SectionBodyIdsLut::Terminal(vec) = node { found_node = true; vec.push(new_section_id); break; } } if !found_node { - lookup.push(SectionBodyIds::Terminal(vec![new_section_id])); + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); } } self.section_order.push_back(new_section_id); self.section_id_counter += 1; - self.sections - .get_mut(&new_section_id) - .map(SectionMut::new) - .expect("previously inserted section") + new_section_id + } + + /// Inserts `section` after the section that comes `before` it, and maintains correct ordering in all of our lookup structures. + pub(crate) fn insert_section_after(&mut self, section: file::Section<'event>, before: SectionId) -> SectionId { + let lookup_section_order = { + let section_order = &self.section_order; + move |section_id| { + section_order + .iter() + .enumerate() + .find_map(|(idx, id)| (*id == section_id).then(|| idx)) + .expect("before-section exists") + } + }; + + let before_order = lookup_section_order(before); + let new_section_id = SectionId(self.section_id_counter); + self.sections.insert(new_section_id, section); + let header = &self.sections[&new_section_id].header; + let lookup = self.section_lookup_tree.entry(header.name.clone()).or_default(); + + let mut found_node = false; + if let Some(subsection_name) = header.subsection_name.clone() { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::NonTerminal(subsections) = node { + found_node = true; + let sections_with_name_and_subsection_name = + subsections.entry(subsection_name.clone()).or_default(); + let insert_pos = find_insert_pos_by_order( + sections_with_name_and_subsection_name, + before_order, + lookup_section_order, + ); + sections_with_name_and_subsection_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + let mut map = HashMap::new(); + map.insert(subsection_name, vec![new_section_id]); + lookup.push(SectionBodyIdsLut::NonTerminal(map)); + } + } else { + for node in lookup.iter_mut() { + if let SectionBodyIdsLut::Terminal(sections_with_name) = node { + found_node = true; + let insert_pos = find_insert_pos_by_order(sections_with_name, before_order, lookup_section_order); + sections_with_name.insert(insert_pos, new_section_id); + break; + } + } + if !found_node { + lookup.push(SectionBodyIdsLut::Terminal(vec![new_section_id])); + } + } + + self.section_order.insert(before_order + 1, new_section_id); + self.section_id_counter += 1; + new_section_id } /// Returns the mapping between section and subsection name to section ids. @@ -64,10 +116,8 @@ impl<'event> File<'event> { &'a self, section_name: &'a str, subsection_name: Option<&str>, - ) -> Result< - impl Iterator + ExactSizeIterator + DoubleEndedIterator + '_, - lookup::existing::Error, - > { + ) -> Result + ExactSizeIterator + DoubleEndedIterator + '_, lookup::existing::Error> + { let section_name = section::Name::from_str_unchecked(section_name); let section_ids = self .section_lookup_tree @@ -80,14 +130,14 @@ impl<'event> File<'event> { if let Some(subsection_name) = subsection_name { let subsection_name: &BStr = subsection_name.into(); for node in section_ids { - if let SectionBodyIds::NonTerminal(subsection_lookup) = node { + if let SectionBodyIdsLut::NonTerminal(subsection_lookup) = node { maybe_ids = subsection_lookup.get(subsection_name).map(|v| v.iter().copied()); break; } } } else { for node in section_ids { - if let SectionBodyIds::Terminal(subsection_lookup) = node { + if let SectionBodyIdsLut::Terminal(subsection_lookup) = node { maybe_ids = Some(subsection_lookup.iter().copied()); break; } @@ -99,14 +149,14 @@ impl<'event> File<'event> { pub(crate) fn section_ids_by_name<'a>( &'a self, section_name: &'a str, - ) -> Result + '_, lookup::existing::Error> { + ) -> Result + '_, lookup::existing::Error> { let section_name = section::Name::from_str_unchecked(section_name); match self.section_lookup_tree.get(§ion_name) { Some(lookup) => Ok(lookup.iter().flat_map({ let section_order = &self.section_order; move |node| match node { - SectionBodyIds::Terminal(v) => Box::new(v.iter().copied()) as Box>, - SectionBodyIds::NonTerminal(v) => Box::new({ + SectionBodyIdsLut::Terminal(v) => Box::new(v.iter().copied()) as Box>, + SectionBodyIdsLut::NonTerminal(v) => Box::new({ let v: Vec<_> = v.values().flatten().copied().collect(); section_order.iter().filter(move |a| v.contains(a)).copied() }), @@ -115,14 +165,27 @@ impl<'event> File<'event> { None => Err(lookup::existing::Error::SectionMissing), } } +} - // TODO: add note indicating that probably a lot if not all information about the original files is currently lost, - // so can't be written back. This will probably change a lot during refactor, so it's not too important now. - pub(crate) fn append(&mut self, mut other: Self) { - for id in std::mem::take(&mut other.section_order) { - let header = other.section_headers.remove(&id).expect("present"); - let body = other.sections.remove(&id).expect("present"); - self.push_section_internal(header, body); +fn find_insert_pos_by_order( + sections_with_name: &[SectionId], + before_order: usize, + lookup_section_order: impl Fn(SectionId) -> usize, +) -> usize { + let mut insert_pos = sections_with_name.len(); // push back by default + for (idx, candidate_id) in sections_with_name.iter().enumerate() { + let candidate_order = lookup_section_order(*candidate_id); + match candidate_order.cmp(&before_order) { + Ordering::Less => {} + Ordering::Equal => { + insert_pos = idx + 1; // insert right after this one + break; + } + Ordering::Greater => { + insert_pos = idx; // insert before this one + break; + } } } + insert_pos } diff --git a/git-config/src/file/write.rs b/git-config/src/file/write.rs new file mode 100644 index 00000000000..71e3590b7f8 --- /dev/null +++ b/git-config/src/file/write.rs @@ -0,0 +1,79 @@ +use bstr::{BStr, BString, ByteSlice}; + +use crate::{parse::Event, File}; + +impl File<'_> { + /// Serialize this type into a `BString` for convenience. + /// + /// Note that `to_string()` can also be used, but might not be lossless. + #[must_use] + pub fn to_bstring(&self) -> BString { + let mut buf = Vec::new(); + self.write_to(&mut buf).expect("io error impossible"); + buf.into() + } + + /// Stream ourselves to the given `out`, in order to reproduce this file mostly losslessly + /// as it was parsed. + pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { + let nl = self.detect_newline_style(); + + { + for event in self.frontmatter_events.as_ref() { + event.write_to(&mut out)?; + } + + if !ends_with_newline(self.frontmatter_events.as_ref(), nl, true) && self.sections.iter().next().is_some() { + out.write_all(nl)?; + } + } + + let mut prev_section_ended_with_newline = true; + for section_id in &self.section_order { + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + let section = self.sections.get(section_id).expect("known section-id"); + section.write_to(&mut out)?; + + prev_section_ended_with_newline = ends_with_newline(section.body.0.as_ref(), nl, false); + if let Some(post_matter) = self.frontmatter_post_section.get(section_id) { + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + for event in post_matter { + event.write_to(&mut out)?; + } + prev_section_ended_with_newline = ends_with_newline(post_matter, nl, prev_section_ended_with_newline); + } + } + + if !prev_section_ended_with_newline { + out.write_all(nl)?; + } + + Ok(()) + } +} + +pub(crate) fn ends_with_newline(e: &[crate::parse::Event<'_>], nl: impl AsRef<[u8]>, default: bool) -> bool { + if e.is_empty() { + return default; + } + e.iter() + .rev() + .take_while(|e| e.to_bstr_lossy().iter().all(|b| b.is_ascii_whitespace())) + .find_map(|e| e.to_bstr_lossy().contains_str(nl.as_ref()).then(|| true)) + .unwrap_or(false) +} + +pub(crate) fn extract_newline<'a, 'b>(e: &'a Event<'b>) -> Option<&'a BStr> { + match e { + Event::Newline(b) => b.as_ref().into(), + _ => None, + } +} + +pub(crate) fn platform_newline() -> &'static BStr { + if cfg!(windows) { "\r\n" } else { "\n" }.into() +} diff --git a/git-config/src/fs.rs b/git-config/src/fs.rs deleted file mode 100644 index cb5f7ce5d4c..00000000000 --- a/git-config/src/fs.rs +++ /dev/null @@ -1,258 +0,0 @@ -#![allow(missing_docs)] -#![allow(unused)] -#![allow(clippy::result_unit_err)] - -use std::{ - borrow::Cow, - convert::TryFrom, - path::{Path, PathBuf}, -}; - -use bstr::BStr; - -use crate::{ - file::{from_env, from_paths}, - lookup, File, -}; - -// TODO: how does this relate to `File::from_env_paths()`? -#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] -pub enum ConfigSource { - /// System-wide configuration path. This is defined as - /// `$(prefix)/etc/gitconfig`. - System, - /// Also known as the user configuration path. This is usually `~/.gitconfig`. - Global, - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. Any single-valued - /// variable set in this file will be overridden by whatever is in the - /// Global configuration file. - User, - - Repository, - // Worktree(&'a Path), - /// Config<'_> values parsed from the environment. - Env, - Cli, -} - -#[derive(Debug, PartialEq, Clone, Eq, Hash, Default)] -pub struct ConfigBuilder { - no_system: bool, - load_env_conf: bool, - override_system_config: Option, - override_global_config: Option, - override_repo_config: Option, -} - -impl ConfigBuilder { - /// Constructs a new builder that finds the default location - #[must_use] - pub fn new() -> Self { - Self { - load_env_conf: true, - ..Self::default() - } - } - - /// Whether or not to skip reading settings from the system-wide - /// `$(prefix)/etc/gitconfig` file. This corresponds to setting the - /// `GIT_CONFIG_NOSYSTEM` environment variable. - #[must_use] - pub fn no_system(&mut self, no_system: bool) -> &mut Self { - self.no_system = no_system; - self - } - - /// Whether or not to respect `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_`, and - /// `GIT_CONFIG_VALUE_` environment variables. By default, this is true. - #[must_use] - pub fn load_environment_entries(&mut self, load_conf: bool) -> &mut Self { - self.load_env_conf = load_conf; - self - } - - /// Override the system-wide configuration file location. Providing [`None`] - /// or not calling this method will use the default location. - #[must_use] - pub fn system_config_path(&mut self, path: Option) -> &mut Self { - self.override_system_config = path; - self - } - - /// Override the global (user) configuration file location. Providing - /// [`None`] or not calling this method will use the default location. - #[must_use] - pub fn global_config_path(&mut self, path: Option) -> &mut Self { - self.override_global_config = path; - self - } - - /// Sets where to read the repository-specific configuration file. This - /// is equivalent to setting `GIT_CONFIG`. If none is provided, then the - /// builder will look in the default location, `.git/config`. - #[must_use] - pub fn repository_config_path(&mut self, path: Option) -> &mut Self { - self.override_repo_config = path; - self - } - - /// Builds a config, ignoring any failed configuration files. - #[must_use] - pub fn build(&self) -> Config<'static> { - let system_conf = if self.no_system { None } else { todo!() }; - - let global_conf = { - let path = self - .override_global_config - .as_ref() - .map_or_else(|| Path::new(".git/config"), PathBuf::as_path); - - File::from_paths(Some(path), Default::default()).ok() - }; - - let env_conf = if self.load_env_conf { - // TODO: when bringing up the system, make sure options can be passed. Have to review this entire module first though. - File::from_env(from_paths::Options::default()).ok().flatten() - } else { - None - }; - - // let user_conf = if self. - - Config { - system_conf, - global_conf, - user_conf: todo!(), - repository_conf: todo!(), - worktree_conf: todo!(), - env_conf, - cli_conf: todo!(), - } - } - - /// Attempts to build a config, returning error if the environment variable - /// is invalid, if a config file is invalid, or if an overridden config file - /// does not exist. This is only recommended when you have a very controlled - /// system state. Otherwise, this will likely fail more often than you'd - /// like. - pub fn try_build(&self) -> Result, ()> { - todo!() - } -} - -pub struct Config<'a> { - system_conf: Option>, - global_conf: Option>, - user_conf: Option>, - repository_conf: Option>, - worktree_conf: Option>, - env_conf: Option>, - cli_conf: Option>, -} - -impl<'a> Config<'a> { - #[must_use] - pub fn value>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Option { - self.value_with_source(section_name, subsection_name, key) - .map(|(value, _)| value) - } - - fn value_with_source>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Option<(T, ConfigSource)> { - let mapping = self.mapping(); - - for (conf, source) in mapping.iter() { - if let Some(conf) = conf { - if let Ok(v) = conf.value(section_name, subsection_name, key) { - return Some((v, *source)); - } - } - } - - None - } - - pub fn try_value>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Result, lookup::Error> { - self.try_value_with_source(section_name, subsection_name, key) - .map(|res| res.map(|(value, _)| value)) - } - - /// Tries to retrieve the value, returning an error if the parsing fails or - /// if the key was not found. On a successful parse, the value will be - /// returned as well as the source location. This respects the priority of - /// the various configuration files. - pub fn try_value_with_source>>( - &'a self, - section_name: &str, - subsection_name: Option<&str>, - key: &str, - ) -> Result, lookup::Error> { - let mapping = self.mapping(); - - for (conf, source) in mapping.iter() { - if let Some(conf) = conf { - return Ok(Some((conf.value(section_name, subsection_name, key)?, *source))); - } - } - - Ok(None) - } - - /// Returns a mapping from [`File`] to [`ConfigSource`] - const fn mapping(&self) -> [(&Option>, ConfigSource); 6] { - [ - (&self.cli_conf, ConfigSource::Cli), - (&self.env_conf, ConfigSource::Env), - (&self.repository_conf, ConfigSource::Repository), - (&self.user_conf, ConfigSource::User), - (&self.global_conf, ConfigSource::Global), - (&self.system_conf, ConfigSource::System), - ] - } -} - -/// Lower-level interface for directly accessing a -impl<'a> Config<'a> { - /// Retrieves the underlying [`File`] object, if one was found during - /// initialization. - #[must_use] - pub fn config(&self, source: ConfigSource) -> Option<&File<'a>> { - match source { - ConfigSource::System => self.system_conf.as_ref(), - ConfigSource::Global => self.global_conf.as_ref(), - ConfigSource::User => self.user_conf.as_ref(), - ConfigSource::Repository => self.repository_conf.as_ref(), - ConfigSource::Env => self.env_conf.as_ref(), - ConfigSource::Cli => self.cli_conf.as_ref(), - } - } - - /// Retrieves the underlying [`File`] object as a mutable reference, - /// if one was found during initialization. - #[must_use] - pub fn config_mut(&mut self, source: ConfigSource) -> Option<&mut File<'a>> { - match source { - ConfigSource::System => self.system_conf.as_mut(), - ConfigSource::Global => self.global_conf.as_mut(), - ConfigSource::User => self.user_conf.as_mut(), - ConfigSource::Repository => self.repository_conf.as_mut(), - ConfigSource::Env => self.env_conf.as_mut(), - ConfigSource::Cli => self.cli_conf.as_mut(), - } - } -} diff --git a/git-config/src/lib.rs b/git-config/src/lib.rs index cd6712544c0..91f9c92633c 100644 --- a/git-config/src/lib.rs +++ b/git-config/src/lib.rs @@ -24,7 +24,7 @@ //! - Legacy headers like `[section.subsection]` are supposed to be turned into to lower case and compared //! case-sensitively. We keep its case and compare case-insensitively. //! -//! [^1]: When read values do not need normalization. +//! [^1]: When read values do not need normalization and it wasn't parsed in 'owned' mode. //! //! [`git-config` files]: https://git-scm.com/docs/git-config#_configuration_file //! [`File`]: crate::File @@ -38,18 +38,16 @@ )] pub mod file; -/// -pub mod fs; + /// pub mod lookup; pub mod parse; /// pub mod value; mod values; -pub use values::{boolean, color, integer, path}; +pub use values::{color, integer, path}; mod types; -pub use types::{Boolean, Color, File, Integer, Path}; - -mod permissions; -pub use permissions::Permissions; +pub use types::{Boolean, Color, File, Integer, Path, Source}; +/// +pub mod source; diff --git a/git-config/src/parse/comment.rs b/git-config/src/parse/comment.rs index aba6d197085..6d4bb15ffb5 100644 --- a/git-config/src/parse/comment.rs +++ b/git-config/src/parse/comment.rs @@ -9,8 +9,8 @@ impl Comment<'_> { #[must_use] pub fn to_owned(&self) -> Comment<'static> { Comment { - comment_tag: self.comment_tag, - comment: Cow::Owned(self.comment.as_ref().into()), + tag: self.tag, + text: Cow::Owned(self.text.as_ref().into()), } } @@ -26,8 +26,8 @@ impl Comment<'_> { /// Stream ourselves to the given `out`, in order to reproduce this comment losslessly. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { - out.write_all(&[self.comment_tag])?; - out.write_all(self.comment.as_ref()) + out.write_all(&[self.tag])?; + out.write_all(self.text.as_ref()) } } diff --git a/git-config/src/parse/event.rs b/git-config/src/parse/event.rs index d2a57f4ab86..b7b96934d04 100644 --- a/git-config/src/parse/event.rs +++ b/git-config/src/parse/event.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display}; -use bstr::BString; +use bstr::{BStr, BString}; use crate::parse::Event; @@ -15,6 +15,22 @@ impl Event<'_> { buf.into() } + /// Turn ourselves into the text we represent, lossy. + /// + /// Note that this will be partial in case of `ValueNotDone` which doesn't include the backslash, and `SectionHeader` will only + /// provide their name, lacking the sub-section name. + pub fn to_bstr_lossy(&self) -> &BStr { + match self { + Self::ValueNotDone(e) | Self::Whitespace(e) | Self::Newline(e) | Self::Value(e) | Self::ValueDone(e) => { + e.as_ref() + } + Self::KeyValueSeparator => "=".into(), + Self::SectionKey(k) => k.0.as_ref(), + Self::SectionHeader(h) => h.name.0.as_ref(), + Self::Comment(c) => c.text.as_ref(), + } + } + /// Stream ourselves to the given `out`, in order to reproduce this event mostly losslessly /// as it was parsed. pub fn write_to(&self, mut out: impl std::io::Write) -> std::io::Result<()> { diff --git a/git-config/src/parse/events.rs b/git-config/src/parse/events.rs index 768c06769d7..a68c181f7b1 100644 --- a/git-config/src/parse/events.rs +++ b/git-config/src/parse/events.rs @@ -7,7 +7,7 @@ use crate::{ parse::{section, Event, Section}, }; -/// A type store without allocation all events that are typicaly preceeding the first section. +/// A type store without allocation all events that are typically preceding the first section. pub type FrontMatterEvents<'a> = SmallVec<[Event<'a>; 8]>; /// A zero-copy `git-config` file parser. @@ -222,9 +222,9 @@ pub struct Events<'a> { impl Events<'static> { /// Parses the provided bytes, returning an [`Events`] that contains allocated /// and owned events. This is similar to [`Events::from_bytes()`], but performance - /// is degraded as it requires allocation for every event. However, this permits - /// the `input` bytes to be dropped and he parser to be passed around - /// without lifetime worries. + /// is degraded as it requires allocation for every event. + /// + /// Use `filter` to only include those events for which it returns true. pub fn from_bytes_owned<'a>( input: &'a [u8], filter: Option) -> bool>, @@ -238,8 +238,10 @@ impl<'a> Events<'a> { /// [`Events`] that provides methods to accessing leading comments and sections /// of a `git-config` file and can be converted into an iterator of [`Event`] /// for higher level processing. - pub fn from_bytes(input: &'a [u8]) -> Result, parse::Error> { - from_bytes(input, std::convert::identity, None) + /// + /// Use `filter` to only include those events for which it returns true. + pub fn from_bytes(input: &'a [u8], filter: Option) -> bool>) -> Result, parse::Error> { + from_bytes(input, std::convert::identity, filter) } /// Attempt to zero-copy parse the provided `input` string. @@ -248,18 +250,18 @@ impl<'a> Events<'a> { /// isn't guaranteed. #[allow(clippy::should_implement_trait)] pub fn from_str(input: &'a str) -> Result, parse::Error> { - Self::from_bytes(input.as_bytes()) + Self::from_bytes(input.as_bytes(), None) } /// Consumes the parser to produce an iterator of all contained events. #[must_use = "iterators are lazy and do nothing unless consumed"] #[allow(clippy::should_implement_trait)] pub fn into_iter(self) -> impl Iterator> + std::iter::FusedIterator { - self.frontmatter - .into_iter() - .chain(self.sections.into_iter().flat_map(|section| { - std::iter::once(parse::Event::SectionHeader(section.section_header)).chain(section.events) - })) + self.frontmatter.into_iter().chain( + self.sections + .into_iter() + .flat_map(|section| std::iter::once(parse::Event::SectionHeader(section.header)).chain(section.events)), + ) } /// Place all contained events into a single `Vec`. @@ -280,7 +282,7 @@ impl<'a> TryFrom<&'a [u8]> for Events<'a> { type Error = parse::Error; fn try_from(value: &'a [u8]) -> Result { - Events::from_bytes(value) + Events::from_bytes(value, None) } } @@ -301,7 +303,7 @@ fn from_bytes<'a, 'b>( } Some(prev_header) => { sections.push(parse::Section { - section_header: prev_header, + header: prev_header, events: std::mem::take(&mut events), }); } @@ -325,7 +327,7 @@ fn from_bytes<'a, 'b>( } Some(prev_header) => { sections.push(parse::Section { - section_header: prev_header, + header: prev_header, events: std::mem::take(&mut events), }); } diff --git a/git-config/src/parse/key.rs b/git-config/src/parse/key.rs new file mode 100644 index 00000000000..d4b11bb4707 --- /dev/null +++ b/git-config/src/parse/key.rs @@ -0,0 +1,27 @@ +/// An unvalidated parse result of parsing input like `remote.origin.url` or `core.bare`. +#[derive(Debug, PartialEq, Ord, PartialOrd, Eq, Hash, Clone, Copy)] +pub struct Key<'a> { + /// The name of the section, like `core` in `core.bare`. + pub section_name: &'a str, + /// The name of the sub-section, like `origin` in `remote.origin.url`. + pub subsection_name: Option<&'a str>, + /// The name of the section key, like `url` in `remote.origin.url`. + pub value_name: &'a str, +} + +/// Parse `input` like `core.bare` or `remote.origin.url` as a `Key` to make its fields available, +/// or `None` if there were not at least 2 tokens separated by `.`. +/// Note that `input` isn't validated, and is `str` as ascii is a subset of UTF-8 which is required for any valid keys. +pub fn parse_unvalidated(input: &str) -> Option> { + let (section_name, subsection_or_key) = input.split_once('.')?; + let (subsection_name, value_name) = match subsection_or_key.rsplit_once('.') { + Some((subsection, key)) => (Some(subsection), key), + None => (None, subsection_or_key), + }; + + Some(Key { + section_name, + subsection_name, + value_name, + }) +} diff --git a/git-config/src/parse/mod.rs b/git-config/src/parse/mod.rs index a48f8eb9da6..19dc6535f11 100644 --- a/git-config/src/parse/mod.rs +++ b/git-config/src/parse/mod.rs @@ -5,7 +5,7 @@ //! The workflow for interacting with this is to use //! [`from_bytes()`] to obtain all parse events or tokens of the given input. //! -//! On a higher level, one can use [`Events`] to parse all evnets into a set +//! On a higher level, one can use [`Events`] to parse all events into a set //! of easily interpretable data type, similar to what [`File`] does. //! //! [`File`]: crate::File @@ -16,8 +16,7 @@ use bstr::BStr; mod nom; pub use self::nom::from_bytes; -/// -pub mod event; +mod event; #[path = "events.rs"] mod events_type; pub use events_type::{Events, FrontMatterEvents}; @@ -26,6 +25,10 @@ mod error; /// pub mod section; +/// +mod key; +pub use key::{parse_unvalidated as key, Key}; + #[cfg(test)] pub(crate) mod tests; @@ -85,10 +88,10 @@ pub enum Event<'a> { /// A parsed section containing the header and the section events, typically /// comprising the keys and their values. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Section<'a> { /// The section name and subsection name, if any. - pub section_header: section::Header<'a>, + pub header: section::Header<'a>, /// The syntactic events found in this section. pub events: section::Events<'a>, } @@ -97,9 +100,9 @@ pub struct Section<'a> { #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] pub struct Comment<'a> { /// The comment marker used. This is either a semicolon or octothorpe/hash. - pub comment_tag: u8, + pub tag: u8, /// The parsed comment. - pub comment: Cow<'a, BStr>, + pub text: Cow<'a, BStr>, } /// A parser error reports the one-indexed line number where the parsing error diff --git a/git-config/src/parse/nom/mod.rs b/git-config/src/parse/nom/mod.rs index 9a1c8958f92..c54a963a34b 100644 --- a/git-config/src/parse/nom/mod.rs +++ b/git-config/src/parse/nom/mod.rs @@ -17,8 +17,8 @@ use nom::{ use crate::parse::{error::ParseNode, section, Comment, Error, Event}; -/// Attempt to zero-copy parse the provided bytes, passing results to `receive_event`. -pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) -> Result<(), Error> { +/// Attempt to zero-copy parse the provided bytes, passing results to `dispatch`. +pub fn from_bytes<'a>(input: &'a [u8], mut dispatch: impl FnMut(Event<'a>)) -> Result<(), Error> { let bom = unicode_bom::Bom::from(input); let mut newlines = 0; let (i, _) = fold_many0( @@ -31,7 +31,7 @@ pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) }), )), || (), - |_acc, event| receive_event(event), + |_acc, event| dispatch(event), )(&input[bom.len()..]) // I don't think this can panic. many0 errors if the child parser returns // a success where the input was not consumed, but alt will only return Ok @@ -47,7 +47,7 @@ pub fn from_bytes<'a>(input: &'a [u8], mut receive_event: impl FnMut(Event<'a>)) let mut node = ParseNode::SectionHeader; let res = fold_many1( - |i| section(i, &mut node, &mut receive_event), + |i| section(i, &mut node, &mut dispatch), || (), |_acc, additional_newlines| { newlines += additional_newlines; @@ -78,8 +78,8 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { Ok(( i, Comment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.as_bstr()), + tag: comment_tag as u8, + text: Cow::Borrowed(comment.as_bstr()), }, )) } @@ -87,13 +87,9 @@ fn comment(i: &[u8]) -> IResult<&[u8], Comment<'_>> { #[cfg(test)] mod tests; -fn section<'a>( - i: &'a [u8], - node: &mut ParseNode, - receive_event: &mut impl FnMut(Event<'a>), -) -> IResult<&'a [u8], usize> { +fn section<'a>(i: &'a [u8], node: &mut ParseNode, dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { let (mut i, header) = section_header(i)?; - receive_event(Event::SectionHeader(header)); + dispatch(Event::SectionHeader(header)); let mut newlines = 0; @@ -105,7 +101,7 @@ fn section<'a>( if let Ok((new_i, v)) = take_spaces(i) { if old_i != new_i { i = new_i; - receive_event(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); + dispatch(Event::Whitespace(Cow::Borrowed(v.as_bstr()))); } } @@ -113,11 +109,11 @@ fn section<'a>( if old_i != new_i { i = new_i; newlines += new_newlines; - receive_event(Event::Newline(Cow::Borrowed(v.as_bstr()))); + dispatch(Event::Newline(Cow::Borrowed(v.as_bstr()))); } } - if let Ok((new_i, new_newlines)) = key_value_pair(i, node, receive_event) { + if let Ok((new_i, new_newlines)) = key_value_pair(i, node, dispatch) { if old_i != new_i { i = new_i; newlines += new_newlines; @@ -127,7 +123,7 @@ fn section<'a>( if let Ok((new_i, comment)) = comment(i) { if old_i != new_i { i = new_i; - receive_event(Event::Comment(comment)); + dispatch(Event::Comment(comment)); } } @@ -161,6 +157,12 @@ fn section_header(i: &[u8]) -> IResult<&[u8], section::Header<'_>> { }, }; + if header.name.is_empty() { + return Err(nom::Err::Error(NomError { + input: i, + code: ErrorKind::NoneOf, + })); + } return Ok((i, header)); } @@ -238,20 +240,20 @@ fn sub_section_delegate<'a>(i: &'a [u8], push_byte: &mut dyn FnMut(u8)) -> IResu fn key_value_pair<'a>( i: &'a [u8], node: &mut ParseNode, - receive_event: &mut impl FnMut(Event<'a>), + dispatch: &mut impl FnMut(Event<'a>), ) -> IResult<&'a [u8], usize> { *node = ParseNode::Name; let (i, name) = config_name(i)?; - receive_event(Event::SectionKey(section::Key(Cow::Borrowed(name)))); + dispatch(Event::SectionKey(section::Key(Cow::Borrowed(name)))); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); } *node = ParseNode::Value; - let (i, newlines) = config_value(i, receive_event)?; + let (i, newlines) = config_value(i, dispatch)?; Ok((i, newlines)) } @@ -276,117 +278,134 @@ fn config_name(i: &[u8]) -> IResult<&[u8], &BStr> { Ok((i, name.as_bstr())) } -fn config_value<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { +fn config_value<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { if let (i, Some(_)) = opt(char('='))(i)? { - receive_event(Event::KeyValueSeparator); + dispatch(Event::KeyValueSeparator); let (i, whitespace) = opt(take_spaces)(i)?; if let Some(whitespace) = whitespace { - receive_event(Event::Whitespace(Cow::Borrowed(whitespace))); + dispatch(Event::Whitespace(Cow::Borrowed(whitespace))); } - let (i, newlines) = value_impl(i, receive_event)?; + let (i, newlines) = value_impl(i, dispatch)?; Ok((i, newlines)) } else { - receive_event(Event::Value(Cow::Borrowed("".into()))); + dispatch(Event::Value(Cow::Borrowed("".into()))); Ok((i, 0)) } } /// Handles parsing of known-to-be values. This function handles both single /// line values as well as values that are continuations. -fn value_impl<'a>(i: &'a [u8], receive_event: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { - let mut parsed_index: usize = 0; - let mut offset: usize = 0; - let mut newlines = 0; - - let mut was_prev_char_escape_char = false; - // This is required to ignore comment markers if they're in a quote. - let mut is_in_quotes = false; - // Used to determine if we return a Value or Value{Not,}Done - let mut partial_value_found = false; - let mut index: usize = 0; - - for c in i.iter() { - if was_prev_char_escape_char { - was_prev_char_escape_char = false; - match c { - // We're escaping a newline, which means we've found a - // continuation. - b'\n' => { - partial_value_found = true; - receive_event(Event::ValueNotDone(Cow::Borrowed(i[offset..index - 1].as_bstr()))); - receive_event(Event::Newline(Cow::Borrowed(i[index..=index].as_bstr()))); - offset = index + 1; - parsed_index = 0; - newlines += 1; - } - b'n' | b't' | b'\\' | b'b' | b'"' => (), - _ => { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); +fn value_impl<'a>(i: &'a [u8], dispatch: &mut impl FnMut(Event<'a>)) -> IResult<&'a [u8], usize> { + let (i, value_end, newlines, mut dispatch) = { + let new_err = |code| nom::Err::Error(NomError { input: i, code }); + let mut value_end = None::; + let mut value_start: usize = 0; + let mut newlines = 0; + + let mut prev_char_was_backslash = false; + // This is required to ignore comment markers if they're in a quote. + let mut is_in_quotes = false; + // Used to determine if we return a Value or Value{Not,}Done + let mut partial_value_found = false; + let mut last_value_index: usize = 0; + + let mut bytes = i.iter(); + while let Some(mut c) = bytes.next() { + if prev_char_was_backslash { + prev_char_was_backslash = false; + let mut consumed = 1; + if *c == b'\r' { + c = bytes.next().ok_or_else(|| new_err(ErrorKind::Escaped))?; + if *c != b'\n' { + return Err(new_err(ErrorKind::Tag)); + } + consumed += 1; } - } - } else { - match c { - b'\n' => { - parsed_index = index; - break; + + match c { + b'\n' => { + partial_value_found = true; + let backslash = 1; + dispatch(Event::ValueNotDone(Cow::Borrowed( + i[value_start..last_value_index - backslash].as_bstr(), + ))); + let nl_end = last_value_index + consumed; + dispatch(Event::Newline(Cow::Borrowed(i[last_value_index..nl_end].as_bstr()))); + value_start = nl_end; + value_end = None; + newlines += 1; + + last_value_index += consumed; + } + b'n' | b't' | b'\\' | b'b' | b'"' => { + last_value_index += 1; + } + _ => { + return Err(new_err(ErrorKind::Escaped)); + } } - b';' | b'#' if !is_in_quotes => { - parsed_index = index; - break; + } else { + match c { + b'\n' => { + value_end = last_value_index.into(); + break; + } + b';' | b'#' if !is_in_quotes => { + value_end = last_value_index.into(); + break; + } + b'\\' => prev_char_was_backslash = true, + b'"' => is_in_quotes = !is_in_quotes, + _ => {} } - b'\\' => was_prev_char_escape_char = true, - b'"' => is_in_quotes = !is_in_quotes, - _ => {} + last_value_index += 1; } } - index += 1; - } - if parsed_index == 0 { - if index != 0 { - parsed_index = i.len(); - } else { - // Didn't parse anything at all, newline straight away. - receive_event(Event::Value(Cow::Borrowed("".into()))); - return Ok((&i[0..], newlines)); + if prev_char_was_backslash { + return Err(new_err(ErrorKind::Escaped)); } - } - // Handle incomplete escape - if was_prev_char_escape_char { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Escaped, - })); - } + if is_in_quotes { + return Err(new_err(ErrorKind::Tag)); + } - // Handle incomplete quotes - if is_in_quotes { - return Err(nom::Err::Error(NomError { - input: i, - code: ErrorKind::Tag, - })); - } + let value_end = match value_end { + None => { + if last_value_index == 0 { + dispatch(Event::Value(Cow::Borrowed("".into()))); + return Ok((&i[0..], newlines)); + } else { + i.len() + } + } + Some(idx) => idx, + }; - let (i, remainder_value) = { - let mut new_index = parsed_index; - for index in (offset..parsed_index).rev() { - if !i[index].is_ascii_whitespace() { - new_index = index + 1; - break; + let dispatch = move |value: &'a [u8]| { + if partial_value_found { + dispatch(Event::ValueDone(Cow::Borrowed(value.as_bstr()))); + } else { + dispatch(Event::Value(Cow::Borrowed(value.as_bstr()))); } - } - (&i[new_index..], &i[offset..new_index]) + }; + (&i[value_start..], value_end - value_start, newlines, dispatch) }; - if partial_value_found { - receive_event(Event::ValueDone(Cow::Borrowed(remainder_value.as_bstr()))); - } else { - receive_event(Event::Value(Cow::Borrowed(remainder_value.as_bstr()))); - } + let (i, remainder_value) = { + let value_end_no_trailing_whitespace = i[..value_end] + .iter() + .enumerate() + .rev() + .find_map(|(idx, b)| (!b.is_ascii_whitespace()).then(|| idx + 1)) + .unwrap_or(0); + ( + &i[value_end_no_trailing_whitespace..], + &i[..value_end_no_trailing_whitespace], + ) + }; + + dispatch(remainder_value); Ok((i, newlines)) } diff --git a/git-config/src/parse/nom/tests.rs b/git-config/src/parse/nom/tests.rs index 19c58aabada..e8d6a6a2fbb 100644 --- a/git-config/src/parse/nom/tests.rs +++ b/git-config/src/parse/nom/tests.rs @@ -167,8 +167,9 @@ mod section { error::ParseNode, section, tests::util::{ - comment_event, fully_consumed, name_event, newline_event, section_header as parsed_section_header, - value_done_event, value_event, value_not_done_event, whitespace_event, + comment_event, fully_consumed, name_event, newline_custom_event, newline_event, + section_header as parsed_section_header, value_done_event, value_event, value_not_done_event, + whitespace_event, }, Event, Section, }; @@ -187,7 +188,7 @@ mod section { i, ( Section { - section_header: match header.expect("header set") { + header: match header.expect("header set") { Event::SectionHeader(header) => header, _ => unreachable!("unexpected"), }, @@ -199,6 +200,73 @@ mod section { }) } + #[test] + fn empty_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + + #[test] + fn simple_value_with_windows_newlines() { + let mut node = ParseNode::SectionHeader; + assert_eq!( + section(b"[a] k = v\r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event("v"), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + assert_eq!( + section(b"[a] k = \r\n", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("a", None), + events: vec![ + whitespace_event(" "), + name_event("k"), + whitespace_event(" "), + Event::KeyValueSeparator, + whitespace_event(" "), + value_event(""), + newline_custom_event("\r\n") + ] + .into(), + }, + 1 + )), + ); + } + #[test] fn empty_section() { let mut node = ParseNode::SectionHeader; @@ -206,7 +274,7 @@ mod section { section(b"[test]", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("test", None), + header: parsed_section_header("test", None), events: Default::default() }, 0 @@ -225,7 +293,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), @@ -261,7 +329,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("a", None), + header: parsed_section_header("a", None), events: vec![ whitespace_event(" "), name_event("k"), @@ -279,7 +347,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("a", None), + header: parsed_section_header("a", None), events: vec![ whitespace_event(" "), name_event("k"), @@ -305,7 +373,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ newline_event(), whitespace_event(" "), @@ -341,12 +409,31 @@ mod section { section(b"[hello] c", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![whitespace_event(" "), name_event("c"), value_event("")].into() }, 0 )) ); + + assert_eq!( + section(b"[hello] c\nd", &mut node).unwrap(), + fully_consumed(( + Section { + header: parsed_section_header("hello", None), + events: vec![ + whitespace_event(" "), + name_event("c"), + value_event(""), + newline_event(), + name_event("d"), + value_event("") + ] + .into() + }, + 1 + )) + ); } #[test] @@ -361,7 +448,7 @@ mod section { section(section_data, &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("hello", None), + header: parsed_section_header("hello", None), events: vec![ whitespace_event(" "), comment_event(';', " commentA"), @@ -403,7 +490,7 @@ mod section { section(b"[section] a = 1 \"\\\"\\\na ; e \"\\\"\\\nd # \"b\t ; c", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("section", None), + header: parsed_section_header("section", None), events: vec![ whitespace_event(" "), name_event("a"), @@ -432,7 +519,7 @@ mod section { section(b"[section \"a\"] b =\"\\\n;\";a", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("section", (" ", "a")), + header: parsed_section_header("section", (" ", "a")), events: vec![ whitespace_event(" "), name_event("b"), @@ -457,7 +544,7 @@ mod section { section(b"[s]hello #world", &mut node).unwrap(), fully_consumed(( Section { - section_header: parsed_section_header("s", None), + header: parsed_section_header("s", None), events: vec![ name_event("hello"), whitespace_event(" "), @@ -477,7 +564,7 @@ mod value_continuation { use crate::parse::{ section, - tests::util::{into_events, newline_event, value_done_event, value_not_done_event}, + tests::util::{into_events, newline_custom_event, newline_event, value_done_event, value_not_done_event}, }; pub fn value_impl<'a>(i: &'a [u8], events: &mut section::Events<'a>) -> nom::IResult<&'a [u8], ()> { @@ -510,6 +597,23 @@ mod value_continuation { value_done_event(" world") ]) ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hello\\\r\n world", &mut events).unwrap().0, b""); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("hello"), + newline_custom_event("\r\n"), + value_done_event(" world") + ]) + ); + + let mut events = section::Events::default(); + assert!( + value_impl(b"hello\\\r\r\n world", &mut events).is_err(), + "\\r must be followed by \\n" + ); } #[test] @@ -545,6 +649,17 @@ mod value_continuation { value_done_event(";\"") ]) ); + + let mut events = section::Events::default(); + assert_eq!(value_impl(b"\"a\\\r\nb;\";c", &mut events).unwrap().0, b";c"); + assert_eq!( + events, + into_events(vec![ + value_not_done_event("\"a"), + newline_custom_event("\r\n"), + value_done_event("b;\"") + ]) + ); } #[test] @@ -622,6 +737,17 @@ mod value_no_continuation { assert_eq!(events, into_events(vec![value_event("hello")])); } + #[test] + fn windows_newline() { + let mut events = section::Events::default(); + assert_eq!(value_impl(b"hi\r\nrest", &mut events).unwrap().0, b"\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + + events.clear(); + assert_eq!(value_impl(b"hi\r\r\r\nrest", &mut events).unwrap().0, b"\r\r\r\nrest"); + assert_eq!(events, into_events(vec![value_event("hi")])); + } + #[test] fn no_comment_newline() { let mut events = section::Events::default(); diff --git a/git-config/src/parse/section/mod.rs b/git-config/src/parse/section/mod.rs index c4618c951bb..459ad5906ef 100644 --- a/git-config/src/parse/section/mod.rs +++ b/git-config/src/parse/section/mod.rs @@ -12,7 +12,7 @@ pub mod header; pub type Events<'a> = SmallVec<[Event<'a>; 64]>; /// A parsed section header, containing a name and optionally a subsection name. -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct Header<'a> { /// The name of the header. pub(crate) name: Name<'a>, @@ -30,7 +30,7 @@ impl Section<'_> { #[must_use] pub fn to_owned(&self) -> Section<'static> { Section { - section_header: self.section_header.to_owned(), + header: self.header.to_owned(), events: self.events.iter().map(Event::to_owned).collect(), } } @@ -38,7 +38,7 @@ impl Section<'_> { impl Display for Section<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.section_header)?; + write!(f, "{}", self.header)?; for event in &self.events { event.fmt(f)?; } diff --git a/git-config/src/parse/tests.rs b/git-config/src/parse/tests.rs index fdd42594111..790c1fee5af 100644 --- a/git-config/src/parse/tests.rs +++ b/git-config/src/parse/tests.rs @@ -114,8 +114,8 @@ pub(crate) mod util { pub(crate) fn comment(comment_tag: char, comment: &'static str) -> Comment<'static> { Comment { - comment_tag: comment_tag as u8, - comment: Cow::Borrowed(comment.into()), + tag: comment_tag as u8, + text: Cow::Borrowed(comment.into()), } } diff --git a/git-config/src/permissions.rs b/git-config/src/permissions.rs deleted file mode 100644 index 327792e5f0d..00000000000 --- a/git-config/src/permissions.rs +++ /dev/null @@ -1,55 +0,0 @@ -/// Configure security relevant options when loading a git configuration. -#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] -#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] -pub struct Permissions { - /// How to use the system configuration. - /// This is defined as `$(prefix)/etc/gitconfig` on unix. - pub system: git_sec::Permission, - /// How to use the global configuration. - /// This is usually `~/.gitconfig`. - pub global: git_sec::Permission, - /// How to use the user configuration. - /// Second user-specific configuration path; if `$XDG_CONFIG_HOME` is not - /// set or empty, `$HOME/.config/git/config` will be used. - pub user: git_sec::Permission, - /// How to use the repository configuration. - pub repository: git_sec::Permission, - /// How to use worktree configuration from `config.worktree`. - // TODO: figure out how this really applies and provide more information here. - pub worktree: git_sec::Permission, - /// How to use the configuration from environment variables. - pub env: git_sec::Permission, - /// What to do when include files are encountered in loaded configuration. - pub includes: git_sec::Permission, -} - -impl Permissions { - /// Allow everything which usually relates to a fully trusted environment - pub fn all() -> Self { - use git_sec::Permission::*; - Permissions { - system: Allow, - global: Allow, - user: Allow, - repository: Allow, - worktree: Allow, - env: Allow, - includes: Allow, - } - } - - /// If in doubt, this configuration can be used to safely load configuration from sources which is usually trusted, - /// that is system and user configuration. Do load any configuration that isn't trusted as it's now owned by the current user. - pub fn secure() -> Self { - use git_sec::Permission::*; - Permissions { - system: Allow, - global: Allow, - user: Allow, - repository: Deny, - worktree: Deny, - env: Allow, - includes: Deny, - } - } -} diff --git a/git-config/src/source.rs b/git-config/src/source.rs new file mode 100644 index 00000000000..1943e455679 --- /dev/null +++ b/git-config/src/source.rs @@ -0,0 +1,101 @@ +use std::{ + borrow::Cow, + ffi::OsString, + path::{Path, PathBuf}, +}; + +use crate::Source; + +/// The category of a [`Source`], in order of ascending precedence. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Kind { + /// A source shared for the entire system. + System, + /// Application specific configuration unique for each user of the `System`. + Global, + /// Configuration relevant only to the repository, possibly including the worktree. + Repository, + /// Configuration specified after all other configuration was loaded for the purpose of overrides. + Override, +} + +impl Kind { + /// Return a list of sources associated with this `Kind` of source, in order of ascending precedence. + pub fn sources(self) -> &'static [Source] { + let src = match self { + Kind::System => &[Source::System] as &[_], + Kind::Global => &[Source::Git, Source::User], + Kind::Repository => &[Source::Local, Source::Worktree], + Kind::Override => &[Source::Env, Source::Cli, Source::Api], + }; + debug_assert!( + src.iter().all(|src| src.kind() == self), + "BUG: classification of source has to match the ordering here, see `Source::kind()`" + ); + src + } +} + +impl Source { + /// Return true if the source indicates a location within a file of a repository. + pub const fn kind(self) -> Kind { + use Source::*; + match self { + System => Kind::System, + Git | User => Kind::Global, + Local | Worktree => Kind::Repository, + Env | Cli | Api => Kind::Override, + } + } + + /// Returns the location at which a file of this type would be stored, or `None` if + /// there is no notion of persistent storage for this source, with `env_var` to obtain environment variables. + /// Note that the location can be relative for repository-local sources like `Local` and `Worktree`, + /// and the caller has to known which base it it relative to, namely the `common_dir` in the `Local` case + /// and the `git_dir` in the `Worktree` case. + /// Be aware that depending on environment overrides, multiple scopes might return the same path, which should + /// only be loaded once nonetheless. + /// + /// With `env_var` it becomes possible to prevent accessing environment variables entirely to comply with `git-sec` + /// permissions for example. + pub fn storage_location(self, env_var: &mut dyn FnMut(&str) -> Option) -> Option> { + use Source::*; + match self { + System => env_var("GIT_CONFIG_NO_SYSTEM") + .is_none() + .then(|| PathBuf::from(env_var("GIT_CONFIG_SYSTEM").unwrap_or_else(|| "/etc/gitconfig".into())).into()), + Git => match env_var("GIT_CONFIG_GLOBAL") { + Some(global_override) => Some(PathBuf::from(global_override).into()), + None => env_var("XDG_CONFIG_HOME") + .map(|home| { + let mut p = PathBuf::from(home); + p.push("git"); + p.push("config"); + p + }) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".config"); + p.push("git"); + p.push("config"); + p + }) + }) + .map(Cow::Owned), + }, + User => env_var("GIT_CONFIG_GLOBAL") + .map(|global_override| PathBuf::from(global_override).into()) + .or_else(|| { + env_var("HOME").map(|home| { + let mut p = PathBuf::from(home); + p.push(".gitconfig"); + p.into() + }) + }), + Local => Some(Path::new("config").into()), + Worktree => Some(Path::new("config.worktree").into()), + Env | Cli | Api => None, + } + } +} diff --git a/git-config/src/types.rs b/git-config/src/types.rs index 69dfa86068e..246a7d44ea5 100644 --- a/git-config/src/types.rs +++ b/git-config/src/types.rs @@ -1,12 +1,44 @@ use std::collections::{HashMap, VecDeque}; +use git_features::threading::OwnShared; + use crate::{ - color, - file::{SectionBody, SectionBodyId, SectionBodyIds}, + color, file, + file::{Metadata, SectionBodyIdsLut, SectionId}, integer, parse::section, }; +/// A list of known sources for git configuration in order of ascending precedence. +/// +/// This means values from the first one will be overridden by values in the second one, and so forth. +/// Note that included files via `include.path` and `includeIf..path` inherit +/// their source. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Source { + /// System-wide configuration path. This is defined as + /// `$(prefix)/etc/gitconfig` (where prefix is the git-installation directory). + System, + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + Git, + /// This is usually `~/.gitconfig` on unix. + User, + /// The configuration of the repository itself, located in `.git/config`. + Local, + /// Configuration specific to a worktree as created with `git worktree` and + /// typically located in `$GIT_DIR/config.worktree` if `extensions.worktreeConfig` + /// is enabled. + Worktree, + /// values parsed from the environment. + Env, + /// Values set from the command-line. + Cli, + /// Entirely internal from a programmatic source + Api, +} + /// High level `git-config` reader and writer. /// /// This is the full-featured implementation that can deserialize, serialize, @@ -36,6 +68,15 @@ use crate::{ /// key `a` with the above config will fetch `d` or replace `d`, since the last /// valid config key/value pair is `a = d`: /// +/// # Filtering +/// +/// All methods exist in a `*_filter(…, filter)` version to allow skipping sections by +/// their metadata. That way it's possible to select values based on their `git_sec::Trust` +/// for example, or by their location. +/// +/// Note that the filter may be executed even on sections that don't contain the key in question, +/// even though the section will have matched the `name` and `subsection_name` respectively. +/// /// ``` /// # use std::borrow::Cow; /// # use std::convert::TryFrom; @@ -46,25 +87,32 @@ use crate::{ /// Consider the `multi` variants of the methods instead, if you want to work /// with all values. /// +/// # Equality +/// +/// In order to make it useful, equality will ignore all non-value bearing information, hence compare +/// only sections and their names, as well as all of their values. The ordering matters, of course. +/// /// [`raw_value()`]: Self::raw_value -#[derive(PartialEq, Eq, Clone, Debug, Default)] +#[derive(Eq, Clone, Debug, Default)] pub struct File<'event> { - /// The list of events that occur before an actual section. Since a + /// The list of events that occur before any section. Since a /// `git-config` file prohibits global values, this vec is limited to only /// comment, newline, and whitespace events. pub(crate) frontmatter_events: crate::parse::FrontMatterEvents<'event>, + /// Frontmatter events to be placed after the given section. + pub(crate) frontmatter_post_section: HashMap>, /// Section name to section id lookup tree, with section bodies for subsections being in a non-terminal /// variant of `SectionBodyIds`. - pub(crate) section_lookup_tree: HashMap, Vec>>, + pub(crate) section_lookup_tree: HashMap, Vec>>, /// This indirection with the SectionId as the key is critical to flexibly /// supporting `git-config` sections, as duplicated keys are permitted. - pub(crate) sections: HashMap>, - /// A way to reconstruct the complete section being a header and a body. - pub(crate) section_headers: HashMap>, + pub(crate) sections: HashMap>, /// Internal monotonically increasing counter for section ids. pub(crate) section_id_counter: usize, /// Section order for output ordering. - pub(crate) section_order: VecDeque, + pub(crate) section_order: VecDeque, + /// The source of the File itself, which is attached to new sections automatically. + pub(crate) meta: OwnShared, } /// Any value that may contain a foreground color, background color, a @@ -101,10 +149,6 @@ pub struct Integer { } /// Any value that can be interpreted as a boolean. -/// -/// Note that while values can effectively be any byte string, the `git-config` -/// documentation has a strict subset of values that may be interpreted as a -/// boolean value, all of which are ASCII and thus UTF-8 representable. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[allow(missing_docs)] pub struct Boolean(pub bool); diff --git a/git-config/src/values/color.rs b/git-config/src/values/color.rs index 3a37d72d23a..49a63f11c5b 100644 --- a/git-config/src/values/color.rs +++ b/git-config/src/values/color.rs @@ -97,7 +97,7 @@ impl TryFrom> for Color { } } -/// Discriminating enum for [`Color`] values. +/// Discriminating enum for names of [`Color`] values. /// /// `git-config` supports the eight standard colors, their bright variants, an /// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash. diff --git a/git-config/src/values/integer.rs b/git-config/src/values/integer.rs index 1734075679e..0a3e729f9b0 100644 --- a/git-config/src/values/integer.rs +++ b/git-config/src/values/integer.rs @@ -63,8 +63,6 @@ impl TryFrom<&BStr> for Integer { return Ok(Self { value, suffix: None }); } - // Assume we have a prefix at this point. - if s.len() <= 1 { return Err(int_err(s)); } @@ -89,7 +87,7 @@ impl TryFrom> for Integer { } } -/// Integer prefixes that are supported by `git-config`. +/// Integer suffixes that are supported by `git-config`. /// /// These values are base-2 unit of measurements, not the base-10 variants. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] diff --git a/git-config/src/values/mod.rs b/git-config/src/values/mod.rs index 3b65c2dd407..59908866c9b 100644 --- a/git-config/src/values/mod.rs +++ b/git-config/src/values/mod.rs @@ -1,5 +1,4 @@ -/// -pub mod boolean; +mod boolean; /// pub mod color; /// diff --git a/git-config/src/values/path.rs b/git-config/src/values/path.rs index 00ae0410ad4..3833efdf271 100644 --- a/git-config/src/values/path.rs +++ b/git-config/src/values/path.rs @@ -9,20 +9,26 @@ pub mod interpolate { use std::path::PathBuf; /// Options for interpolating paths with [`Path::interpolate()`][crate::Path::interpolate()]. - #[derive(Clone, Copy, Default)] - pub struct Options<'a> { - /// The location where gitoxide or git is installed + #[derive(Clone, Copy)] + pub struct Context<'a> { + /// The location where gitoxide or git is installed. If `None`, `%(prefix)` in paths will cause an error. pub git_install_dir: Option<&'a std::path::Path>, - /// The home directory of the current user. - /// - /// Used during path interpolation. + /// The home directory of the current user. If `None`, `~/` in paths will cause an error. pub home_dir: Option<&'a std::path::Path>, - /// A function returning the home directory of a given user. - /// - /// Used during path interpolation. + /// A function returning the home directory of a given user. If `None`, `~name/` in paths will cause an error. pub home_for_user: Option Option>, } + impl Default for Context<'_> { + fn default() -> Self { + Context { + git_install_dir: None, + home_dir: None, + home_for_user: Some(home_for_user), + } + } + } + /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -114,11 +120,11 @@ impl<'a> Path<'a> { /// wasn't provided. pub fn interpolate( self, - interpolate::Options { + interpolate::Context { git_install_dir, home_dir, home_for_user, - }: interpolate::Options<'_>, + }: interpolate::Context<'_>, ) -> Result, interpolate::Error> { if self.is_empty() { return Err(interpolate::Error::Missing { what: "path" }); diff --git a/git-config/tests/.gitattributes b/git-config/tests/.gitattributes index 5a9d49f190e..272372614d0 100644 --- a/git-config/tests/.gitattributes +++ b/git-config/tests/.gitattributes @@ -1,3 +1,4 @@ # assure line feeds don't interfere with our working copy hash -/fixtures/**/* text crlf=input eol=lf +/fixtures/**/* text eol=lf +/fixtures/repo-config.crlf text eol=crlf diff --git a/git-config/tests/file/access/read_only.rs b/git-config/tests/file/access/read_only.rs index 8a7fc9531a0..acc4e728256 100644 --- a/git-config/tests/file/access/read_only.rs +++ b/git-config/tests/file/access/read_only.rs @@ -1,7 +1,11 @@ use std::{borrow::Cow, convert::TryFrom, error::Error}; use bstr::BStr; -use git_config::{color, integer, path, Boolean, Color, File, Integer}; +use git_config::{ + color, + file::{init, Metadata}, + integer, path, Boolean, Color, File, Integer, +}; use crate::file::cow_str; @@ -23,114 +27,122 @@ fn get_value_for_all_provided_values() -> crate::Result { location = ~/tmp location-quoted = "~/quoted" "#; + for lossy in [false, true] { + let config = File::from_bytes_no_includes( + config.as_bytes(), + Metadata::api(), + init::Options { + lossy, + ..Default::default() + }, + )?; + + assert!(!config.value::("core", None, "bool-explicit")?.0); + assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); + + assert!(config.value::("core", None, "bool-implicit")?.0); + assert!( + config + .try_value::("core", None, "bool-implicit") + .expect("exists")? + .0 + ); - let config = git_config::parse::Events::from_bytes_owned(config.as_bytes(), None).map(File::from)?; + assert!(config.boolean("core", None, "bool-implicit").expect("present")?); + assert_eq!(config.string("doesnt", None, "exist"), None); - assert!(!config.value::("core", None, "bool-explicit")?.0); - assert!(!config.boolean("core", None, "bool-explicit").expect("exists")?); + assert_eq!( + config.value::("core", None, "integer-no-prefix")?, + Integer { + value: 10, + suffix: None + } + ); - assert!(config.value::("core", None, "bool-implicit")?.0); - assert!( - config - .try_value::("core", None, "bool-implicit") - .expect("exists")? - .0 - ); + assert_eq!( + config.value::("core", None, "integer-no-prefix")?, + Integer { + value: 10, + suffix: None + } + ); - assert!(config.boolean("core", None, "bool-implicit").expect("present")?); - assert_eq!(config.string("doesnt", None, "exist"), None); + assert_eq!( + config.value::("core", None, "integer-prefix")?, + Integer { + value: 10, + suffix: Some(integer::Suffix::Gibi), + } + ); - assert_eq!( - config.value::("core", None, "integer-no-prefix")?, - Integer { - value: 10, - suffix: None - } - ); + assert_eq!( + config.value::("core", None, "color")?, + Color { + foreground: Some(color::Name::BrightGreen), + background: Some(color::Name::Red), + attributes: color::Attribute::BOLD + } + ); - assert_eq!( - config.value::("core", None, "integer-no-prefix")?, - Integer { - value: 10, - suffix: None + { + let string = config.value::>("core", None, "other")?; + assert_eq!(string, cow_str("hello world")); + assert!( + matches!(string, Cow::Borrowed(_)), + "no copy is made, we reference the `file` itself" + ); } - ); - assert_eq!( - config.value::("core", None, "integer-prefix")?, - Integer { - value: 10, - suffix: Some(integer::Suffix::Gibi), - } - ); + assert_eq!( + config.string("core", None, "other-quoted").unwrap(), + cow_str("hello world") + ); - assert_eq!( - config.value::("core", None, "color")?, - Color { - foreground: Some(color::Name::BrightGreen), - background: Some(color::Name::Red), - attributes: color::Attribute::BOLD + { + let strings = config.strings("core", None, "other-quoted").unwrap(); + assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); + assert!(matches!(strings[0], Cow::Borrowed(_))); + assert!(matches!(strings[1], Cow::Borrowed(_))); } - ); - { - let string = config.value::>("core", None, "other")?; - assert_eq!(string, cow_str("hello world")); - assert!( - matches!(string, Cow::Borrowed(_)), - "no copy is made, we reference the `file` itself" + { + let cow = config.string("core", None, "other").expect("present"); + assert_eq!(cow.as_ref(), "hello world"); + assert!(matches!(cow, Cow::Borrowed(_))); + } + assert_eq!( + config.string("core", None, "other-quoted").expect("present").as_ref(), + "hello world" ); - } - - assert_eq!( - config.string("core", None, "other-quoted").unwrap(), - cow_str("hello world") - ); - { - let strings = config.strings("core", None, "other-quoted").unwrap(); - assert_eq!(strings, vec![cow_str("hello"), cow_str("hello world")]); - assert!(matches!(strings[0], Cow::Borrowed(_))); - assert!(matches!(strings[1], Cow::Borrowed(_))); - } + { + let actual = config.value::("core", None, "location")?; + assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); + + let home = std::env::current_dir()?; + let expected = home.join("tmp"); + assert!(matches!(actual.value, Cow::Borrowed(_))); + assert_eq!( + actual + .interpolate(path::interpolate::Context { + home_dir: home.as_path().into(), + ..Default::default() + }) + .unwrap(), + expected + ); + } - { - let cow = config.string("core", None, "other").expect("present"); - assert_eq!(cow.as_ref(), "hello world"); - assert!(matches!(cow, Cow::Borrowed(_))); - } - assert_eq!( - config.string("core", None, "other-quoted").expect("present").as_ref(), - "hello world" - ); + let actual = config.path("core", None, "location").expect("present"); + assert_eq!(&*actual, "~/tmp"); - { - let actual = config.value::("core", None, "location")?; - assert_eq!(&*actual, "~/tmp", "no interpolation occurs when querying a path"); + let actual = config.path("core", None, "location-quoted").expect("present"); + assert_eq!(&*actual, "~/quoted"); - let home = std::env::current_dir()?; - let expected = home.join("tmp"); - assert!(matches!(actual.value, Cow::Borrowed(_))); - assert_eq!( - actual - .interpolate(path::interpolate::Options { - home_dir: home.as_path().into(), - ..Default::default() - }) - .unwrap(), - expected - ); + let actual = config.value::("core", None, "location-quoted")?; + assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); } - let actual = config.path("core", None, "location").expect("present"); - assert_eq!(&*actual, "~/tmp"); - - let actual = config.path("core", None, "location-quoted").expect("present"); - assert_eq!(&*actual, "~/quoted"); - - let actual = config.value::("core", None, "location-quoted")?; - assert_eq!(&*actual, "~/quoted", "but the path is unquoted"); - Ok(()) } diff --git a/git-config/tests/file/init/comfort.rs b/git-config/tests/file/init/comfort.rs new file mode 100644 index 00000000000..629bbec7cd6 --- /dev/null +++ b/git-config/tests/file/init/comfort.rs @@ -0,0 +1,81 @@ +use git_config::source; +use git_testtools::Env; +use serial_test::serial; + +#[test] +fn from_globals() { + let config = git_config::File::from_globals().unwrap(); + assert!(config.sections().all(|section| { + let kind = section.meta().source.kind(); + kind != source::Kind::Repository && kind != source::Kind::Override + })); +} + +#[test] +#[serial] +fn from_environment_overrides() { + let config = git_config::File::from_environment_overrides().unwrap(); + assert!(config.is_void()); +} + +#[test] +#[serial] +fn from_git_dir() -> crate::Result { + let worktree_dir = git_testtools::scripted_fixture_repo_read_only("make_config_repo.sh")?; + let git_dir = worktree_dir.join(".git"); + let worktree_dir = worktree_dir.canonicalize()?; + let _env = Env::new() + .set( + "GIT_CONFIG_SYSTEM", + worktree_dir.join("system.config").display().to_string(), + ) + .set("HOME", worktree_dir.display().to_string()) + .set("GIT_CONFIG_COUNT", "1") + .set("GIT_CONFIG_KEY_0", "include.path") + .set( + "GIT_CONFIG_VALUE_0", + worktree_dir.join("c.config").display().to_string(), + ); + + let config = git_config::File::from_git_dir(git_dir)?; + assert_eq!( + config.string("a", None, "local").expect("present").as_ref(), + "value", + "a value from the local repo configuration" + ); + assert_eq!( + config.string("a", None, "local-include").expect("present").as_ref(), + "from-a.config", + "an override from a local repo include" + ); + assert_eq!( + config.string("a", None, "system").expect("present").as_ref(), + "from-system.config", + "system configuration can be overridden with GIT_CONFIG_SYSTEM" + ); + assert_eq!( + config.string("a", None, "system-override").expect("present").as_ref(), + "from-b.config", + "globals resolve their includes" + ); + assert_eq!( + config.string("a", None, "user").expect("present").as_ref(), + "from-user.config", + "per-user configuration" + ); + assert_eq!( + config.string("env", None, "override").expect("present").as_ref(), + "from-c.config", + "environment includes are resolved" + ); + + // on CI this file actually exists in xdg home and our values aren't present + if !(cfg!(unix) && git_testtools::is_ci::cached()) { + assert_eq!( + config.string("a", None, "git").expect("present").as_ref(), + "git-application", + "we load the XDG directories, based on the HOME fallback" + ); + } + Ok(()) +} diff --git a/git-config/tests/file/init/from_env.rs b/git-config/tests/file/init/from_env.rs index 3a945f6fd0a..0cb9109b596 100644 --- a/git-config/tests/file/init/from_env.rs +++ b/git-config/tests/file/init/from_env.rs @@ -1,44 +1,19 @@ -use std::{borrow::Cow, env, fs}; +use std::{borrow::Cow, fs}; use git_config::{ - file::{from_env, from_paths, from_paths::Options}, + file::{includes, init, init::from_env}, File, }; +use git_testtools::Env; use serial_test::serial; use tempfile::tempdir; use crate::file::init::from_paths::escape_backslashes; -pub struct Env<'a> { - altered_vars: Vec<&'a str>, -} - -impl<'a> Env<'a> { - pub(crate) fn new() -> Self { - Env { - altered_vars: Vec::new(), - } - } - - pub(crate) fn set(mut self, var: &'a str, value: impl Into) -> Self { - env::set_var(var, value.into()); - self.altered_vars.push(var); - self - } -} - -impl<'a> Drop for Env<'a> { - fn drop(&mut self) { - for var in &self.altered_vars { - env::remove_var(var); - } - } -} - #[test] #[serial] fn empty_without_relevant_environment() { - let config = File::from_env(Options::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -46,7 +21,7 @@ fn empty_without_relevant_environment() { #[serial] fn empty_with_zero_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "0"); - let config = File::from_env(Options::default()).unwrap(); + let config = File::from_env(Default::default()).unwrap(); assert!(config.is_none()); } @@ -54,7 +29,7 @@ fn empty_with_zero_count() { #[serial] fn parse_error_with_invalid_count() { let _env = Env::new().set("GIT_CONFIG_COUNT", "invalid"); - let err = File::from_env(Options::default()).unwrap_err(); + let err = File::from_env(Default::default()).unwrap_err(); assert!(matches!(err, from_env::Error::InvalidConfigCount { .. })); } @@ -66,7 +41,7 @@ fn single_key_value_pair() { .set("GIT_CONFIG_KEY_0", "core.key") .set("GIT_CONFIG_VALUE_0", "value"); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), Cow::<[u8]>::Borrowed(b"value") @@ -87,7 +62,7 @@ fn multiple_key_value_pairs() { .set("GIT_CONFIG_KEY_2", "core.c") .set("GIT_CONFIG_VALUE_2", "c"); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(Default::default()).unwrap().unwrap(); assert_eq!( config.raw_value("core", None, "a").unwrap(), @@ -112,10 +87,17 @@ fn error_on_relative_paths_in_include_paths() { .set("GIT_CONFIG_KEY_0", "include.path") .set("GIT_CONFIG_VALUE_0", "some_git_config"); - let res = File::from_env(Options::default()); + let res = File::from_env(init::Options { + includes: includes::Options { + max_depth: 1, + ..Default::default() + } + .strict(), + ..Default::default() + }); assert!(matches!( res, - Err(from_env::Error::FromPathsError(from_paths::Error::MissingConfigPath)) + Err(from_env::Error::Includes(includes::Error::MissingConfigPath)) )); } @@ -139,7 +121,15 @@ fn follow_include_paths() { .set("GIT_CONFIG_KEY_3", "include.origin.path") .set("GIT_CONFIG_VALUE_3", escape_backslashes(b_path)); - let config = File::from_env(Options::default()).unwrap().unwrap(); + let config = File::from_env(init::Options { + includes: includes::Options { + max_depth: 1, + ..Default::default() + }, + ..Default::default() + }) + .unwrap() + .unwrap(); assert_eq!( config.raw_value("core", None, "key").unwrap(), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs index 07bc12f8e8f..3732798ed7f 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/mod.rs @@ -1,5 +1,6 @@ mod util; +use git_testtools::Env; use serial_test::serial; use util::{assert_section_value, Condition, GitEnv}; @@ -87,11 +88,10 @@ fn dot_slash_path_is_replaced_with_directory_containing_the_including_config_fil #[test] #[serial] fn dot_slash_from_environment_causes_error() -> crate::Result { - use git_config::file::from_paths; let env = GitEnv::repo_name("worktree")?; { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", @@ -99,12 +99,12 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", "./include.path"); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, - Err(git_config::file::from_env::Error::FromPathsError( - from_paths::Error::MissingConfigPath + Err(git_config::file::init::from_env::Error::Includes( + git_config::file::includes::Error::MissingConfigPath )) ), "this is a failure of resolving the include path, after trying to include it" @@ -113,17 +113,17 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { let absolute_path = escape_backslashes(env.home_dir().join("include.config")); { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set("GIT_CONFIG_KEY_0", "includeIf.gitdir:./worktree/.path") .set("GIT_CONFIG_VALUE_0", &absolute_path); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!( matches!( res, - Err(git_config::file::from_env::Error::FromPathsError( - from_paths::Error::MissingConfigPath + Err(git_config::file::init::from_env::Error::Includes( + git_config::file::includes::Error::MissingConfigPath )) ), "here the pattern path tries to be resolved and fails as target config isn't set" @@ -131,7 +131,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { } { - let _environment = crate::file::init::from_env::Env::new() + let _environment = Env::new() .set("GIT_CONFIG_COUNT", "1") .set( "GIT_CONFIG_KEY_0", @@ -139,7 +139,7 @@ fn dot_slash_from_environment_causes_error() -> crate::Result { ) .set("GIT_CONFIG_VALUE_0", absolute_path); - let res = git_config::File::from_env(env.include_options()); + let res = git_config::File::from_env(env.to_init_options()); assert!(res.is_ok(), "missing paths are ignored as before"); } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs index 763d1625639..064ce420163 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/gitdir/util.rs @@ -7,6 +7,7 @@ use std::{ }; use bstr::{BString, ByteSlice}; +use git_config::file::init::{self}; use crate::file::{ cow_str, @@ -83,9 +84,9 @@ impl GitEnv { } impl GitEnv { - pub fn include_options(&self) -> git_config::file::from_paths::Options { + pub fn to_init_options(&self) -> init::Options<'_> { let mut opts = options_with_git_dir(self.git_dir()); - opts.interpolate.home_dir = Some(self.home_dir()); + opts.includes.interpolate.home_dir = Some(self.home_dir()); opts } @@ -123,7 +124,13 @@ pub fn assert_section_value( paths.push(env.home_dir().join(".gitconfig")); } - let config = git_config::File::from_paths(paths, env.include_options())?; + let config = git_config::File::from_paths_metadata( + paths + .into_iter() + .map(|path| git_config::file::Metadata::try_from_path(path, git_config::Source::Local).unwrap()), + env.to_init_options(), + )? + .expect("non-empty"); assert_eq!( config.string("section", None, "value"), diff --git a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs index 2538c232f52..772cb4c4f21 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/mod.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/mod.rs @@ -1,6 +1,9 @@ -use std::{fs, path::Path}; +use std::{fs, path::Path, str::FromStr}; -use git_config::{file::from_paths, path, File}; +use git_config::{ + file::{includes, init}, + path, File, +}; use tempfile::tempdir; use crate::file::{cow_str, init::from_paths::escape_backslashes}; @@ -9,80 +12,128 @@ mod gitdir; mod onbranch; #[test] -fn include_and_includeif_correct_inclusion_order() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("p"); +fn include_and_includeif_correct_inclusion_order_and_delayed_resolve_include() -> crate::Result { + let dir = tempdir()?; + let config_path = dir.path().join("root"); let first_include_path = dir.path().join("first-incl"); let second_include_path = dir.path().join("second-incl"); let include_if_path = dir.path().join("incl-if"); fs::write( first_include_path.as_path(), " -[core] - b = first-incl-path", - ) - .unwrap(); +; first include beginning +[section] + value = first-incl-path +# first include end no nl", + )?; fs::write( second_include_path.as_path(), - " -[core] - b = second-incl-path", - ) - .unwrap(); + "; second include beginning +[section] + value = second-incl-path ; post value comment +# second include end +", + )?; fs::write( include_if_path.as_path(), " -[core] - b = incl-if-path", - ) - .unwrap(); +# includeIf beginning +[section] + value = incl-if-path +; include if end no nl", + )?; - fs::write( - config_path.as_path(), - format!( - r#" -[core] + let root_config = format!( + r#" ; root beginning +# root pre base +[section] + value = base # base comment +; root post base [include] path = {} -[includeIf "gitdir:p/"] + path = {} ; paths are multi-values +# root past first include +[section] + value = base-past-first-include +# root before include-if no-nl +[includeIf "gitdir:root/"] path = {} +[section] + value = base-past-includeIf [include] - path = {}"#, - escape_backslashes(&first_include_path), - escape_backslashes(&include_if_path), - escape_backslashes(&second_include_path), - ), - ) - .unwrap(); + path = {} +# root past last include +[section] + value = base-past-second-include +; root last include"#, + escape_backslashes(&first_include_path), + escape_backslashes(&first_include_path), + escape_backslashes(&include_if_path), + escape_backslashes(&second_include_path), + ); + fs::write(config_path.as_path(), &root_config)?; let dir = config_path.join(".git"); - let config = File::from_paths(Some(&config_path), options_with_git_dir(&dir)).unwrap(); + for delayed_resolve in [false, true] { + let meta = git_config::file::Metadata::try_from_path(&config_path, git_config::Source::Api)?; + let options = options_with_git_dir(&dir); + let config = if delayed_resolve { + let mut config = File::from_bytes_owned(&mut root_config.as_bytes().into(), meta, Default::default())?; + config.resolve_includes(options)?; + config + } else { + File::from_paths_metadata(Some(meta), options)?.expect("non-empty") + }; - assert_eq!( - config.strings("core", None, "b"), - Some(vec![ - cow_str("first-incl-path"), - cow_str("incl-if-path"), - cow_str("second-incl-path") - ]), - "first include is matched correctly", - ); - assert_eq!( - config.string("core", None, "b"), - Some(cow_str("second-incl-path")), - "second include is matched after incl-if", - ); + assert_eq!( + config.strings("section", None, "value"), + Some(vec![ + cow_str("base"), + cow_str("first-incl-path"), + cow_str("first-incl-path"), + cow_str("base-past-first-include"), + cow_str("incl-if-path"), + cow_str("base-past-includeIf"), + cow_str("second-incl-path"), + cow_str("base-past-second-include"), + ]), + "include order isn't changed also in relation to the root configuratino, delayed_resolve = {}", + delayed_resolve, + ); + assert_eq!(config.sections().count(), 11); + + let config_string = config.to_string(); + let deserialized = File::from_str(&config_string)?; + assert_eq!(config, config, "equality comparisons work"); + assert_eq!( + deserialized.sections().count(), + config.sections().count(), + "sections must match to have a chance for equality" + ); + assert_eq!(config, deserialized, "we can round-trip the information at least"); + assert_eq!( + deserialized.to_string(), + config_string, + "serialization works exactly as before" + ); + } + Ok(()) } -fn options_with_git_dir(git_dir: &Path) -> from_paths::Options<'_> { - from_paths::Options { - git_dir: Some(git_dir), - interpolate: path::interpolate::Options { - home_dir: Some(git_dir.parent().unwrap()), - ..Default::default() - }, +fn options_with_git_dir(git_dir: &Path) -> init::Options<'_> { + init::Options { + includes: includes::Options::follow( + path::interpolate::Context { + home_dir: Some(git_dir.parent().unwrap()), + ..Default::default() + }, + includes::conditional::Context { + git_dir: Some(git_dir), + ..Default::default() + }, + ), ..Default::default() } } diff --git a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs index 819cb33066a..f9255841587 100644 --- a/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs +++ b/git-config/tests/file/init/from_paths/includes/conditional/onbranch.rs @@ -4,7 +4,11 @@ use std::{ }; use bstr::{BString, ByteSlice}; -use git_config::file::from_paths; +use git_config::file::{ + includes, + includes::conditional, + init::{self}, +}; use git_ref::{ transaction::{Change, PreviousValue, RefEdit}, FullName, Target, @@ -233,12 +237,25 @@ value = branch-override-by-include )?; let branch_name = FullName::try_from(BString::from(branch_name))?; - let options = from_paths::Options { - branch_name: Some(branch_name.as_ref()), + let options = init::Options { + includes: includes::Options::follow( + Default::default(), + conditional::Context { + branch_name: Some(branch_name.as_ref()), + ..Default::default() + }, + ), ..Default::default() }; - let config = git_config::File::from_paths(Some(&root_config), options)?; + let config = git_config::File::from_paths_metadata( + Some(git_config::file::Metadata::try_from_path( + &root_config, + git_config::Source::Local, + )?), + options, + )? + .expect("non-empty"); assert_eq!( config.string("section", None, "value"), Some(cow_str(match expect { @@ -269,7 +286,7 @@ value = branch-override-by-include }), git_repository::lock::acquire::Fail::Immediately, )? - .commit(repo.committer().to_ref())?; + .commit(repo.committer_or_default())?; let dir = assure_git_agrees(expect, dir)?; Ok(GitEnv { repo, dir }) diff --git a/git-config/tests/file/init/from_paths/includes/unconditional.rs b/git-config/tests/file/init/from_paths/includes/unconditional.rs index f0c3433471d..6e586a240e4 100644 --- a/git-config/tests/file/init/from_paths/includes/unconditional.rs +++ b/git-config/tests/file/init/from_paths/includes/unconditional.rs @@ -1,9 +1,22 @@ use std::fs; -use git_config::{file::from_paths, File}; +use git_config::{ + file::{includes, init, init::from_paths}, + File, +}; use tempfile::tempdir; -use crate::file::{cow_str, init::from_paths::escape_backslashes}; +use crate::file::{ + cow_str, + init::from_paths::{escape_backslashes, into_meta}, +}; + +fn follow_options() -> init::Options<'static> { + init::Options { + includes: includes::Options::follow(Default::default(), Default::default()), + ..Default::default() + } +} #[test] fn multiple() -> crate::Result { @@ -61,7 +74,7 @@ fn multiple() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], Default::default())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?.expect("non-empty"); assert_eq!(config.string("core", None, "c"), Some(cow_str("12"))); assert_eq!(config.integer("core", None, "d"), Some(Ok(41))); @@ -106,68 +119,70 @@ fn respect_max_depth() -> crate::Result { .replace("{}", &max_depth.to_string()), )?; - let config = File::from_paths(vec![dir.path().join("0")], Default::default())?; + let config = + File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), follow_options())?.expect("non-empty"); assert_eq!(config.integers("core", None, "i"), Some(Ok(vec![0, 1, 2, 3, 4]))); + fn make_options(max_depth: u8, error_on_max_depth_exceeded: bool) -> init::Options<'static> { + init::Options { + includes: includes::Options { + max_depth, + err_on_max_depth_exceeded: error_on_max_depth_exceeded, + ..Default::default() + }, + ..Default::default() + } + } + // with max_allowed_depth of 1 and 4 levels of includes and error_on_max_depth_exceeded: false, max_allowed_depth is exceeded and the value of level 1 is returned // this is equivalent to running git with --no-includes option - let options = from_paths::Options { - max_depth: 1, - error_on_max_depth_exceeded: false, - ..Default::default() - }; - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let options = make_options(1, false); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(1))); // with default max_allowed_depth of 10 and 4 levels of includes, last level is read - let options = from_paths::Options::default(); - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let options = init::Options { + includes: includes::Options::follow(Default::default(), Default::default()), + ..Default::default() + }; + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 5, the base and 4 levels of includes, last level is read - let options = from_paths::Options { - max_depth: 5, - ..Default::default() - }; - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let options = make_options(5, false); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(4))); // with max_allowed_depth of 2 and 4 levels of includes, max_allowed_depth is exceeded and error is returned - let options = from_paths::Options { - max_depth: 2, - ..Default::default() - }; - let config = File::from_paths(vec![dir.path().join("0")], options); + let options = make_options(2, true); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 2 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 2 + })) )); // with max_allowed_depth of 2 and 4 levels of includes and error_on_max_depth_exceeded: false , max_allowed_depth is exceeded and the value of level 2 is returned - let options = from_paths::Options { - max_depth: 2, - error_on_max_depth_exceeded: false, - ..Default::default() - }; - let config = File::from_paths(vec![dir.path().join("0")], options)?; + let options = make_options(2, false); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options)?.expect("non-empty"); assert_eq!(config.integer("core", None, "i"), Some(Ok(2))); // with max_allowed_depth of 0 and 4 levels of includes, max_allowed_depth is exceeded and error is returned - let options = from_paths::Options { - max_depth: 0, - ..Default::default() - }; - let config = File::from_paths(vec![dir.path().join("0")], options); + let options = make_options(0, true); + let config = File::from_paths_metadata(into_meta(vec![dir.path().join("0")]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 0 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 0 + })) )); Ok(()) } #[test] -fn simple() { - let dir = tempdir().unwrap(); +fn simple() -> crate::Result { + let dir = tempdir()?; let a_path = dir.path().join("a"); let b_path = dir.path().join("b"); @@ -187,19 +202,18 @@ fn simple() { escape_backslashes(&b_path), escape_backslashes(&b_path) ), - ) - .unwrap(); + )?; fs::write( b_path.as_path(), " [core] b = false", - ) - .unwrap(); + )?; - let config = File::from_paths(vec![a_path], Default::default()).unwrap(); + let config = File::from_paths_metadata(into_meta(vec![a_path]), follow_options())?.expect("non-empty"); assert_eq!(config.boolean("core", None, "b"), Some(Ok(false))); + Ok(()) } #[test] @@ -233,22 +247,31 @@ fn cycle_detection() -> crate::Result { ), )?; - let options = from_paths::Options { - max_depth: 4, + let options = init::Options { + includes: includes::Options { + max_depth: 4, + err_on_max_depth_exceeded: true, + ..Default::default() + }, ..Default::default() }; - let config = File::from_paths(vec![a_path.clone()], options); + let config = File::from_paths_metadata(into_meta(vec![a_path.clone()]), options); assert!(matches!( config.unwrap_err(), - from_paths::Error::IncludeDepthExceeded { max_depth: 4 } + from_paths::Error::Init(init::Error::Includes(includes::Error::IncludeDepthExceeded { + max_depth: 4 + })) )); - let options = from_paths::Options { - max_depth: 4, - error_on_max_depth_exceeded: false, + let options = init::Options { + includes: includes::Options { + max_depth: 4, + err_on_max_depth_exceeded: false, + ..Default::default() + }, ..Default::default() }; - let config = File::from_paths(vec![a_path], options)?; + let config = File::from_paths_metadata(into_meta(vec![a_path]), options)?.expect("non-empty"); assert_eq!(config.integers("core", None, "b"), Some(Ok(vec![0, 1, 0, 1, 0]))); Ok(()) } @@ -292,7 +315,7 @@ fn nested() -> crate::Result { ), )?; - let config = File::from_paths(vec![c_path], Default::default())?; + let config = File::from_paths_metadata(into_meta(vec![c_path]), follow_options())?.expect("non-empty"); assert_eq!(config.integer("core", None, "c"), Some(Ok(1))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); diff --git a/git-config/tests/file/init/from_paths/mod.rs b/git-config/tests/file/init/from_paths/mod.rs index b2b8d24fb78..8c807e687b8 100644 --- a/git-config/tests/file/init/from_paths/mod.rs +++ b/git-config/tests/file/init/from_paths/mod.rs @@ -1,6 +1,6 @@ -use std::{borrow::Cow, fs, io}; +use std::{fs, path::PathBuf}; -use git_config::File; +use git_config::{File, Source}; use tempfile::tempdir; use crate::file::cow_str; @@ -10,33 +10,29 @@ pub(crate) fn escape_backslashes(path: impl AsRef) -> String { path.as_ref().to_str().unwrap().replace('\\', "\\\\") } -#[test] -fn file_not_found() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("config"); - - let paths = vec![config_path]; - let err = File::from_paths(paths, Default::default()).unwrap_err(); - assert!( - matches!(err, git_config::file::from_paths::Error::Io(io_error) if io_error.kind() == io::ErrorKind::NotFound) - ); -} - -#[test] -fn single_path() { - let dir = tempdir().unwrap(); - let config_path = dir.path().join("config"); - fs::write(config_path.as_path(), b"[core]\nboolean = true").unwrap(); - - let paths = vec![config_path]; - let config = File::from_paths(paths, Default::default()).unwrap(); - - assert_eq!( - config.raw_value("core", None, "boolean").unwrap(), - Cow::<[u8]>::Borrowed(b"true") - ); - - assert_eq!(config.num_values(), 1); +mod from_path_no_includes { + #[test] + fn file_not_found() { + let dir = tempfile::tempdir().unwrap(); + let config_path = dir.path().join("config"); + + let err = git_config::File::from_path_no_includes(config_path, git_config::Source::Local).unwrap_err(); + assert!( + matches!(err, git_config::file::init::from_paths::Error::Io(io_error) if io_error.kind() == std::io::ErrorKind::NotFound) + ); + } + + #[test] + fn single_path() { + let dir = tempfile::tempdir().unwrap(); + let config_path = dir.path().join("config"); + std::fs::write(config_path.as_path(), b"[core]\nboolean = true").unwrap(); + + let config = git_config::File::from_path_no_includes(config_path, git_config::Source::Local).unwrap(); + + assert_eq!(config.raw_value("core", None, "boolean").unwrap().as_ref(), "true"); + assert_eq!(config.num_values(), 1); + } } #[test] @@ -56,18 +52,68 @@ fn multiple_paths_single_value() -> crate::Result { fs::write(d_path.as_path(), b"[core]\na = false")?; let paths = vec![a_path, b_path, c_path, d_path]; - let config = File::from_paths(paths, Default::default())?; + let config = File::from_paths_metadata(into_meta(paths), Default::default())?.expect("non-empty"); assert_eq!(config.boolean("core", None, "a"), Some(Ok(false))); assert_eq!(config.boolean("core", None, "b"), Some(Ok(true))); assert_eq!(config.boolean("core", None, "c"), Some(Ok(true))); assert_eq!(config.num_values(), 4); + assert_eq!(config.sections().count(), 4, "each value is in a dedicated section"); Ok(()) } #[test] -fn multiple_paths_multi_value() -> crate::Result { +fn frontmatter_is_maintained_in_multiple_files() -> crate::Result { + let dir = tempdir()?; + + let a_path = dir.path().join("a"); + fs::write(a_path.as_path(), b";before a\n[core]\na = true")?; + + let b_path = dir.path().join("b"); + fs::write(b_path.as_path(), b";before b\n [core]\nb = true")?; + + let c_path = dir.path().join("c"); + fs::write(c_path.as_path(), b"# nothing in c")?; + + let d_path = dir.path().join("d"); + fs::write(d_path.as_path(), b"\n; nothing in d")?; + + let paths = vec![a_path, b_path, c_path, d_path]; + let mut config = File::from_paths_metadata(into_meta(paths), Default::default())?.expect("non-empty"); + + assert_eq!( + config.to_string(), + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n" + ); + + config.append(config.clone()); + assert_eq!( + config.to_string(), + ";before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n;before a\n[core]\na = true\n;before b\n [core]\nb = true\n# nothing in c\n; nothing in d\n", + "other files post-section matter works as well, adding newlines as needed" + ); + + assert_eq!( + config + .frontmatter() + .expect("present") + .map(|e| e.to_string()) + .collect::>() + .join(""), + ";before a\n" + ); + + assert_eq!( + config.sections_and_postmatter().count(), + 4, + "we trust rust here and don't validate it's actually what we think it is" + ); + Ok(()) +} + +#[test] +fn multiple_paths_multi_value_and_filter() -> crate::Result { let dir = tempdir()?; let a_path = dir.path().join("a"); @@ -85,23 +131,66 @@ fn multiple_paths_multi_value() -> crate::Result { let e_path = dir.path().join("e"); fs::write(e_path.as_path(), b"[include]\npath = e_path")?; - let paths = vec![a_path, b_path, c_path, d_path, e_path]; - let config = File::from_paths(paths, Default::default())?; + let paths_and_source = vec![ + (a_path, Source::System), + (b_path, Source::Git), + (c_path, Source::User), + (d_path, Source::Worktree), + (e_path, Source::Local), + ]; + + let config = File::from_paths_metadata( + paths_and_source + .iter() + .map(|(p, s)| git_config::file::Metadata::try_from_path(p, *s).unwrap()), + Default::default(), + )? + .expect("non-empty"); assert_eq!( config.strings("core", None, "key"), Some(vec![cow_str("a"), cow_str("b"), cow_str("c"),]) ); + assert_eq!( + config.string_filter("core", None, "key", &mut |m| m.source == Source::System), + Some(cow_str("a")), + "the filter discards all values with higher priority" + ); + + assert_eq!( + config.strings_filter("core", None, "key", &mut |m| m.source == Source::Git + || m.source == Source::User), + Some(vec![cow_str("b"), cow_str("c")]) + ); + assert_eq!( config.strings("include", None, "path"), Some(vec![cow_str("d_path"), cow_str("e_path")]) ); assert_eq!(config.num_values(), 5); + assert_eq!( + config + .sections() + .map(|s| ( + s.meta().path.as_ref().expect("each section has file source").to_owned(), + s.meta().source, + s.meta().level + )) + .collect::>(), + paths_and_source.into_iter().map(|(p, s)| (p, s, 0)).collect::>(), + "sections are added in order and their path and sources are set as given, levels are 0 for the non-included ones" + ); Ok(()) } +fn into_meta(paths: impl IntoIterator) -> impl IntoIterator { + paths + .into_iter() + .map(|p| git_config::file::Metadata::try_from_path(p, git_config::Source::Local).unwrap()) +} + mod includes { mod conditional; mod unconditional; diff --git a/git-config/tests/file/init/mod.rs b/git-config/tests/file/init/mod.rs index 47eb4a8d09c..ba8967d590b 100644 --- a/git-config/tests/file/init/mod.rs +++ b/git-config/tests/file/init/mod.rs @@ -1,3 +1,4 @@ +mod comfort; pub mod from_env; mod from_paths; mod from_str; diff --git a/git-config/tests/file/mod.rs b/git-config/tests/file/mod.rs index 3d0ed7bf1a4..2a1133acc15 100644 --- a/git-config/tests/file/mod.rs +++ b/git-config/tests/file/mod.rs @@ -10,7 +10,7 @@ pub fn cow_str(s: &str) -> Cow<'_, BStr> { fn size_in_memory() { assert_eq!( std::mem::size_of::>(), - 1032, + 1040, "This shouldn't change without us noticing" ); } @@ -21,8 +21,7 @@ mod open { #[test] fn parse_config_with_windows_line_endings_successfully() { - let mut buf = Vec::new(); - File::from_path_with_buf(&fixture_path("repo-config.crlf"), &mut buf).unwrap(); + File::from_path_no_includes(&fixture_path("repo-config.crlf"), git_config::Source::Local).unwrap(); } } @@ -30,4 +29,5 @@ mod access; mod impls; mod init; mod mutable; +mod resolve_includes; mod write; diff --git a/git-config/tests/file/mutable/multi_value.rs b/git-config/tests/file/mutable/multi_value.rs index 5e4a5edea04..80219cc1778 100644 --- a/git-config/tests/file/mutable/multi_value.rs +++ b/git-config/tests/file/mutable/multi_value.rs @@ -76,7 +76,7 @@ mod set { values.set_string_at(0, "Hello"); assert_eq!( config.to_string(), - "[core]\n a = Hello\n [core]\n a =d\n a= f" + "[core]\n a = Hello\n [core]\n a =d\n a= f\n" ); Ok(()) } @@ -88,7 +88,7 @@ mod set { values.set_string_at(2, "Hello"); assert_eq!( config.to_string(), - "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello" + "[core]\n a = b\"100\"\n [core]\n a =d\n a= Hello\n" ); Ok(()) } @@ -100,7 +100,7 @@ mod set { values.set_all("Hello"); assert_eq!( config.to_string(), - "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello" + "[core]\n a = Hello\n [core]\n a= Hello\n a =Hello\n" ); Ok(()) } @@ -112,7 +112,7 @@ mod set { values.set_all(""); assert_eq!( config.to_string(), - "[core]\n a = \n [core]\n a= \n a =" + "[core]\n a = \n [core]\n a= \n a =\n" ); Ok(()) } @@ -129,7 +129,7 @@ mod delete { values.delete(0); assert_eq!( config.to_string(), - "[core]\n \n [core]\n a =d\n a= f", + "[core]\n \n [core]\n a =d\n a= f\n", ); } diff --git a/git-config/tests/file/mutable/section.rs b/git-config/tests/file/mutable/section.rs index 3b35401d456..2add8166b84 100644 --- a/git-config/tests/file/mutable/section.rs +++ b/git-config/tests/file/mutable/section.rs @@ -18,7 +18,7 @@ mod remove { assert_eq!(section.num_values(), num_values); } - assert!(!section.is_empty(), "everything is still there"); + assert!(!section.is_void(), "everything is still there"); assert_eq!( config.to_string(), "\n [a]\n \n \n \n \n " @@ -47,16 +47,17 @@ mod pop { num_values -= 1; assert_eq!(section.num_values(), num_values); } - assert!(!section.is_empty(), "there still is some whitespace"); + assert!(!section.is_void(), "there still is some whitespace"); assert_eq!(config.to_string(), "\n [a]\n"); Ok(()) } } mod set { - use super::multi_value_section; use std::convert::TryInto; + use super::multi_value_section; + #[test] fn various_escapes_onto_various_kinds_of_values() -> crate::Result { let mut config = multi_value_section(); @@ -71,7 +72,7 @@ mod set { assert_eq!(prev_value.as_deref().expect("prev value set"), expected_prev_value); } - assert_eq!(config.to_string(), "\n [a]\n a = \n b = \" a\"\n c=\"b\\t\"\n d\"; comment\"\n e =a\\n\\tc d\\\\ \\\"x\\\""); + assert_eq!(config.to_string(), "\n [a]\n a = \n b = \" a\"\n c=\"b\\t\"\n d\"; comment\"\n e =a\\n\\tc d\\\\ \\\"x\\\"\n"); assert_eq!( config .section_mut("a", None)? @@ -128,25 +129,29 @@ mod push { #[test] fn values_are_escaped() { for (value, expected) in [ - ("a b", "[a]\n\tk = a b"), - (" a b", "[a]\n\tk = \" a b\""), - ("a b\t", "[a]\n\tk = \"a b\\t\""), - (";c", "[a]\n\tk = \";c\""), - ("#c", "[a]\n\tk = \"#c\""), - ("a\nb\n\tc", "[a]\n\tk = a\\nb\\n\\tc"), + ("a b", "$head\tk = a b$nl"), + (" a b", "$head\tk = \" a b\"$nl"), + ("a b\t", "$head\tk = \"a b\\t\"$nl"), + (";c", "$head\tk = \";c\"$nl"), + ("#c", "$head\tk = \"#c\"$nl"), + ("a\nb\n\tc", "$head\tk = a\\nb\\n\\tc$nl"), ] { let mut config = git_config::File::default(); let mut section = config.new_section("a", None).unwrap(); section.set_implicit_newline(false); section.push(Key::try_from("k").unwrap(), value); + let expected = expected + .replace("$head", &format!("[a]{nl}", nl = section.newline())) + .replace("$nl", §ion.newline().to_string()); assert_eq!(config.to_bstring(), expected); } } } mod set_leading_whitespace { - use std::convert::TryFrom; + use std::{borrow::Cow, convert::TryFrom}; + use bstr::BString; use git_config::parse::section::Key; use crate::file::cow_str; @@ -155,9 +160,12 @@ mod set_leading_whitespace { fn any_whitespace_is_ok() -> crate::Result { let mut config = git_config::File::default(); let mut section = config.new_section("core", None)?; - section.set_leading_whitespace(cow_str("\n\t").into()); + + let nl = section.newline().to_owned(); + section.set_leading_whitespace(Some(Cow::Owned(BString::from(format!("{nl}\t"))))); section.push(Key::try_from("a")?, "v"); - assert_eq!(config.to_string(), "[core]\n\n\ta = v\n"); + + assert_eq!(config.to_string(), format!("[core]{nl}{nl}\ta = v{nl}")); Ok(()) } diff --git a/git-config/tests/file/mutable/value.rs b/git-config/tests/file/mutable/value.rs index fbce8329e16..c1354fee540 100644 --- a/git-config/tests/file/mutable/value.rs +++ b/git-config/tests/file/mutable/value.rs @@ -42,26 +42,32 @@ mod set_string { use crate::file::mutable::value::init_config; fn assert_set_string(expected: &str) { + let nl = git_config::File::default().detect_newline_style().to_string(); for input in [ "[a] k = v", "[a] k = ", "[a] k =", - "[a] k =\n", + "[a] k =$nl", "[a] k ", - "[a] k\n", + "[a] k$nl", "[a] k", ] { - let mut file: git_config::File = input.parse().unwrap(); + let mut file: git_config::File = input.replace("$nl", &nl).parse().unwrap(); let mut v = file.raw_value_mut("a", None, "k").unwrap(); v.set_string(expected); assert_eq!(v.get().unwrap().as_ref(), expected); - let file: git_config::File = match file.to_string().parse() { + let file_string = file.to_string(); + let file: git_config::File = match file_string.parse() { Ok(f) => f, - Err(err) => panic!("{:?} failed with: {}", file.to_string(), err), + Err(err) => panic!("{:?} failed with: {}", file_string, err), }; - assert_eq!(file.raw_value("a", None, "k").expect("present").as_ref(), expected); + assert_eq!( + file.raw_value("a", None, "k").expect("present").as_ref(), + expected, + "{file_string:?}" + ); } } @@ -126,7 +132,8 @@ mod set_string { a=hello world [core] c=d - e=f"#, + e=f +"#, ); let mut value = config.raw_value_mut("core", None, "e")?; @@ -137,7 +144,8 @@ mod set_string { a=hello world [core] c=d - e="#, + e= +"#, ); Ok(()) } @@ -154,14 +162,14 @@ mod delete { value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f", + "[core]\n \n [core]\n c=d\n e=f\n", ); let mut value = config.raw_value_mut("core", None, "c")?; value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n \n e=f", + "[core]\n \n [core]\n \n e=f\n", ); Ok(()) } @@ -189,7 +197,8 @@ mod delete { a=hello world [core] c=d - e=f"#, + e=f +"#, ); Ok(()) } @@ -204,7 +213,7 @@ mod delete { } assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" + "[core]\n \n [core]\n c=d\n e=f\n" ); Ok(()) } @@ -224,7 +233,7 @@ b value.delete(); assert_eq!( config.to_string(), - "[core]\n \n [core]\n c=d\n e=f" + "[core]\n \n [core]\n c=d\n e=f\n" ); Ok(()) } diff --git a/git-config/tests/file/resolve_includes.rs b/git-config/tests/file/resolve_includes.rs new file mode 100644 index 00000000000..25c0697544e --- /dev/null +++ b/git-config/tests/file/resolve_includes.rs @@ -0,0 +1,38 @@ +use git_config::{file, file::init}; + +#[test] +fn missing_includes_are_ignored_by_default() -> crate::Result { + let input = r#" + [include] + path = /etc/absolute/missing.config + path = relative-missing.config + path = ./also-relative-missing.config + path = %(prefix)/no-install.config + path = ~/no-user.config + + [includeIf "onbranch:no-branch"] + path = no-branch-provided.config + [includeIf "gitdir:./no-git-dir"] + path = no-git-dir.config + "#; + + let mut config: git_config::File<'_> = input.parse()?; + + let mut follow_options = file::includes::Options::follow(Default::default(), Default::default()); + follow_options.err_on_missing_config_path = false; + config.resolve_includes(init::Options { + includes: follow_options, + ..Default::default() + })?; + + assert!( + config + .resolve_includes(init::Options { + includes: follow_options.strict(), + ..Default::default() + }) + .is_err(), + "strict mode fails if something couldn't be interpolated" + ); + Ok(()) +} diff --git a/git-config/tests/file/write.rs b/git-config/tests/file/write.rs index cd6d3418e58..00929a88c2a 100644 --- a/git-config/tests/file/write.rs +++ b/git-config/tests/file/write.rs @@ -1,5 +1,47 @@ use std::convert::TryFrom; +use bstr::ByteVec; +use git_config::file::{init, Metadata}; + +#[test] +fn empty_sections_roundtrip() { + let input = r#" + [a] + [b] + [c] + + [d] +"#; + + let config = git_config::File::try_from(input).unwrap(); + assert_eq!(config.to_bstring(), input); +} + +#[test] +fn empty_sections_with_comments_roundtrip() { + let input = r#"; pre-a + [a] # side a + ; post a + [b] ; side b + [c] ; side c + ; post c + [d] # side d +"#; + + let mut config = git_config::File::try_from(input).unwrap(); + let mut single_string = config.to_bstring(); + assert_eq!(single_string, input); + assert_eq!( + config.append(config.clone()).to_string(), + { + let clone = single_string.clone(); + single_string.push_str(&clone); + single_string + }, + "string-duplication is the same as data structure duplication" + ); +} + #[test] fn complex_lossless_roundtrip() { let input = r#" @@ -22,10 +64,10 @@ fn complex_lossless_roundtrip() { ; more comments # another one - [test "sub-section \"special\" C:\\root"] + [test "sub-section \"special\" C:\\root"] ; section comment bool-explicit = false bool-implicit - integer-no-prefix = 10 + integer-no-prefix = 10 ; a value comment integer-prefix = 10g color = brightgreen red \ bold @@ -49,4 +91,20 @@ fn complex_lossless_roundtrip() { "#; let config = git_config::File::try_from(input).unwrap(); assert_eq!(config.to_bstring(), input); + + let lossy_config = git_config::File::from_bytes_owned( + &mut input.as_bytes().into(), + Metadata::api(), + init::Options { + lossy: true, + ..Default::default() + }, + ) + .unwrap(); + + let lossy_config: git_config::File = lossy_config.to_string().parse().unwrap(); + assert_eq!( + lossy_config, config, + "Even lossy configuration serializes properly to be able to restore all values" + ); } diff --git a/git-config/tests/fixtures/generated-archives/.gitignore b/git-config/tests/fixtures/generated-archives/.gitignore new file mode 100644 index 00000000000..aca5621b76a --- /dev/null +++ b/git-config/tests/fixtures/generated-archives/.gitignore @@ -0,0 +1 @@ +/make_config_repo.tar.xz diff --git a/git-config/tests/fixtures/make_config_repo.sh b/git-config/tests/fixtures/make_config_repo.sh new file mode 100644 index 00000000000..d4d3deb49d9 --- /dev/null +++ b/git-config/tests/fixtures/make_config_repo.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q + +cat <>.git/config +[a] + local = value + +[include] + path = ../a.config +EOF + + +cat <>a.config +[a] + local-include = from-a.config +EOF + +cat <>system.config +[a] + system = from-system.config + system-override = base +[include] + path = ./b.config +EOF + +cat <>.gitconfig +[a] + user = from-user.config +EOF + +cat <>b.config +[a] + system-override = from-b.config +EOF + +cat <>c.config +[env] + override = from-c.config +EOF + +mkdir -p .config/git +( + cd .config/git + cat <>config + [a] + git = git-application +EOF +) diff --git a/git-config/tests/fixtures/repo-config.crlf b/git-config/tests/fixtures/repo-config.crlf index 96672b93484..a7cd0042827 100644 --- a/git-config/tests/fixtures/repo-config.crlf +++ b/git-config/tests/fixtures/repo-config.crlf @@ -1,14 +1,14 @@ -; hello -# world - -[core] - repositoryformatversion = 0 - bare = true -[other] - repositoryformatversion = 0 - - bare = true - -# before section with newline -[foo] - repositoryformatversion = 0 +; hello +# world + +[core] + repositoryformatversion = 0 + bare = true +[other] + repositoryformatversion = 0 + + bare = true + +# before section with newline +[foo] + repositoryformatversion = 0 diff --git a/git-config/tests/parse/from_bytes.rs b/git-config/tests/parse/from_bytes.rs index 91acb2c5d04..b12e09d94ff 100644 --- a/git-config/tests/parse/from_bytes.rs +++ b/git-config/tests/parse/from_bytes.rs @@ -2,6 +2,14 @@ use git_config::parse::Events; use super::*; +#[test] +fn fuzz() { + assert!( + Events::from_str("[]A=\\\\\r\\\n\n").is_err(), + "empty sections are not allowed, and it won't crash either" + ); +} + #[test] #[rustfmt::skip] fn complex() { @@ -152,8 +160,8 @@ fn skips_bom() { "; assert_eq!( - Events::from_bytes(bytes), - Events::from_bytes(bytes_with_gb18030_bom.as_bytes()) + Events::from_bytes(bytes, None), + Events::from_bytes(bytes_with_gb18030_bom.as_bytes(), None) ); assert_eq!( Events::from_bytes_owned(bytes, None), diff --git a/git-config/tests/parse/key.rs b/git-config/tests/parse/key.rs new file mode 100644 index 00000000000..af46e647486 --- /dev/null +++ b/git-config/tests/parse/key.rs @@ -0,0 +1,39 @@ +use git_config::parse; + +#[test] +fn missing_dot_is_invalid() { + assert_eq!(parse::key("hello"), None); +} + +#[test] +fn section_name_and_key() { + assert_eq!( + parse::key("core.bare"), + Some(parse::Key { + section_name: "core", + subsection_name: None, + value_name: "bare" + }) + ); +} + +#[test] +fn section_name_subsection_and_key() { + assert_eq!( + parse::key("remote.origin.url"), + Some(parse::Key { + section_name: "remote", + subsection_name: Some("origin"), + value_name: "url" + }) + ); + + assert_eq!( + parse::key("includeIf.gitdir/i:C:\\bare.git.path"), + Some(parse::Key { + section_name: "includeIf", + subsection_name: Some("gitdir/i:C:\\bare.git"), + value_name: "path" + }) + ); +} diff --git a/git-config/tests/parse/mod.rs b/git-config/tests/parse/mod.rs index 36d4df3ba37..659ebc26db9 100644 --- a/git-config/tests/parse/mod.rs +++ b/git-config/tests/parse/mod.rs @@ -4,6 +4,7 @@ use git_config::parse::{Event, Events, Section}; mod error; mod from_bytes; +mod key; mod section; #[test] diff --git a/git-config/tests/values/path.rs b/git-config/tests/values/path.rs index c41dd464d3a..6ae146e5a39 100644 --- a/git-config/tests/values/path.rs +++ b/git-config/tests/values/path.rs @@ -37,7 +37,7 @@ mod interpolate { std::path::PathBuf::from(format!("{}{}{}", git_install_dir, std::path::MAIN_SEPARATOR, expected)); assert_eq!( git_config::Path::from(cow_str(val)) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { git_install_dir: Path::new(git_install_dir).into(), ..Default::default() }) @@ -55,7 +55,7 @@ mod interpolate { let git_install_dir = "/tmp/git"; assert_eq!( git_config::Path::from(Cow::Borrowed(b(path))) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { git_install_dir: Path::new(git_install_dir).into(), ..Default::default() }) @@ -77,7 +77,7 @@ mod interpolate { let expected = home.join("user").join("bar"); assert_eq!( git_config::Path::from(cow_str(path)) - .interpolate(path::interpolate::Options { + .interpolate(path::interpolate::Context { home_dir: Some(&home), home_for_user: Some(home_for_user), ..Default::default() @@ -115,7 +115,7 @@ mod interpolate { fn interpolate_without_context( path: impl AsRef, ) -> Result, git_config::path::interpolate::Error> { - git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(path::interpolate::Options { + git_config::Path::from(Cow::Owned(path.as_ref().to_owned().into())).interpolate(path::interpolate::Context { home_for_user: Some(home_for_user), ..Default::default() }) diff --git a/git-credentials/CHANGELOG.md b/git-credentials/CHANGELOG.md index af0d80ffe7f..4c0eeb30824 100644 --- a/git-credentials/CHANGELOG.md +++ b/git-credentials/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 4 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +39,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +51,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-date/CHANGELOG.md b/git-date/CHANGELOG.md index a170e201216..b981067a844 100644 --- a/git-date/CHANGELOG.md +++ b/git-date/CHANGELOG.md @@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.0.2 (2022-07-22) + +### New Features + + - initialize `Time` from `now_utc` and `now_local` + Localtime support depends on some other factors now, but that + will only get better over time. + + We might have to document `unsound_local_time` at some point. + - `Time::is_set()` to see if the time is more than just the default. + +### Commit Statistics + + + + - 3 commits contributed to the release. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) + - `Time::is_set()` to see if the time is more than just the default. ([`aeda76e`](https://github.com/Byron/gitoxide/commit/aeda76ed500d2edba62747d667227f2664edd267)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.0.1 (2022-06-13) ### New Features @@ -16,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 58 calendar days. + - 5 commits contributed to the release over the course of 58 calendar days. - 59 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -31,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - reflog lookup by date is complete ([`b3d009e`](https://github.com/Byron/gitoxide/commit/b3d009e80e3e81afd3d095fa2d7b5fc737d585c7)) - Add `Time` type. ([`cfb6a72`](https://github.com/Byron/gitoxide/commit/cfb6a726ddb763f7c22688f8ef309e719c2dfce4)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Merge branch 'test-archive-support' ([`350df01`](https://github.com/Byron/gitoxide/commit/350df01042d6ca8b93f8737fa101e69b50535a0f)) diff --git a/git-date/Cargo.toml b/git-date/Cargo.toml index 269585540b2..babccb1fc69 100644 --- a/git-date/Cargo.toml +++ b/git-date/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-date" -version = "0.0.1" +version = "0.0.2" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project parsing dates the way git does" @@ -20,6 +20,7 @@ serde1 = ["serde", "bstr/serde1"] bstr = { version = "0.2.13", default-features = false, features = ["std"]} serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} itoa = "1.0.1" +time = { version = "0.3.2", default-features = false, features = ["local-offset"] } document-features = { version = "0.2.0", optional = true } diff --git a/git-date/src/time.rs b/git-date/src/time.rs index 1fa1f77881d..c7b711e13b6 100644 --- a/git-date/src/time.rs +++ b/git-date/src/time.rs @@ -1,4 +1,4 @@ -use std::io; +use std::{convert::TryInto, io, ops::Sub}; use crate::Time; @@ -31,6 +31,7 @@ impl Default for Time { } } +/// Instantiation impl Time { /// Create a new instance from seconds and offset. pub fn new(seconds_since_unix_epoch: u32, offset_in_seconds: i32) -> Self { @@ -41,6 +42,66 @@ impl Time { } } + /// Return the current time without figuring out a timezone offset + pub fn now_utc() -> Self { + let seconds_since_unix_epoch = time::OffsetDateTime::now_utc() + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + Self { + seconds_since_unix_epoch, + offset_in_seconds: 0, + sign: Sign::Plus, + } + } + + /// Return the current local time, or `None` if the local time wasn't available. + pub fn now_local() -> Option { + let now = time::OffsetDateTime::now_utc(); + let seconds_since_unix_epoch = now + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + // TODO: make this work without cfg(unsound_local_offset), see + // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + let offset = time::UtcOffset::local_offset_at(now).ok()?; + Self { + seconds_since_unix_epoch, + offset_in_seconds: offset.whole_seconds(), + sign: Sign::Plus, + } + .into() + } + + /// Return the current local time, or the one at UTC if the local time wasn't available. + pub fn now_local_or_utc() -> Self { + let now = time::OffsetDateTime::now_utc(); + let seconds_since_unix_epoch = now + .sub(std::time::SystemTime::UNIX_EPOCH) + .whole_seconds() + .try_into() + .expect("this is not year 2038"); + // TODO: make this work without cfg(unsound_local_offset), see + // https://github.com/time-rs/time/issues/293#issuecomment-909158529 + let offset_in_seconds = time::UtcOffset::local_offset_at(now) + .map(|ofs| ofs.whole_seconds()) + .unwrap_or(0); + Self { + seconds_since_unix_epoch, + offset_in_seconds, + sign: Sign::Plus, + } + } +} + +impl Time { + /// Return true if this time has been initialized to anything non-default, i.e. 0. + pub fn is_set(&self) -> bool { + *self != Self::default() + } + /// Return the passed seconds since epoch since this signature was made. pub fn seconds(&self) -> u32 { self.seconds_since_unix_epoch @@ -74,7 +135,8 @@ impl Time { } out.write_all(itoa.format(minutes).as_bytes()).map(|_| ()) } - /// Computes the number of bytes necessary to render this time + + /// Computes the number of bytes necessary to render this time. pub fn size(&self) -> usize { // TODO: this is not year 2038 safe…but we also can't parse larger numbers (or represent them) anyway. It's a trap nonetheless // that can be fixed by increasing the size to usize. diff --git a/git-date/tests/time/mod.rs b/git-date/tests/time/mod.rs index 72e5b7ea4a2..cc694654b61 100644 --- a/git-date/tests/time/mod.rs +++ b/git-date/tests/time/mod.rs @@ -1,6 +1,16 @@ use bstr::ByteSlice; use git_date::{time::Sign, Time}; +#[test] +fn is_set() { + assert!(!Time::default().is_set()); + assert!(Time { + seconds_since_unix_epoch: 1, + ..Default::default() + } + .is_set()); +} + #[test] fn write_to() -> Result<(), Box> { for (time, expected) in &[ diff --git a/git-diff/CHANGELOG.md b/git-diff/CHANGELOG.md index 413e125595e..d4a86f3b239 100644 --- a/git-diff/CHANGELOG.md +++ b/git-diff/CHANGELOG.md @@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.17.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 30 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) +
+ ## 0.16.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +40,7 @@ A maintenance release without user-facing changes. - - 5 commits contributed to the release over the course of 34 calendar days. + - 6 commits contributed to the release over the course of 34 calendar days. - 43 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -31,6 +58,8 @@ A maintenance release without user-facing changes. - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) + * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) ## 0.15.0 (2022-04-05) @@ -73,7 +102,7 @@ A maintenance release primarily to adapt to dependent crates. - - 8 commits contributed to the release over the course of 68 calendar days. + - 7 commits contributed to the release over the course of 68 calendar days. - 69 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#364](https://github.com/Byron/gitoxide/issues/364) @@ -92,7 +121,6 @@ A maintenance release primarily to adapt to dependent crates. - Merge branch 'for-onefetch' ([`8e5cb65`](https://github.com/Byron/gitoxide/commit/8e5cb65da75036a13ed469334e7ae6c527d9fff6)) - Release git-hash v0.9.3, git-features v0.20.0, git-config v0.2.0, safety bump 12 crates ([`f0cbb24`](https://github.com/Byron/gitoxide/commit/f0cbb24b2e3d8f028be0e773f9da530da2656257)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) @@ -114,7 +142,7 @@ A maintenance release primarily to adapt to dependent crates. - - 10 commits contributed to the release over the course of 51 calendar days. + - 11 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#266](https://github.com/Byron/gitoxide/issues/266), [#279](https://github.com/Byron/gitoxide/issues/279) @@ -143,6 +171,7 @@ A maintenance release primarily to adapt to dependent crates. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - thanks clippy ([`7dd2313`](https://github.com/Byron/gitoxide/commit/7dd2313d980fe7c058319ae66d313b3097e3ae5f)) diff --git a/git-diff/Cargo.toml b/git-diff/Cargo.toml index 0a3b489e686..be52d6aa112 100644 --- a/git-diff/Cargo.toml +++ b/git-diff/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-diff" -version = "0.16.0" +version = "0.17.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "Calculate differences between various git objects" @@ -14,8 +14,8 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.4", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" [dev-dependencies] diff --git a/git-diff/src/tree/visit.rs b/git-diff/src/tree/visit.rs index ef791d7aaf0..eee7803b3ff 100644 --- a/git-diff/src/tree/visit.rs +++ b/git-diff/src/tree/visit.rs @@ -37,7 +37,7 @@ pub enum Change { pub enum Action { /// Continue the traversal of changes. Continue, - /// Stop the traversal of changes, making this te last call to [visit(…)][Visit::visit()]. + /// Stop the traversal of changes, making this the last call to [visit(…)][Visit::visit()]. Cancel, } diff --git a/git-discover/CHANGELOG.md b/git-discover/CHANGELOG.md index d7c5338fc1e..9b96e4e9fb4 100644 --- a/git-discover/CHANGELOG.md +++ b/git-discover/CHANGELOG.md @@ -5,6 +5,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +### New Features + + - add `DOT_GIT_DIR` constant, containing the name ".git". + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - add `DOT_GIT_DIR` constant, containing the name ".git". ([`0103501`](https://github.com/Byron/gitoxide/commit/010350180459aec41132c960ddafc7b81dd9c04d)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Remove another special case on windows due to canonicalize() ([`61abb0b`](https://github.com/Byron/gitoxide/commit/61abb0b006292d2122784b032e198cc716fb7b92)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) @@ -22,7 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 12 commits contributed to the release over the course of 16 calendar days. + - 13 commits contributed to the release over the course of 16 calendar days. - 16 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -34,6 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) - refactor ([`ec37cb8`](https://github.com/Byron/gitoxide/commit/ec37cb8005fa272aed2e23e65adc291875b1fd68)) diff --git a/git-discover/Cargo.toml b/git-discover/Cargo.toml index a4a85aa2f4f..744b6e52f45 100644 --- a/git-discover/Cargo.toml +++ b/git-discover/Cargo.toml @@ -15,9 +15,9 @@ doctest = false [dependencies] git-sec = { version = "^0.3.0", path = "../git-sec", features = ["thiserror"] } -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } git-ref = { version = "^0.15.0", path = "../git-ref" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"] } thiserror = "1.0.26" diff --git a/git-discover/src/is.rs b/git-discover/src/is.rs index 1dd221674c7..7951d660606 100644 --- a/git-discover/src/is.rs +++ b/git-discover/src/is.rs @@ -1,6 +1,7 @@ -use crate::DOT_GIT_DIR; use std::{borrow::Cow, ffi::OsStr, path::Path}; +use crate::DOT_GIT_DIR; + /// Returns true if the given `git_dir` seems to be a bare repository. /// /// Please note that repositories without an index generally _look_ bare, even though they might also be uninitialized. diff --git a/git-discover/src/path.rs b/git-discover/src/path.rs index 8c882185b3a..6269d68cc32 100644 --- a/git-discover/src/path.rs +++ b/git-discover/src/path.rs @@ -1,6 +1,7 @@ -use crate::DOT_GIT_DIR; use std::{io::Read, path::PathBuf}; +use crate::DOT_GIT_DIR; + /// pub mod from_gitdir_file { /// The error returned by [`from_gitdir_file()`][crate::path::from_gitdir_file()]. diff --git a/git-features/CHANGELOG.md b/git-features/CHANGELOG.md index 2ef78770a66..7c94a266a43 100644 --- a/git-features/CHANGELOG.md +++ b/git-features/CHANGELOG.md @@ -5,6 +5,51 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.22.0 (2022-07-22) + +### New Features + + - initialize `Time` from `now_utc` and `now_local` + Localtime support depends on some other factors now, but that + will only get better over time. + + We might have to document `unsound_local_time` at some point. + +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 17 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - initialize `Time` from `now_utc` and `now_local` ([`c76fde7`](https://github.com/Byron/gitoxide/commit/c76fde7de278b49ded13b655d5345e4eb8c1b134)) + - a first sketch on how identity management could look like. ([`780f14f`](https://github.com/Byron/gitoxide/commit/780f14f5c270802e51cf039639c2fbdb5ac5a85e)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - git-features' walkdir: 2.3.1 -> 2.3.2 ([`41dd754`](https://github.com/Byron/gitoxide/commit/41dd7545234e6d2637d2bca5bb6d4f6d8bfc8f57)) +
+ ## 0.21.1 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +58,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 6 calendar days. + - 3 commits contributed to the release over the course of 6 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +70,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Assure we used most recent version of crossbeam-utils ([`033f0d3`](https://github.com/Byron/gitoxide/commit/033f0d3e0015b7eead6408c775d2101eb413ffbf))
diff --git a/git-features/Cargo.toml b/git-features/Cargo.toml index 778cfcfb5df..8438d2e257d 100644 --- a/git-features/Cargo.toml +++ b/git-features/Cargo.toml @@ -2,7 +2,7 @@ name = "git-features" description = "A crate to integrate various capabilities using compile-time feature flags" repository = "https://github.com/Byron/gitoxide" -version = "0.21.1" +version = "0.22.0" authors = ["Sebastian Thiel "] license = "MIT/Apache-2.0" edition = "2018" @@ -84,7 +84,7 @@ required-features = ["io-pipe"] [dependencies] #! ### Optional Dependencies -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } @@ -114,8 +114,8 @@ bytes = { version = "1.0.0", optional = true } flate2 = { version = "1.0.17", optional = true, default-features = false } quick-error = { version = "2.0.0", optional = true } -## make the `time` module available with access to the local time as configured by the system. -time = { version = "0.3.2", optional = true, default-features = false, features = ["local-offset"] } +## If enabled, OnceCell will be made available for interior mutability either in sync or unsync forms. +once_cell = { version = "1.13.0", optional = true } document-features = { version = "0.2.0", optional = true } diff --git a/git-features/src/lib.rs b/git-features/src/lib.rs index f8af9589ddd..2d2b55f084a 100644 --- a/git-features/src/lib.rs +++ b/git-features/src/lib.rs @@ -31,10 +31,6 @@ pub mod threading; #[cfg(feature = "zlib")] pub mod zlib; -/// -#[cfg(feature = "time")] -pub mod time; - /// pub mod iter { /// An iterator over chunks of input, producing `Vec` with a size of `size`, with the last chunk being the remainder and thus diff --git a/git-features/src/parallel/in_parallel.rs b/git-features/src/parallel/in_parallel.rs index 52ad15545f9..50df0d1f4ce 100644 --- a/git-features/src/parallel/in_parallel.rs +++ b/git-features/src/parallel/in_parallel.rs @@ -16,7 +16,7 @@ pub fn join(left: impl FnOnce() -> O1 + Send, right: impl Fn /// That way it's possible to handle threads without needing the 'static lifetime for data they interact with. /// /// Note that the threads should not rely on actual parallelism as threading might be turned off entirely, hence should not -/// connect each other with channels as deadlock would occour in single-threaded mode. +/// connect each other with channels as deadlock would occur in single-threaded mode. pub fn threads<'env, F, R>(f: F) -> std::thread::Result where F: FnOnce(&crossbeam_utils::thread::Scope<'env>) -> R, @@ -85,7 +85,7 @@ where } /// An experiment to have fine-grained per-item parallelization with built-in aggregation via thread state. -/// This is only good for operations where near-random access isn't detremental, so it's not usually great +/// This is only good for operations where near-random access isn't detrimental, so it's not usually great /// for file-io as it won't make use of sorted inputs well. /// Note that `periodic` is not guaranteed to be called in case other threads come up first and finish too fast. // TODO: better docs diff --git a/git-features/src/parallel/serial.rs b/git-features/src/parallel/serial.rs index da61709bb3c..c0bf564b82d 100644 --- a/git-features/src/parallel/serial.rs +++ b/git-features/src/parallel/serial.rs @@ -57,7 +57,7 @@ mod not_parallel { } /// An experiment to have fine-grained per-item parallelization with built-in aggregation via thread state. - /// This is only good for operations where near-random access isn't detremental, so it's not usually great + /// This is only good for operations where near-random access isn't detrimental, so it's not usually great /// for file-io as it won't make use of sorted inputs well. // TODO: better docs pub fn in_parallel_with_slice( diff --git a/git-features/src/threading.rs b/git-features/src/threading.rs index b262454b308..ff0c819a5e5 100644 --- a/git-features/src/threading.rs +++ b/git-features/src/threading.rs @@ -6,6 +6,9 @@ mod _impl { use std::sync::Arc; + /// A thread-safe cell which can be written to only once. + #[cfg(feature = "once_cell")] + pub type OnceCell = once_cell::sync::OnceCell; /// A reference counted pointer type for shared ownership. pub type OwnShared = Arc; /// A synchronization primitive which can start read-only and transition to support mutation. @@ -53,6 +56,9 @@ mod _impl { rc::Rc, }; + /// A thread-safe cell which can be written to only once. + #[cfg(feature = "once_cell")] + pub type OnceCell = once_cell::unsync::OnceCell; /// A reference counted pointer type for shared ownership. pub type OwnShared = Rc; /// A synchronization primitive which can start read-only and transition to support mutation. diff --git a/git-features/src/time.rs b/git-features/src/time.rs deleted file mode 100644 index e0a6b5b712e..00000000000 --- a/git-features/src/time.rs +++ /dev/null @@ -1,35 +0,0 @@ -/// -pub mod tz { - mod error { - use std::fmt; - - /// The error returned by [`current_utc_offset()`][super::current_utc_offset()] - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub struct Error; - - impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("The system's UTC offset could not be determined") - } - } - - impl std::error::Error for Error {} - } - pub use error::Error; - - /// The UTC offset in seconds - pub type UTCOffsetInSeconds = i32; - - /// Return time offset in seconds from UTC based on the current timezone. - /// - /// Note that there may be various legitimate reasons for failure, which should be accounted for. - pub fn current_utc_offset() -> Result { - // TODO: make this work without cfg(unsound_local_offset), see - // https://github.com/time-rs/time/issues/293#issuecomment-909158529 - // TODO: get a function to return the current time as well to avoid double-lookups - // (to get the offset, the current time is needed) - time::UtcOffset::current_local_offset() - .map(|ofs| ofs.whole_seconds()) - .map_err(|_| Error) - } -} diff --git a/git-glob/CHANGELOG.md b/git-glob/CHANGELOG.md index 3bf45a8f3f1..f99b3508fba 100644 --- a/git-glob/CHANGELOG.md +++ b/git-glob/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.1 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 12 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) +
+ ## 0.3.0 (2022-05-18) ### New Features @@ -12,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `fmt::Display` impl for `Pattern`. This way the original pattern can be reproduced on the fly without actually storing it, saving one allocation. + - add `Default` impl for `pattern::Case` ### Changed (BREAKING) @@ -23,14 +48,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Even though it's convenient to have a base path per pattern, it's quite some duplication. + - `Pattern::matches()` is now private + It doesn't work as one would expect due to it wanting to match relative + paths only. Thus it's better to spare folks the surprise and instead + use `wildmatch()` directly. It works the same, but doesn't + have certain shortcuts which aren't needed for standard matches + anyway. ### Commit Statistics - - 17 commits contributed to the release over the course of 35 calendar days. + - 31 commits contributed to the release over the course of 35 calendar days. - 35 days passed between releases. - - 3 commits where understood as [conventional](https://www.conventionalcommits.org). + - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) ### Thanks Clippy @@ -47,23 +78,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) + - `fmt::Display` impl for `Pattern`. ([`455a72e`](https://github.com/Byron/gitoxide/commit/455a72eb0c01c158f43d9b9a1180886f677bad00)) + - adapt to changes in git-path ([`cc2d810`](https://github.com/Byron/gitoxide/commit/cc2d81012d107da7a61bf4de5b28342dea5083b7)) + - add `Default` impl for `pattern::Case` ([`2c88b57`](https://github.com/Byron/gitoxide/commit/2c88b575630e1b179955dad578e779aad8dd58d8)) + - cleanup ([`1ab4705`](https://github.com/Byron/gitoxide/commit/1ab470589450ecda45826c38417616f227e3031b)) + - Allow basename matches to work like before ([`4f6cefc`](https://github.com/Byron/gitoxide/commit/4f6cefc96bea5f116eb26a9de8095271fd0f58e2)) + - adjust baseline to only handle patterns that work without a dir stack ([`fb65a39`](https://github.com/Byron/gitoxide/commit/fb65a39e1826c331545b7141c0741904ed5bb1a4)) + - discover an entirely new class of exclude matches… ([`f8dd5ce`](https://github.com/Byron/gitoxide/commit/f8dd5ce8ce27cd24b9d81795dcf01ce03efe802d)) + - Basic match group pattern matching ([`cc1312d`](https://github.com/Byron/gitoxide/commit/cc1312dc06d1dccfa2e3cf0ae134affa9a3fa947)) + - `Pattern::matches()` is now private ([`568f013`](https://github.com/Byron/gitoxide/commit/568f013e762423fc54a8fb1daed1e7b59c1dc0f0)) - push base path handling to the caller ([`e4b57b1`](https://github.com/Byron/gitoxide/commit/e4b57b197884bc981b8e3c9ee8c7b5349afa594b)) - A slightly ugly way of not adjusting input patterns too much ([`3912ee6`](https://github.com/Byron/gitoxide/commit/3912ee66b6117681331df5e6e0f8345335728bde)) - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) - a failing test to show that the absolute pattern handling isn't quite there yet ([`74c89eb`](https://github.com/Byron/gitoxide/commit/74c89ebbd235e8f5464e0665cc7bc7a930a8eb76)) + - remove `base_path` field from `Pattern` ([`f76a426`](https://github.com/Byron/gitoxide/commit/f76a426833530c7a7e787487cfceaba2c80b21ac)) + - make fmt ([`5fc5459`](https://github.com/Byron/gitoxide/commit/5fc5459b17b623726f99846c432a70106464e970)) - cleanup tests ([`16570ef`](https://github.com/Byron/gitoxide/commit/16570ef96785c62eb813d4613df097aca3aa0d8f)) - case-insensitive tests for baseline path matching ([`bc928f9`](https://github.com/Byron/gitoxide/commit/bc928f9c00b5f00527a122c8bf847278e90ffb04)) - invert meaning of `wildcard::Mode::SLASH_IS_LITERAL` ([`8fd9f24`](https://github.com/Byron/gitoxide/commit/8fd9f24e2f751292a99b4f92cc47df67e17ab537)) - - `fmt::Display` impl for `Pattern`. ([`455a72e`](https://github.com/Byron/gitoxide/commit/455a72eb0c01c158f43d9b9a1180886f677bad00)) - - remove `base_path` field from `Pattern` ([`f76a426`](https://github.com/Byron/gitoxide/commit/f76a426833530c7a7e787487cfceaba2c80b21ac)) - make glob tests work on windows for now… ([`29738ed`](https://github.com/Byron/gitoxide/commit/29738edc56da6dbb9b853ac8f7482672eafd5050)) - See if being less pedantic yields the correct results ([`18953e4`](https://github.com/Byron/gitoxide/commit/18953e4c367ef1d3c2b28a0b027acc715af6372f)) * **[#384](https://github.com/Byron/gitoxide/issues/384)** - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) + - make sure existing files aren't written into ([`9b5a8a2`](https://github.com/Byron/gitoxide/commit/9b5a8a243d49b6567d1db31050d3bf3123dd54d3)) + - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) - make fmt ([`251b6df`](https://github.com/Byron/gitoxide/commit/251b6df5dbdda24b7bdc452085f808f3acef69d8)) + - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) - thanks clippy ([`5bf6b52`](https://github.com/Byron/gitoxide/commit/5bf6b52cd51bef19079e87230e5ac463f8f881c0)) + - Merge branch 'main' into worktree-stack ([`8674c11`](https://github.com/Byron/gitoxide/commit/8674c11973e5282d087e35a71c70e418b6cc75be)) - thanks clippy ([`74f6420`](https://github.com/Byron/gitoxide/commit/74f64202dfc6d9b34228595e260014708ec388e3))
@@ -80,10 +125,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 55 commits contributed to the release over the course of 6 calendar days. + - 51 commits contributed to the release over the course of 6 calendar days. - 6 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) + - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Thanks Clippy @@ -99,7 +144,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#301](https://github.com/Byron/gitoxide/issues/301)** - `parse()` returns a `Pattern`. ([`6ce3611`](https://github.com/Byron/gitoxide/commit/6ce3611891d4b60c86055bf749a1b4060ee2c3e1)) - - make fmt ([`5fc5459`](https://github.com/Byron/gitoxide/commit/5fc5459b17b623726f99846c432a70106464e970)) - docs for git-glob ([`8f4969f`](https://github.com/Byron/gitoxide/commit/8f4969fe7c2e3f3bb38275d5e4ccb08d0bde02bb)) - all wildmatch tests succeed ([`d3a7349`](https://github.com/Byron/gitoxide/commit/d3a7349b707911670f17a92a0f82681544ebc769)) - add all character classes sans some of the more obscure ones ([`538d41d`](https://github.com/Byron/gitoxide/commit/538d41d51d7cdc472b2a712823a5a69810f75015)) @@ -122,7 +166,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - question mark support ([`e83c8df`](https://github.com/Byron/gitoxide/commit/e83c8df03e801e00571f5934331e004af9774c7f)) - very basic beginnings of wildmatch ([`334c624`](https://github.com/Byron/gitoxide/commit/334c62459dbb6763a46647a64129f89e27b5781b)) - fix logic in wildmatch tests; validate feasibility of all test cases ([`1336bc9`](https://github.com/Byron/gitoxide/commit/1336bc938cc43e3a2f9e47af64f2c9933c9fc961)) - - adapt to changes in git-path ([`cc2d810`](https://github.com/Byron/gitoxide/commit/cc2d81012d107da7a61bf4de5b28342dea5083b7)) - test corpus for wildcard matches ([`bd8f95f`](https://github.com/Byron/gitoxide/commit/bd8f95f757e45b3cf8523d3e11503f4571461abf)) - frame for wildmatch function and its tests ([`04ca834`](https://github.com/Byron/gitoxide/commit/04ca8349e326f7b7505a9ea49a401565259f21dc)) - more tests for early exit in case no-wildcard prefix doesn't match ([`1ff348c`](https://github.com/Byron/gitoxide/commit/1ff348c4f09839569dcd8bb93699e7004fa59d4a)) @@ -147,9 +190,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - bring in all ~140 tests for git pattern matching, git-ignore styile ([`f9ab830`](https://github.com/Byron/gitoxide/commit/f9ab830df2920387c1cffec048be3a4089f4aa40)) - refactor ([`dbe7305`](https://github.com/Byron/gitoxide/commit/dbe7305d371c7dad02d8888492b60b882b418a46)) - refactor ([`8a54341`](https://github.com/Byron/gitoxide/commit/8a543410e10326ce506b8a7ba65e662641835849)) - * **[#384](https://github.com/Byron/gitoxide/issues/384)** - - make sure existing files aren't written into ([`9b5a8a2`](https://github.com/Byron/gitoxide/commit/9b5a8a243d49b6567d1db31050d3bf3123dd54d3)) - - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** - Release git-glob v0.2.0, safety bump 3 crates ([`ab6bed7`](https://github.com/Byron/gitoxide/commit/ab6bed7e2aa19eeb9990441741008c430f373708)) - thanks clippy ([`b1a6100`](https://github.com/Byron/gitoxide/commit/b1a610029e1b40600f90194ce986155238f58101)) @@ -178,8 +218,8 @@ Initial release with pattern parsing functionality. - - 11 commits contributed to the release. - - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 commits contributed to the release. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) ### Commit Details @@ -191,15 +231,7 @@ Initial release with pattern parsing functionality. * **[#301](https://github.com/Byron/gitoxide/issues/301)** - prepare changelog prior to release ([`2794bb2`](https://github.com/Byron/gitoxide/commit/2794bb2f6bd80cccba508fa9f251609499167646)) - Add git-glob crate with pattern matching parsing from git-attributes::ignore ([`b3efc94`](https://github.com/Byron/gitoxide/commit/b3efc94134a32018db1d6a2d7f8cc397c4371999)) - - add `Default` impl for `pattern::Case` ([`2c88b57`](https://github.com/Byron/gitoxide/commit/2c88b575630e1b179955dad578e779aad8dd58d8)) - - cleanup ([`1ab4705`](https://github.com/Byron/gitoxide/commit/1ab470589450ecda45826c38417616f227e3031b)) - - Allow basename matches to work like before ([`4f6cefc`](https://github.com/Byron/gitoxide/commit/4f6cefc96bea5f116eb26a9de8095271fd0f58e2)) - - adjust baseline to only handle patterns that work without a dir stack ([`fb65a39`](https://github.com/Byron/gitoxide/commit/fb65a39e1826c331545b7141c0741904ed5bb1a4)) - - discover an entirely new class of exclude matches… ([`f8dd5ce`](https://github.com/Byron/gitoxide/commit/f8dd5ce8ce27cd24b9d81795dcf01ce03efe802d)) - - Basic match group pattern matching ([`cc1312d`](https://github.com/Byron/gitoxide/commit/cc1312dc06d1dccfa2e3cf0ae134affa9a3fa947)) - - `Pattern::matches()` is now private ([`568f013`](https://github.com/Byron/gitoxide/commit/568f013e762423fc54a8fb1daed1e7b59c1dc0f0)) * **Uncategorized** - Release git-glob v0.1.0 ([`0f66c5d`](https://github.com/Byron/gitoxide/commit/0f66c5d56bd3f0febff881065911638f22e71158)) - - Merge branch 'main' into worktree-stack ([`8674c11`](https://github.com/Byron/gitoxide/commit/8674c11973e5282d087e35a71c70e418b6cc75be)) diff --git a/git-glob/Cargo.toml b/git-glob/Cargo.toml index 32459266332..f4aba3a7d3c 100644 --- a/git-glob/Cargo.toml +++ b/git-glob/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-glob" -version = "0.3.0" +version = "0.3.1" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing with pattern matching" diff --git a/git-hash/CHANGELOG.md b/git-hash/CHANGELOG.md index af33a0dec45..e1eac4bd7bd 100644 --- a/git-hash/CHANGELOG.md +++ b/git-hash/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.9.6 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release over the course of 12 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) +
+ ## 0.9.5 (2022-06-13) ### New Features @@ -17,7 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 2 commits contributed to the release over the course of 5 calendar days. + - 3 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -31,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#427](https://github.com/Byron/gitoxide/issues/427)** - expose `Prefix::MIN_HEX_LEN`. ([`652f228`](https://github.com/Byron/gitoxide/commit/652f228bb7ec880856d4e6ee1c171b0b85a735e2)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) diff --git a/git-hash/Cargo.toml b/git-hash/Cargo.toml index a50ca1542f6..23fdaea0a55 100644 --- a/git-hash/Cargo.toml +++ b/git-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-hash" -version = "0.9.5" +version = "0.9.6" description = "Borrowed and owned git hash digests used to identify git objects" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" diff --git a/git-index/CHANGELOG.md b/git-index/CHANGELOG.md index 597fbc2ff85..4d3da1c26eb 100644 --- a/git-index/CHANGELOG.md +++ b/git-index/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.3.0 (2022-05-18) ### New Features @@ -21,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 15 commits contributed to the release over the course of 23 calendar days. + - 17 commits contributed to the release over the course of 34 calendar days. - 45 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -36,13 +60,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - upgrade git-index->atoi to 1.0 ([`728dd65`](https://github.com/Byron/gitoxide/commit/728dd6501b86b12e1d0237256f94059a7ead14a9)) * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) - - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) + - Differentiate between owned and ref'ed path storage ([`c71b2bb`](https://github.com/Byron/gitoxide/commit/c71b2bb944c3066e7e44fbdd8a2e511a5a5d944a)) - `State::path_backing()`. ([`8ab219a`](https://github.com/Byron/gitoxide/commit/8ab219ac47ca67f2478b8715d7820fd6171c0db2)) - sketch `open_index()` on `Worktree`, but… ([`ff76261`](https://github.com/Byron/gitoxide/commit/ff76261f568f6b717a93b1f2dcf5d8e8b63acfca)) - - Differentiate between owned and ref'ed path storage ([`c71b2bb`](https://github.com/Byron/gitoxide/commit/c71b2bb944c3066e7e44fbdd8a2e511a5a5d944a)) - support for separating lifetimes of entries and path-backing ([`645ed50`](https://github.com/Byron/gitoxide/commit/645ed50dc2ae5ded2df0c09daf4fe366b90cf47e)) - An attempt to build a lookup table of attribute files, but… ([`9841efb`](https://github.com/Byron/gitoxide/commit/9841efb566748dae6c79c5990c4fd1ecbc468aef)) - refactor ([`475aa6a`](https://github.com/Byron/gitoxide/commit/475aa6a3e08f63df627a0988cd16c20494960c79)) + - Adjustments to support lower MSRV ([`16a0973`](https://github.com/Byron/gitoxide/commit/16a09737f0e81654cc7a5bbc9043385528524ca5)) * **[#384](https://github.com/Byron/gitoxide/issues/384)** - prevent line-ending conversions for shell scripts on windows ([`96bb4d4`](https://github.com/Byron/gitoxide/commit/96bb4d460db420e18dfd0f925109c740e971820d)) - No need to isolate archives by crate name ([`19d46f3`](https://github.com/Byron/gitoxide/commit/19d46f35440419b9911b6e2bca2cfc975865dce9)) @@ -50,7 +74,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Assure we don't pick up unnecessary files during publishing ([`545b2d5`](https://github.com/Byron/gitoxide/commit/545b2d5121ba64efaee7564d5191cec37661efd7)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) + - Merge branch 'git_includeif' of https://github.com/svetli-n/gitoxide into svetli-n-git_includeif ([`0e01da7`](https://github.com/Byron/gitoxide/commit/0e01da74dffedaa46190db6a7b60a2aaff190d81)) ## 0.2.0 (2022-04-03) @@ -67,7 +93,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 17 commits contributed to the release over the course of 73 calendar days. + - 43 commits contributed to the release over the course of 73 calendar days. - 73 days passed between releases. - 2 commits where understood as [conventional](https://www.conventionalcommits.org). - 5 unique issues were worked on: [#293](https://github.com/Byron/gitoxide/issues/293), [#298](https://github.com/Byron/gitoxide/issues/298), [#301](https://github.com/Byron/gitoxide/issues/301), [#329](https://github.com/Byron/gitoxide/issues/329), [#333](https://github.com/Byron/gitoxide/issues/333) @@ -80,10 +106,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **[#293](https://github.com/Byron/gitoxide/issues/293)** - Remove performance bottleneck when reading TREE extension ([`50411c8`](https://github.com/Byron/gitoxide/commit/50411c8031e3103bb84da46b94b8faf92c597df9)) + - Assert store tree cache matches actual source objects ([`b062bec`](https://github.com/Byron/gitoxide/commit/b062becd01058f5c519538f89d9d8fec8342114d)) + - Sketch a surprisingly difficult way of loading objects in verify_extension() ([`3baeab4`](https://github.com/Byron/gitoxide/commit/3baeab4ab216132536d5c182b3e316ce65095085)) + - Properly sort cache tree entries upon load ([`421d1ba`](https://github.com/Byron/gitoxide/commit/421d1ba853a75560f59cb0ce2b353087db0dff56)) + - tree-ordering validation shows something wrong ([`5fb2857`](https://github.com/Byron/gitoxide/commit/5fb2857e9f970c150f5221ca721506e7bc046ef4)) + - First stab at tree verification ([`f928350`](https://github.com/Byron/gitoxide/commit/f9283500e8316ab949fc0ff9c2fc13a498380873)) + - Verify entry order ([`2d101eb`](https://github.com/Byron/gitoxide/commit/2d101ebbd36e000ffec0e965012081fec2e234f7)) + - refactor ([`017e915`](https://github.com/Byron/gitoxide/commit/017e9153aaaa1cdd6788d9f61ff1ffbd61bc1b30)) + - basic index file checksum verification ([`c644565`](https://github.com/Byron/gitoxide/commit/c644565d5b8d9ae3991ee82a7ffa5e21a2705f91)) + - At least check for the presence of extensions ([`28c056c`](https://github.com/Byron/gitoxide/commit/28c056c6d2bbfb16a826238fd6879adecbeb1171)) + - thorough checking of Tree extension ([`d1063aa`](https://github.com/Byron/gitoxide/commit/d1063aa20bfcefb064ba08089f095baef1299dcd)) + - refactor ([`d0725bd`](https://github.com/Byron/gitoxide/commit/d0725bd40f0b9af0e0af34ffe77c2d8406c6d24c)) + - Fix tree-extension loading for empty trees ([`2e13989`](https://github.com/Byron/gitoxide/commit/2e1398985edfaf9e62ff5643cf4756d9d9717862)) + - Now we are able to load indices correctly ([`762efa3`](https://github.com/Byron/gitoxide/commit/762efa3f5e4ebda4d3bcc6a9bba43c6cdb407937)) + - Add breaking test with conflicting file in index ([`791a9f8`](https://github.com/Byron/gitoxide/commit/791a9f84ff8871c7beb0e2100a4dcba0e9384737)) + - Print extension names instead of count ([`1cc07e0`](https://github.com/Byron/gitoxide/commit/1cc07e0cfdae74e388abb29d7acb9c6f643278b4)) + - Print basic index information, including the tree extension ([`9277cf8`](https://github.com/Byron/gitoxide/commit/9277cf877e1f2276dcad1efdeb97e0e3d96ed3f0)) - lower rust edition to 2018 ([`0b1543d`](https://github.com/Byron/gitoxide/commit/0b1543d481337ed51dcfdeb907af21f0bc98dcb9)) - lower MSRV to 1.52 ([`c2cc939`](https://github.com/Byron/gitoxide/commit/c2cc939d131a278c177c5f44d3b26127c65bd352)) * **[#298](https://github.com/Byron/gitoxide/issues/298)** - Use hash_hasher based hash state for better keys/less collisions ([`814de07`](https://github.com/Byron/gitoxide/commit/814de079f4226f42efa49ad334a348bce67184e4)) + - Also print stage of entries ([`003515f`](https://github.com/Byron/gitoxide/commit/003515f3c90a49fbe9db9b84987233486711beb8)) + - simple printing of basic entry information ([`329538b`](https://github.com/Byron/gitoxide/commit/329538b9c3f44bb8e70a4567ba90dc3b594c2dfc)) * **[#301](https://github.com/Byron/gitoxide/issues/301)** - basic version of index checkout via command-line ([`f23b8d2`](https://github.com/Byron/gitoxide/commit/f23b8d2f1c4b767d337ec51888afaa8b3719798c)) - document-features support for git-index and git-worktree ([`1367cf5`](https://github.com/Byron/gitoxide/commit/1367cf5bc5908639e67e12f78f57835c5fd68a90)) @@ -101,6 +145,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Merge branch 'short-id' ([`5849d5b`](https://github.com/Byron/gitoxide/commit/5849d5b326b83f98a16cf1d956c720c7f0fd4445)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - Implemented git-worktree ([`4177d72`](https://github.com/Byron/gitoxide/commit/4177d72c95bd94cf6a49e917dc21918044e8250b)) + - Release git-hash v0.9.2, git-object v0.17.1, git-pack v0.16.1 ([`0db19b8`](https://github.com/Byron/gitoxide/commit/0db19b8deaf11a4d4cbc03fa3ae40eea104bc302)) + - refactor ([`afdeca1`](https://github.com/Byron/gitoxide/commit/afdeca1e5ec119607c5d1f5ccec5d216fc7d5261)) + - thanks clippy ([`2f25bf1`](https://github.com/Byron/gitoxide/commit/2f25bf1ebf44aef8c4886eaefb3e87836d535f61)) + - thanks clippy ([`d721019`](https://github.com/Byron/gitoxide/commit/d721019aebe5b01ddb15c9b1aab279647069452a)) + - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) + - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) + - upgrade to tui 0.17 and prodash 18 ([`eba101a`](https://github.com/Byron/gitoxide/commit/eba101a576ecb7bc0f63357d0dd81eb817b94be4)) + - dependency update ([`ca59e44`](https://github.com/Byron/gitoxide/commit/ca59e448061698dd559db43123fe676ae61129a0)) ## 0.1.0 (2022-01-19) @@ -113,16 +165,16 @@ certain extensions are present. - - 98 commits contributed to the release over the course of 490 calendar days. + - 72 commits contributed to the release over the course of 490 calendar days. - 509 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - - 2 unique issues were worked on: [#293](https://github.com/Byron/gitoxide/issues/293), [#298](https://github.com/Byron/gitoxide/issues/298) + - 1 unique issue was worked on: [#293](https://github.com/Byron/gitoxide/issues/293) ### Thanks Clippy -[Clippy](https://github.com/rust-lang/rust-clippy) helped 7 times to make code idiomatic. +[Clippy](https://github.com/rust-lang/rust-clippy) helped 5 times to make code idiomatic. ### Commit Details @@ -139,34 +191,18 @@ certain extensions are present. - FSMN V2 decoding ([`04279bf`](https://github.com/Byron/gitoxide/commit/04279bffc8bd43abe85f559634436be789782829)) - Failing test for fs-monitor V1 ([`625b89a`](https://github.com/Byron/gitoxide/commit/625b89a7786fe9de29c9ad2ca41a734174f53128)) - Validate UNTR with exclude-file oids ([`20ebb81`](https://github.com/Byron/gitoxide/commit/20ebb81c9ece6c2d693edd6243eaa730fa7cf44c)) - - Assert store tree cache matches actual source objects ([`b062bec`](https://github.com/Byron/gitoxide/commit/b062becd01058f5c519538f89d9d8fec8342114d)) - read remaining pieces of UNTR ([`9d9cc95`](https://github.com/Byron/gitoxide/commit/9d9cc95a24d86cf5f66f1746c09ece032640a892)) - - Sketch a surprisingly difficult way of loading objects in verify_extension() ([`3baeab4`](https://github.com/Byron/gitoxide/commit/3baeab4ab216132536d5c182b3e316ce65095085)) - Make stat parsing more general/reusable ([`c41b933`](https://github.com/Byron/gitoxide/commit/c41b933d14f2e4538928e4fbd682e1017702e69c)) - refactor ([`a1dc8de`](https://github.com/Byron/gitoxide/commit/a1dc8dedc5d9e1712295131d2332c21f3df4ac45)) - simplify UNTR directory indexing ([`7857d08`](https://github.com/Byron/gitoxide/commit/7857d08a25eac1c7d4a91f04eb80a83ec5677d1b)) - - Properly sort cache tree entries upon load ([`421d1ba`](https://github.com/Byron/gitoxide/commit/421d1ba853a75560f59cb0ce2b353087db0dff56)) - flatten UNTR directory list for later access via bitmaps ([`2e39184`](https://github.com/Byron/gitoxide/commit/2e391841af52f88b7a0472179e5dda89cc6c9808)) - - tree-ordering validation shows something wrong ([`5fb2857`](https://github.com/Byron/gitoxide/commit/5fb2857e9f970c150f5221ca721506e7bc046ef4)) - read UNTR directory blocks and bitmaps ([`59f46fe`](https://github.com/Byron/gitoxide/commit/59f46fe134e8619f501c79da4290cadd5548751c)) - - First stab at tree verification ([`f928350`](https://github.com/Byron/gitoxide/commit/f9283500e8316ab949fc0ff9c2fc13a498380873)) - First portion of reading the untracked cache ([`ed2fe5d`](https://github.com/Byron/gitoxide/commit/ed2fe5dbfbcf79ffdcdceed90f6321792cff076d)) - failing test for UNTR extension ([`223f2cc`](https://github.com/Byron/gitoxide/commit/223f2cc1c88f76dc75ca6706f1f61514ab52e496)) - - Verify entry order ([`2d101eb`](https://github.com/Byron/gitoxide/commit/2d101ebbd36e000ffec0e965012081fec2e234f7)) - - Now we are able to load indices correctly ([`762efa3`](https://github.com/Byron/gitoxide/commit/762efa3f5e4ebda4d3bcc6a9bba43c6cdb407937)) - Add UNTR extension fixture ([`3c7ba24`](https://github.com/Byron/gitoxide/commit/3c7ba247a3fdab114d9d549de50d6143c7fcce0a)) - - refactor ([`017e915`](https://github.com/Byron/gitoxide/commit/017e9153aaaa1cdd6788d9f61ff1ffbd61bc1b30)) - - Add breaking test with conflicting file in index ([`791a9f8`](https://github.com/Byron/gitoxide/commit/791a9f84ff8871c7beb0e2100a4dcba0e9384737)) - - basic index file checksum verification ([`c644565`](https://github.com/Byron/gitoxide/commit/c644565d5b8d9ae3991ee82a7ffa5e21a2705f91)) - REUC reading works ([`29c1af9`](https://github.com/Byron/gitoxide/commit/29c1af9b2d7b9879a806fc84cfc89ed6c0d7f083)) - - At least check for the presence of extensions ([`28c056c`](https://github.com/Byron/gitoxide/commit/28c056c6d2bbfb16a826238fd6879adecbeb1171)) - - Print extension names instead of count ([`1cc07e0`](https://github.com/Byron/gitoxide/commit/1cc07e0cfdae74e388abb29d7acb9c6f643278b4)) - frame and test for REUC exstension ([`229cabe`](https://github.com/Byron/gitoxide/commit/229cabe8de9e1bd244d56d24327b05e3d80dfb6e)) - - thorough checking of Tree extension ([`d1063aa`](https://github.com/Byron/gitoxide/commit/d1063aa20bfcefb064ba08089f095baef1299dcd)) - add git index with REUC exstension ([`8359fdb`](https://github.com/Byron/gitoxide/commit/8359fdb6c49b263bc7ac2f3105254b83eac47638)) - - refactor ([`d0725bd`](https://github.com/Byron/gitoxide/commit/d0725bd40f0b9af0e0af34ffe77c2d8406c6d24c)) - - Fix tree-extension loading for empty trees ([`2e13989`](https://github.com/Byron/gitoxide/commit/2e1398985edfaf9e62ff5643cf4756d9d9717862)) - - Print basic index information, including the tree extension ([`9277cf8`](https://github.com/Byron/gitoxide/commit/9277cf877e1f2276dcad1efdeb97e0e3d96ed3f0)) - Support for 'sdir' extension ([`a38c3b8`](https://github.com/Byron/gitoxide/commit/a38c3b889cfbf1447c87d489d3eb9902e757aa4b)) - Turn git-bitmap Array into Vec, as it will be able to adjust its size ([`9e99e01`](https://github.com/Byron/gitoxide/commit/9e99e016c17f0d5bcd2ab645261dfac58cb48be5)) - first stab at decoding ewah bitmaps ([`353a53c`](https://github.com/Byron/gitoxide/commit/353a53ccab5af990e7c384b74b38e5429417d449)) @@ -210,21 +246,10 @@ certain extensions are present. - The realization that FileBuffer really shouldn't be used anymore ([`b481f13`](https://github.com/Byron/gitoxide/commit/b481f136c4084b8839ebecb604dea5aa30d3a44e)) - base setup for index testing ([`aa60fdf`](https://github.com/Byron/gitoxide/commit/aa60fdf3d86e08877c88f9e4973f546642ed1370)) - notes on how test indices have been created ([`3040857`](https://github.com/Byron/gitoxide/commit/3040857ec4d2e0557b4920eaa77ddc4292d9adae)) - * **[#298](https://github.com/Byron/gitoxide/issues/298)** - - Also print stage of entries ([`003515f`](https://github.com/Byron/gitoxide/commit/003515f3c90a49fbe9db9b84987233486711beb8)) - - simple printing of basic entry information ([`329538b`](https://github.com/Byron/gitoxide/commit/329538b9c3f44bb8e70a4567ba90dc3b594c2dfc)) * **Uncategorized** - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - - Release git-hash v0.9.2, git-object v0.17.1, git-pack v0.16.1 ([`0db19b8`](https://github.com/Byron/gitoxide/commit/0db19b8deaf11a4d4cbc03fa3ae40eea104bc302)) - thanks clippy ([`09df2bc`](https://github.com/Byron/gitoxide/commit/09df2bcb4b45f72742d139530907be8aa4dc36f8)) - - refactor ([`afdeca1`](https://github.com/Byron/gitoxide/commit/afdeca1e5ec119607c5d1f5ccec5d216fc7d5261)) - - thanks clippy ([`2f25bf1`](https://github.com/Byron/gitoxide/commit/2f25bf1ebf44aef8c4886eaefb3e87836d535f61)) - thanks clippy ([`93c3d23`](https://github.com/Byron/gitoxide/commit/93c3d23d255a02d65b5404c2f62f96d94e36f33d)) - - thanks clippy ([`d721019`](https://github.com/Byron/gitoxide/commit/d721019aebe5b01ddb15c9b1aab279647069452a)) - - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c)) - - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - - upgrade to tui 0.17 and prodash 18 ([`eba101a`](https://github.com/Byron/gitoxide/commit/eba101a576ecb7bc0f63357d0dd81eb817b94be4)) - - dependency update ([`ca59e44`](https://github.com/Byron/gitoxide/commit/ca59e448061698dd559db43123fe676ae61129a0)) - Fix index without extension test & thanks clippy ([`066464d`](https://github.com/Byron/gitoxide/commit/066464d2ad2833012fa196fe41e93a54ab05457f)) - thanks clippy ([`f477032`](https://github.com/Byron/gitoxide/commit/f47703256fe6a5c68ed3af6705bcdf01262500d6)) - thanks clippy ([`5526020`](https://github.com/Byron/gitoxide/commit/552602074a99dc536624f0c6295e807caf32f58b)) diff --git a/git-index/Cargo.toml b/git-index/Cargo.toml index 8b65d72e914..921fafb8ad4 100644 --- a/git-index/Cargo.toml +++ b/git-index/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-index" -version = "0.3.0" +version = "0.4.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A work-in-progress crate of the gitoxide project dedicated implementing the git index file" @@ -32,10 +32,10 @@ internal-testing-to-avoid-being-run-by-cargo-test-all = [] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.0", path = "../git-features", features = ["rustsha1", "progress"] } -git-hash = { version = "^0.9.4", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1", "progress"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-bitmap = { version = "^0.1.0", path = "../git-bitmap" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" memmap2 = "0.5.0" diff --git a/git-mailmap/CHANGELOG.md b/git-mailmap/CHANGELOG.md index 7d9b0e85208..0e8eb169986 100644 --- a/git-mailmap/CHANGELOG.md +++ b/git-mailmap/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.2.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 45 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) @@ -27,6 +51,7 @@ A maintenance release without user-facing changes. * **[#301](https://github.com/Byron/gitoxide/issues/301)** - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) diff --git a/git-mailmap/Cargo.toml b/git-mailmap/Cargo.toml index 762520f430c..78285f0541d 100644 --- a/git-mailmap/Cargo.toml +++ b/git-mailmap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-mailmap" -version = "0.2.0" +version = "0.3.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project for parsing mailmap files" @@ -17,7 +17,7 @@ serde1 = ["serde", "bstr/serde1", "git-actor/serde1"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-actor = { version = "^0.10.0", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } bstr = { version = "0.2.13", default-features = false, features = ["std", "unicode"]} quick-error = "2.0.0" serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-mailmap/src/entry.rs b/git-mailmap/src/entry.rs index e157428145b..69a0c53ee57 100644 --- a/git-mailmap/src/entry.rs +++ b/git-mailmap/src/entry.rs @@ -2,7 +2,7 @@ use bstr::BStr; use crate::Entry; -/// Acccess +/// Access impl<'a> Entry<'a> { /// The name to map to. pub fn new_name(&self) -> Option<&'a BStr> { diff --git a/git-mailmap/src/lib.rs b/git-mailmap/src/lib.rs index c471b5b10bf..2248db26d75 100644 --- a/git-mailmap/src/lib.rs +++ b/git-mailmap/src/lib.rs @@ -10,7 +10,7 @@ pub mod parse; /// Parse the given `buf` of bytes line by line into mapping [Entries][Entry]. /// -/// Errors may occour per line, but it's up to the caller to stop iteration when +/// Errors may occur per line, but it's up to the caller to stop iteration when /// one is encountered. pub fn parse(buf: &[u8]) -> parse::Lines<'_> { parse::Lines::new(buf) diff --git a/git-mailmap/src/snapshot.rs b/git-mailmap/src/snapshot.rs index e61ea56ce80..c6adb243e65 100644 --- a/git-mailmap/src/snapshot.rs +++ b/git-mailmap/src/snapshot.rs @@ -183,7 +183,7 @@ impl<'a> From> for EmailEntry { } impl Snapshot { - /// Create a new snapshot from the given bytes buffer, ignoring all parse errors that may occour on a line-by-line basis. + /// Create a new snapshot from the given bytes buffer, ignoring all parse errors that may occur on a line-by-line basis. /// /// This is similar to what git does. pub fn from_bytes(buf: &[u8]) -> Self { diff --git a/git-object/CHANGELOG.md b/git-object/CHANGELOG.md index 9882c056dd3..ff0f2102286 100644 --- a/git-object/CHANGELOG.md +++ b/git-object/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.20.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 21 calendar days. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 0.19.0 (2022-05-18) ### New Features @@ -17,7 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 2 commits contributed to the release over the course of 30 calendar days. + - 3 commits contributed to the release over the course of 30 calendar days. - 45 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#389](https://github.com/Byron/gitoxide/issues/389) @@ -32,6 +63,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596)) * **[#389](https://github.com/Byron/gitoxide/issues/389)** - `TagRefIter::tagger()`. ([`0d22ab4`](https://github.com/Byron/gitoxide/commit/0d22ab459ce14bc57549270142595d8ebd98ea41)) + * **Uncategorized** + - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e)) ## 0.18.0 (2022-04-03) @@ -56,7 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 18 commits contributed to the release over the course of 73 calendar days. + - 17 commits contributed to the release over the course of 55 calendar days. - 60 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#329](https://github.com/Byron/gitoxide/issues/329), [#364](https://github.com/Byron/gitoxide/issues/364) @@ -93,7 +126,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - upgrade document-features ([`c35e62e`](https://github.com/Byron/gitoxide/commit/c35e62e0da9ac1f7dcb863f5f9c69108c728d32e)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - Release git-actor v0.8.1 ([`08fe550`](https://github.com/Byron/gitoxide/commit/08fe5508472f2eb209db8a5fc4e4942a9d7db93d)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) ## 0.17.1 (2022-02-01) @@ -108,7 +140,7 @@ A automated maintenance release without impact to the public API. - - 5 commits contributed to the release over the course of 4 calendar days. + - 5 commits contributed to the release over the course of 7 calendar days. - 8 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#293](https://github.com/Byron/gitoxide/issues/293) @@ -158,7 +190,7 @@ A automated maintenance release without impact to the public API. - - 13 commits contributed to the release over the course of 51 calendar days. + - 14 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#266](https://github.com/Byron/gitoxide/issues/266), [#279](https://github.com/Byron/gitoxide/issues/279) @@ -187,9 +219,10 @@ A automated maintenance release without impact to the public API. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'oknozor-feat/traversal-sort-by-committer-date' ([`6add377`](https://github.com/Byron/gitoxide/commit/6add3773c64a9155c236a36bd002099c218882eb)) - - rename `commit::ref_iter::Token::into_id()` to `*::try_into_id()` ([`fda2a8d`](https://github.com/Byron/gitoxide/commit/fda2a8d2f5f8b7d80b4cc0177d08d6a061f1b8f1)) - Add sorting mode to ancestor traversal #270 ([`eb36a3d`](https://github.com/Byron/gitoxide/commit/eb36a3dda83a46ad59078a904f4e277f298a24e1)) + - rename `commit::ref_iter::Token::into_id()` to `*::try_into_id()` ([`fda2a8d`](https://github.com/Byron/gitoxide/commit/fda2a8d2f5f8b7d80b4cc0177d08d6a061f1b8f1)) - thanks clippy ([`7dd2313`](https://github.com/Byron/gitoxide/commit/7dd2313d980fe7c058319ae66d313b3097e3ae5f)) @@ -203,7 +236,7 @@ Maintenance release due, which isn't really required but one now has to be caref - - 7 commits contributed to the release over the course of 25 calendar days. + - 6 commits contributed to the release over the course of 11 calendar days. - 12 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#250](https://github.com/Byron/gitoxide/issues/250), [#259](https://github.com/Byron/gitoxide/issues/259) @@ -223,7 +256,6 @@ Maintenance release due, which isn't really required but one now has to be caref - Release git-features v0.18.0, git-actor v0.7.0, git-config v0.1.9, git-object v0.16.0, git-diff v0.12.0, git-traverse v0.11.0, git-pack v0.15.0, git-odb v0.25.0, git-packetline v0.12.2, git-transport v0.14.0, git-protocol v0.13.0, git-ref v0.10.0, git-repository v0.13.0, cargo-smart-release v0.7.0, safety bump 12 crates ([`acd3737`](https://github.com/Byron/gitoxide/commit/acd37371dcd92ebac3d1f039224d02f2b4e9fa0b)) - Adjust changelogs prior to release ([`ec38950`](https://github.com/Byron/gitoxide/commit/ec3895005d141abe79764eaff7c0f04153e38d73)) - Merge branch 'git-loose-objects' of https://github.com/xmo-odoo/gitoxide into xmo-odoo-git-loose-objects ([`ee737cd`](https://github.com/Byron/gitoxide/commit/ee737cd237ad70bf9f2c5e0d3e4557909e495bca)) - - Move "loose object header" ser/de to git-object ([`3d1565a`](https://github.com/Byron/gitoxide/commit/3d1565acfc336baf6487edccefd72d0226141a08)) ## 0.15.1 (2021-11-16) @@ -234,7 +266,7 @@ A maintenance release triggered by changes to git-pack and changelog rewrites. - - 6 commits contributed to the release. + - 7 commits contributed to the release over the course of 15 calendar days. - 27 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#254](https://github.com/Byron/gitoxide/issues/254) @@ -251,8 +283,9 @@ A maintenance release triggered by changes to git-pack and changelog rewrites. - Release git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0 ([`f606fa9`](https://github.com/Byron/gitoxide/commit/f606fa9a0ca338534252df8921cd5e9d3875bf94)) - better changelog descriptions. ([`f69b2d6`](https://github.com/Byron/gitoxide/commit/f69b2d627099639bc144fd94fde678d84a10d6f7)) - Adjusting changelogs prior to release of git-config v0.1.8, git-object v0.15.1, git-diff v0.11.1, git-traverse v0.10.1, git-pack v0.14.0, git-odb v0.24.0, git-packetline v0.12.1, git-transport v0.13.1, git-protocol v0.12.1, git-ref v0.9.1, git-repository v0.12.0, cargo-smart-release v0.6.0, safety bump 5 crates ([`39b40c8`](https://github.com/Byron/gitoxide/commit/39b40c8c3691029cc146b893fa0d8d25d56d0819)) - - Improve error handling of encode::header_field_multi_line & simplify ([`bab9fb5`](https://github.com/Byron/gitoxide/commit/bab9fb567e47bb88d27b36f6ffa95c62c14ed80a)) - Adjust changelogs prior to git-pack release ([`ac8015d`](https://github.com/Byron/gitoxide/commit/ac8015de710142c2bedd0e4188e872e0cf1ceccc)) + - Move "loose object header" ser/de to git-object ([`3d1565a`](https://github.com/Byron/gitoxide/commit/3d1565acfc336baf6487edccefd72d0226141a08)) + - Improve error handling of encode::header_field_multi_line & simplify ([`bab9fb5`](https://github.com/Byron/gitoxide/commit/bab9fb567e47bb88d27b36f6ffa95c62c14ed80a)) ## v0.15.0 (2021-10-19) @@ -412,7 +445,7 @@ or generally trying to figure out what changed between commits. - - 2 commits contributed to the release over the course of 1 calendar day. + - 5 commits contributed to the release over the course of 5 calendar days. - 10 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -426,6 +459,9 @@ or generally trying to figure out what changed between commits. * **Uncategorized** - Release git-object v0.13.1 ([`2c55ea7`](https://github.com/Byron/gitoxide/commit/2c55ea759caa1d317f008966ae388b3cf0ce0f6d)) - Bump git-hash v0.6.0 ([`6efd90d`](https://github.com/Byron/gitoxide/commit/6efd90db54f7f7441b76159dba3be80c15657a3d)) + - [object #190] consistent method naming ([`c5de433`](https://github.com/Byron/gitoxide/commit/c5de433e569c2cc8e78f3f84e368a11fe95f522a)) + - [object #190] More conversion methods for Object ([`78bacf9`](https://github.com/Byron/gitoxide/commit/78bacf97d669f3adfebdb093054c162cfd5214fa)) + - [repository #190] A way to write objects and the empty tree specifically ([`7c559d6`](https://github.com/Byron/gitoxide/commit/7c559d6e1b68bc89220bca426257f383bce586ae)) ## v0.13.0 (2021-08-27) @@ -434,7 +470,7 @@ or generally trying to figure out what changed between commits. - - 31 commits contributed to the release over the course of 8 calendar days. + - 28 commits contributed to the release over the course of 8 calendar days. - 10 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -450,15 +486,12 @@ or generally trying to figure out what changed between commits. - [object #177] dissolve 'immutable' module ([`70e11c2`](https://github.com/Byron/gitoxide/commit/70e11c21b0637cd250f54381d5490e9976880ad9)) - [object #177] fix docs ([`2fd23ed`](https://github.com/Byron/gitoxide/commit/2fd23ed9ad556b8e46cf650e23f0c6726e304708)) - [object #177] resolve 'mutable' module ([`b201b32`](https://github.com/Byron/gitoxide/commit/b201b3260e3eec98ed71716c1aab1ba4a06ab829)) - - [object #190] consistent method naming ([`c5de433`](https://github.com/Byron/gitoxide/commit/c5de433e569c2cc8e78f3f84e368a11fe95f522a)) - [object #177] refactor ([`216dd0f`](https://github.com/Byron/gitoxide/commit/216dd0f10add7a11ebdf96732ed7649d74815d64)) - [object #177] refactor ([`472e13b`](https://github.com/Byron/gitoxide/commit/472e13b27e97a196c644d716cad1801bd62fac71)) - [object #177] Commit::write_to migration ([`60b9365`](https://github.com/Byron/gitoxide/commit/60b936553bef3c9126d46ece9779f08b5eef9a95)) - - [object #190] More conversion methods for Object ([`78bacf9`](https://github.com/Byron/gitoxide/commit/78bacf97d669f3adfebdb093054c162cfd5214fa)) - [object #177] commit::RefIter -> CommitRefIter ([`e603306`](https://github.com/Byron/gitoxide/commit/e603306e81f392af97aa5afd232653de56bf3ce9)) - [object #177] migrate immutable::commit into crate::commit ([`45d3934`](https://github.com/Byron/gitoxide/commit/45d393438eac2c7ecd47670922437dd0de4cd69b)) - [object #177] refactor tag write_to ([`7f19559`](https://github.com/Byron/gitoxide/commit/7f1955916ae9d7e17be971170c853487e3755169)) - - [repository #190] A way to write objects and the empty tree specifically ([`7c559d6`](https://github.com/Byron/gitoxide/commit/7c559d6e1b68bc89220bca426257f383bce586ae)) - [object #177] tag::RefIter -> TagRefIter ([`28587c6`](https://github.com/Byron/gitoxide/commit/28587c691eb74e5cb097afb2b63f9d9e2561c45d)) - [object #177] into_mutable() -> into_owned() ([`7e701ce`](https://github.com/Byron/gitoxide/commit/7e701ce49efe5d40327770a988aae88692d88219)) - [object #177] fix docs ([`25d8e7b`](https://github.com/Byron/gitoxide/commit/25d8e7b1862bd05489359b162a32c6ad45ecdf9a)) @@ -469,12 +502,12 @@ or generally trying to figure out what changed between commits. - [object #177] rename immutable::* to immutable::*Ref ([`6deb012`](https://github.com/Byron/gitoxide/commit/6deb01291fb382b7fb9206682e319afa81bacc05)) - Release git-object v0.13.0 ([`708fc5a`](https://github.com/Byron/gitoxide/commit/708fc5abd8af4dd7459f388c7092bf35915c6662)) - Merge pull request #172 from mellowagain/main ([`61aebbf`](https://github.com/Byron/gitoxide/commit/61aebbfff02eb87e0e8c49438a093a21b1134baf)) - - Release git-actor v0.4.0 ([`16358c9`](https://github.com/Byron/gitoxide/commit/16358c9bf03604857d51bfa4dbfd2fc8c5210da7)) - - [actor #173] fix docs ([`2d7956a`](https://github.com/Byron/gitoxide/commit/2d7956a22511d73b767e443dac21b60e93f286dd)) - Release git-actor v0.5.0 ([`a684b0f`](https://github.com/Byron/gitoxide/commit/a684b0ff96ebfc5e4b3ce78452dc21ce856a6869)) - - Upgrade to nom-7 ([`f0aa3e1`](https://github.com/Byron/gitoxide/commit/f0aa3e1b5b407b2afd187c9cb622676fcddaf706)) - [actor #175] refactor ([`ec88c59`](https://github.com/Byron/gitoxide/commit/ec88c5905194150cc94db4d4c20e9f4e2f6595c3)) + - Release git-actor v0.4.0 ([`16358c9`](https://github.com/Byron/gitoxide/commit/16358c9bf03604857d51bfa4dbfd2fc8c5210da7)) + - [actor #173] fix docs ([`2d7956a`](https://github.com/Byron/gitoxide/commit/2d7956a22511d73b767e443dac21b60e93f286dd)) - [actor #173] rename immutable::Signature to SignatureRef! ([`96461ac`](https://github.com/Byron/gitoxide/commit/96461ace776d6b351b313d4f2697f2d95b9e196e)) + - Upgrade to nom-7 ([`f0aa3e1`](https://github.com/Byron/gitoxide/commit/f0aa3e1b5b407b2afd187c9cb622676fcddaf706)) - [smart-release #162] use TreeRef capabilities to lookup path ([`51d1943`](https://github.com/Byron/gitoxide/commit/51d19433e6704fabb6547a0ba1b5c32afce43d8b)) - [repository #162] what could be a correct implementation of a tree path lookup ([`1f638ee`](https://github.com/Byron/gitoxide/commit/1f638eee0aa5f6e1cc34c5bc59a18b5f22af4cbc)) @@ -799,7 +832,7 @@ or generally trying to figure out what changed between commits. - - 16 commits contributed to the release over the course of 90 calendar days. + - 17 commits contributed to the release over the course of 90 calendar days. - 94 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -827,6 +860,7 @@ or generally trying to figure out what changed between commits. - Document borrowed odb objects ([`7626f7f`](https://github.com/Byron/gitoxide/commit/7626f7f3af885f1b95801f9dbc71bee0bc77400e)) - remove dash in all repository links ([`98c1360`](https://github.com/Byron/gitoxide/commit/98c1360ba4d2fb3443602b7da8775906224feb1d)) - Finish removal of rust 2018 idioms ([`0d1699e`](https://github.com/Byron/gitoxide/commit/0d1699e0e0bc9052be0a74b1b3f3d3eeeec39e3e)) + - refactor ([`e4bcfe6`](https://github.com/Byron/gitoxide/commit/e4bcfe6406b14feffa63598c7cdcc8ecc73222bd)) ### Thanks Clippy @@ -841,7 +875,7 @@ or generally trying to figure out what changed between commits. - - 7 commits contributed to the release over the course of 29 calendar days. + - 6 commits contributed to the release over the course of 29 calendar days. - 30 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -854,7 +888,6 @@ or generally trying to figure out what changed between commits. * **Uncategorized** - (cargo-release) version 0.4.0 ([`0d7b60e`](https://github.com/Byron/gitoxide/commit/0d7b60e856325009431172e1df742a1cd2165575)) - - refactor ([`e4bcfe6`](https://github.com/Byron/gitoxide/commit/e4bcfe6406b14feffa63598c7cdcc8ecc73222bd)) - (cargo-release) version 0.4.0 ([`f9dd225`](https://github.com/Byron/gitoxide/commit/f9dd225afc4aafde1a8b8148943f56f2c547a9ea)) - [clone] proper parsing of V1 refs ([`d262307`](https://github.com/Byron/gitoxide/commit/d26230727ef795a819852bc82d6c2e9956809d8c)) - [clone] Don't expose hex-error in public interfaces anymore ([`92dab30`](https://github.com/Byron/gitoxide/commit/92dab3033890fe26fe2b901d87abe16abd065cce)) diff --git a/git-object/Cargo.toml b/git-object/Cargo.toml index caad1b9af50..248cd1fa296 100644 --- a/git-object/Cargo.toml +++ b/git-object/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-object" -version = "0.19.0" +version = "0.20.0" description = "Immutable and mutable git objects with decoding and encoding support" authors = ["Sebastian Thiel "] repository = "https://github.com/Byron/gitoxide" @@ -21,10 +21,10 @@ serde1 = ["serde", "bstr/serde1", "smallvec/serde", "git-hash/serde1", "git-acto verbose-object-parsing-errors = ["nom/std"] [dependencies] -git-features = { version = "^0.21.0", path = "../git-features", features = ["rustsha1"] } -git-hash = { version = "^0.9.4", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-validate = { version = "^0.5.4", path = "../git-validate" } -git-actor = { version = "^0.10.0", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } btoi = "0.4.2" itoa = "1.0.1" diff --git a/git-odb/CHANGELOG.md b/git-odb/CHANGELOG.md index be2294d5dea..07497f117f0 100644 --- a/git-odb/CHANGELOG.md +++ b/git-odb/CHANGELOG.md @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.31.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 7 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.30.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +43,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +55,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-odb/Cargo.toml b/git-odb/Cargo.toml index c873fcc650c..1d0ef65a716 100644 --- a/git-odb/Cargo.toml +++ b/git-odb/Cargo.toml @@ -27,11 +27,11 @@ path = "tests/odb-single-threaded.rs" required-features = [] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } -git-path = { version = "^0.3.0", path = "../git-path" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["rustsha1", "walkdir", "zlib", "crc32" ] } +git-path = { version = "^0.4.0", path = "../git-path" } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-quote = { version = "^0.2.0", path = "../git-quote" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-object = { version = "^0.20.0", path = "../git-object" } git-pack = { version = "^0.21.0", path = "../git-pack" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-odb/src/store_impls/dynamic/load_index.rs b/git-odb/src/store_impls/dynamic/load_index.rs index a1c455d0bc9..9075274c11b 100644 --- a/git-odb/src/store_impls/dynamic/load_index.rs +++ b/git-odb/src/store_impls/dynamic/load_index.rs @@ -531,7 +531,7 @@ impl super::Store { let mut files = slot.files.load_full(); let files_mut = Arc::make_mut(&mut files); // set the generation before we actually change the value, otherwise readers of old generations could observe the new one. - // We rather want them to turn around here and update their index, which, by that time, migth actually already be available. + // We rather want them to turn around here and update their index, which, by that time, might actually already be available. // If not, they would fail unable to load a pack or index they need, but that's preferred over returning wrong objects. // Safety: can't race as we hold the lock, have to set the generation beforehand to help avoid others to observe the value. slot.generation.store(generation, Ordering::SeqCst); diff --git a/git-odb/src/store_impls/dynamic/mod.rs b/git-odb/src/store_impls/dynamic/mod.rs index 50bb93de8bd..94548551ab8 100644 --- a/git-odb/src/store_impls/dynamic/mod.rs +++ b/git-odb/src/store_impls/dynamic/mod.rs @@ -134,7 +134,7 @@ pub mod structure { /// /// Note that this call is expensive as it gathers additional information about loose object databases. /// Note that it may change as we collect information due to the highly volatile nature of the - /// implementation. The likelyhood of actual changes is low though as these still depend on something + /// implementation. The likelihood of actual changes is low though as these still depend on something /// changing on disk and somebody reading at the same time. pub fn structure(&self) -> Result, load_index::Error> { let index = self.index.load(); diff --git a/git-odb/src/store_impls/dynamic/types.rs b/git-odb/src/store_impls/dynamic/types.rs index a055cdc961e..e902514a11b 100644 --- a/git-odb/src/store_impls/dynamic/types.rs +++ b/git-odb/src/store_impls/dynamic/types.rs @@ -259,7 +259,7 @@ impl IndexAndPacks { } } - /// If we are garbaged, put ourselve into the loaded state. Otherwise put ourselves back to unloaded. + /// If we are garbaged, put ourselves into the loaded state. Otherwise put ourselves back to unloaded. pub(crate) fn put_back(&mut self) { match self { IndexAndPacks::Index(bundle) => { diff --git a/git-odb/tests/odb/store/dynamic.rs b/git-odb/tests/odb/store/dynamic.rs index 6d6b6df9a27..4d84e123910 100644 --- a/git-odb/tests/odb/store/dynamic.rs +++ b/git-odb/tests/odb/store/dynamic.rs @@ -325,7 +325,7 @@ fn contains() { unreachable_indices: 0, unreachable_packs: 0 }, - "when asking for an object in the smallest pack, all inbetween packs are also loaded." + "when asking for an object in the smallest pack, all in between packs are also loaded." ); assert!(!new_handle.contains(hex_to_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))); diff --git a/git-pack/CHANGELOG.md b/git-pack/CHANGELOG.md index b3647b8bc0e..c58ec88ce70 100644 --- a/git-pack/CHANGELOG.md +++ b/git-pack/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.21.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 4 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.20.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +39,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release over the course of 22 calendar days. + - 3 commits contributed to the release over the course of 22 calendar days. - 22 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +51,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - Merge branch 'davidkna-discover-x-fs' ([`9abaeda`](https://github.com/Byron/gitoxide/commit/9abaeda2d22e2dbb1db1632c6eb637f1458d06e1))
diff --git a/git-pack/Cargo.toml b/git-pack/Cargo.toml index 0c56ec907f9..ed441f6b5cc 100644 --- a/git-pack/Cargo.toml +++ b/git-pack/Cargo.toml @@ -37,13 +37,13 @@ path = "tests/pack-single-threaded.rs" required-features = ["internal-testing-to-avoid-being-run-by-cargo-test-all"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["crc32", "rustsha1", "progress", "zlib"] } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-path = { version = "^0.4.0", path = "../git-path" } git-chunk = { version = "^0.3.0", path = "../git-chunk" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-traverse = { version = "^0.15.0", path = "../git-traverse" } -git-diff = { version = "^0.16.0", path = "../git-diff" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-traverse = { version = "^0.16.0", path = "../git-traverse" } +git-diff = { version = "^0.17.0", path = "../git-diff" } git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } smallvec = "1.3.0" diff --git a/git-pack/src/cache/delta/mod.rs b/git-pack/src/cache/delta/mod.rs index c308d97f13c..d99a3d5510f 100644 --- a/git-pack/src/cache/delta/mod.rs +++ b/git-pack/src/cache/delta/mod.rs @@ -44,7 +44,7 @@ enum NodeKind { pub struct Tree { /// The root nodes, i.e. base objects root_items: Vec>, - /// The child nodes, i.e. those that rely a base object, like ref and ofs delta objets + /// The child nodes, i.e. those that rely a base object, like ref and ofs delta objects child_items: Vec>, /// The last encountered node was either a root or a child. last_seen: Option, diff --git a/git-pack/src/data/file/decode_entry.rs b/git-pack/src/data/file/decode_entry.rs index 14e54b2fdf3..b3a7920deb0 100644 --- a/git-pack/src/data/file/decode_entry.rs +++ b/git-pack/src/data/file/decode_entry.rs @@ -135,7 +135,7 @@ impl File { /// a base object, instead of an in-pack offset. /// /// `delta_cache` is a mechanism to avoid looking up base objects multiple times when decompressing multiple objects in a row. - /// Use a [Noop-Cache][cache::Never] to disable caching alltogether at the cost of repeating work. + /// Use a [Noop-Cache][cache::Never] to disable caching all together at the cost of repeating work. pub fn decode_entry( &self, entry: crate::data::Entry, @@ -165,7 +165,7 @@ impl File { } } - /// resolve: technically, this shoudln't ever be required as stored local packs don't refer to objects by id + /// resolve: technically, this shouldn't ever be required as stored local packs don't refer to objects by id /// that are outside of the pack. Unless, of course, the ref refers to an object within this pack, which means /// it's very, very large as 20bytes are smaller than the corresponding MSB encoded number fn resolve_deltas( @@ -298,7 +298,7 @@ impl File { let end = first_buffer_size + second_buffer_size; if delta_range.start < end { // …this means that the delta size is even larger than two uncompressed worst-case - // intermediate results combined. It would already be undesireable to have it bigger + // intermediate results combined. It would already be undesirable to have it bigger // then the target size (as you could just store the object in whole). // However, this just means that it reuses existing deltas smartly, which as we rightfully // remember stand for an object each. However, this means a lot of data is read to restore diff --git a/git-pack/src/data/input/entries_to_bytes.rs b/git-pack/src/data/input/entries_to_bytes.rs index 8843774b41e..7082387ce5a 100644 --- a/git-pack/src/data/input/entries_to_bytes.rs +++ b/git-pack/src/data/input/entries_to_bytes.rs @@ -21,7 +21,7 @@ pub struct EntriesToBytesIter { data_version: crate::data::Version, /// The amount of entries seen so far num_entries: u32, - /// If we are done, no additional writes will occour + /// If we are done, no additional writes will occur is_done: bool, /// The kind of hash to use for the digest object_hash: git_hash::Kind, @@ -33,7 +33,7 @@ where W: std::io::Read + std::io::Write + std::io::Seek, { /// Create a new instance reading [entries][input::Entry] from an `input` iterator and write pack data bytes to - /// `output` writer, resembling a pack of `version`. The amonut of entries will be dynaimcally determined and + /// `output` writer, resembling a pack of `version`. The amount of entries will be dynaimcally determined and /// the pack is completed once the last entry was written. /// `object_hash` is the kind of hash to use for the pack checksum and maybe other places, depending on the version. /// diff --git a/git-pack/src/data/output/bytes.rs b/git-pack/src/data/output/bytes.rs index 03ed7ec4fa1..ba854d3713d 100644 --- a/git-pack/src/data/output/bytes.rs +++ b/git-pack/src/data/output/bytes.rs @@ -37,7 +37,7 @@ pub struct FromEntriesIter { /// It stores the pack offsets at which objects begin. /// Additionally we store if an object was invalid, and if so we will not write it nor will we allow delta objects to it. pack_offsets_and_validity: Vec<(u64, bool)>, - /// If we are done, no additional writes will occour + /// If we are done, no additional writes will occur is_done: bool, } diff --git a/git-pack/src/data/output/count/mod.rs b/git-pack/src/data/output/count/mod.rs index 7a567ea0fd9..e9a423c836e 100644 --- a/git-pack/src/data/output/count/mod.rs +++ b/git-pack/src/data/output/count/mod.rs @@ -8,7 +8,7 @@ use crate::data::output::Count; pub enum PackLocation { /// We did not lookup this object NotLookedUp, - /// The object was looked up and there may be a location in a pack, along with enty information + /// The object was looked up and there may be a location in a pack, along with entry information LookedUp(Option), } diff --git a/git-pack/src/data/output/count/objects/types.rs b/git-pack/src/data/output/count/objects/types.rs index 3dd63dfd12b..8294e1318c6 100644 --- a/git-pack/src/data/output/count/objects/types.rs +++ b/git-pack/src/data/output/count/objects/types.rs @@ -58,7 +58,7 @@ impl Default for ObjectExpansion { } } -/// Configuration options for the pack generation functions provied in [this module][crate::data::output]. +/// Configuration options for the pack generation functions provided in [this module][crate::data::output]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Options { diff --git a/git-pack/src/data/output/entry/iter_from_counts.rs b/git-pack/src/data/output/entry/iter_from_counts.rs index d5f6176236a..37725b99d7c 100644 --- a/git-pack/src/data/output/entry/iter_from_counts.rs +++ b/git-pack/src/data/output/entry/iter_from_counts.rs @@ -351,7 +351,7 @@ mod types { PackCopyAndBaseObjects, } - /// Configuration options for the pack generation functions provied in [this module][crate::data::output]. + /// Configuration options for the pack generation functions provided in [this module][crate::data::output]. #[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] #[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))] pub struct Options { diff --git a/git-pack/src/multi_index/access.rs b/git-pack/src/multi_index/access.rs index dc8338943eb..1bb79fb709f 100644 --- a/git-pack/src/multi_index/access.rs +++ b/git-pack/src/multi_index/access.rs @@ -24,7 +24,7 @@ pub struct Entry { /// Access methods impl File { - /// Returns the verion of the multi-index file. + /// Returns the version of the multi-index file. pub fn version(&self) -> Version { self.version } diff --git a/git-pack/tests/pack/data/file.rs b/git-pack/tests/pack/data/file.rs index bc8b5d4418d..5be6a86f0ed 100644 --- a/git-pack/tests/pack/data/file.rs +++ b/git-pack/tests/pack/data/file.rs @@ -67,7 +67,7 @@ mod decode_entry { #[test] fn blob_ofs_delta_two_links() { let buf = decode_entry_at_offset(3033); - assert_eq!(buf.len(), 173, "buffer length is the acutal object size"); + assert_eq!(buf.len(), 173, "buffer length is the actual object size"); assert_eq!( buf.capacity(), 2381, @@ -82,7 +82,7 @@ mod decode_entry { #[test] fn blob_ofs_delta_single_link() { let buf = decode_entry_at_offset(3569); - assert_eq!(buf.len(), 1163, "buffer length is the acutal object size"); + assert_eq!(buf.len(), 1163, "buffer length is the actual object size"); assert_eq!( buf.capacity(), 2398, diff --git a/git-pack/tests/pack/data/input.rs b/git-pack/tests/pack/data/input.rs index bf80d74a778..5bedd196bbf 100644 --- a/git-pack/tests/pack/data/input.rs +++ b/git-pack/tests/pack/data/input.rs @@ -182,7 +182,7 @@ mod lookup_ref_delta_objects { }), Ok(entry(base(), D_B)), ]; - let actual = LookupRefDeltaObjectsIter::new(input.into_iter(), |_, _| unreachable!("wont be called")) + let actual = LookupRefDeltaObjectsIter::new(input.into_iter(), |_, _| unreachable!("won't be called")) .collect::>(); for (actual, expected) in actual.into_iter().zip(expected.into_iter()) { assert_eq!(format!("{:?}", actual), format!("{:?}", expected)); diff --git a/git-packetline/tests/read/mod.rs b/git-packetline/tests/read/mod.rs index ca3d51f956b..f4e5b91e164 100644 --- a/git-packetline/tests/read/mod.rs +++ b/git-packetline/tests/read/mod.rs @@ -187,7 +187,7 @@ pub mod streaming_peek_iter { "it should read the second part of the identical file from the previously advanced reader" ); - // this reset is will cause actual io::Errors to occour + // this reset is will cause actual io::Errors to occur rd.reset(); let res = rd.read_line().await; assert_eq!( diff --git a/git-packetline/tests/read/sideband.rs b/git-packetline/tests/read/sideband.rs index ff364b5e0fc..5484b598216 100644 --- a/git-packetline/tests/read/sideband.rs +++ b/git-packetline/tests/read/sideband.rs @@ -183,7 +183,7 @@ async fn peek_past_a_delimiter_is_no_error() -> crate::Result { let res = reader.peek_data_line().await; assert!( res.is_none(), - "peeking past a flush packet is a 'natural' event that shold not cause an error" + "peeking past a flush packet is a 'natural' event that should not cause an error" ); Ok(()) } diff --git a/git-path/CHANGELOG.md b/git-path/CHANGELOG.md index 20b808ba443..1c29bdd6e26 100644 --- a/git-path/CHANGELOG.md +++ b/git-path/CHANGELOG.md @@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.0 (2022-07-22) + +### Changed (BREAKING) + + - `realpath()` handles `cwd` internally + This makes for more convenient usage in the common case. + +### Commit Statistics + + + + - 11 commits contributed to the release over the course of 32 calendar days. + - 33 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - `realpath()` handles `cwd` internally ([`dfa1e05`](https://github.com/Byron/gitoxide/commit/dfa1e05d3c983f1e8b1cb3b80d03608341187883)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - fix docs ([`4f8e3b1`](https://github.com/Byron/gitoxide/commit/4f8e3b169e57d599439c7abc861c82c08bcd92e3)) + - thanks clippy ([`7a2a31e`](https://github.com/Byron/gitoxide/commit/7a2a31e5758a2be8434f22cd9401ac00539f2bd9)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - avoid unwraps in tests as they are now stable ([`efa1423`](https://github.com/Byron/gitoxide/commit/efa14234c352b6b8417f0a42fc946e88f2eb52d3)) + - remove canonicalized-path abstraction ([`9496e55`](https://github.com/Byron/gitoxide/commit/9496e5512975825efebe0db86335d0d2dc8c9095)) +
+ ## 0.3.0 (2022-06-19) ### Bug Fixes (BREAKING) @@ -15,7 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 3 commits contributed to the release. + - 4 commits contributed to the release. - 6 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -27,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) - Fix git-paths tests; improve error handling. ([`9c00504`](https://github.com/Byron/gitoxide/commit/9c0050451f634a54e610c86199b5d7d393378878)) - docs for git-path ([`a520092`](https://github.com/Byron/gitoxide/commit/a52009244c9b1059ebb3d5dd472c25f9c49691f3)) - Remove `git-config` test utilities from `git-path`. ([`c9933c0`](https://github.com/Byron/gitoxide/commit/c9933c0b0f51d21dc8244b2acc33d7dc8a33f6ce)) diff --git a/git-path/Cargo.toml b/git-path/Cargo.toml index 7ea12582b5d..bc63239df8e 100644 --- a/git-path/Cargo.toml +++ b/git-path/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-path" -version = "0.3.0" +version = "0.4.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing paths and their conversions" diff --git a/git-pathspec/Cargo.toml b/git-pathspec/Cargo.toml index 0224338d4d9..24b8b5efcb4 100644 --- a/git-pathspec/Cargo.toml +++ b/git-pathspec/Cargo.toml @@ -13,3 +13,13 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +git-glob = { version = "^0.3.0", path = "../git-glob" } +git-attributes = { version = "^0.3.0", path = "../git-attributes" } + +bstr = { version = "0.2.13", default-features = false, features = ["std"]} +bitflags = "1.3.2" +thiserror = "1.0.26" + +[dev-dependencies] +git-testtools = { path = "../tests/tools" } +once_cell = "1.12.0" diff --git a/git-pathspec/README.md b/git-pathspec/README.md new file mode 100644 index 00000000000..47bcb5b62bd --- /dev/null +++ b/git-pathspec/README.md @@ -0,0 +1,16 @@ +# `git-pathspec` + +### Testing + +#### Fuzzing + +`cargo fuzz` is used for fuzzing, installable with `cargo install cargo-fuzz`. + +Targets can be listed with `cargo fuzz list` and executed via `cargo +nightly fuzz run `, +where `` can be `parse` for example. + +### Notes + +- There is one additional keyword that `git` can parse, but that this crate doesn't support yet: the `prefix` keyword + + [Here is a commit](https://github.com/git/git/commit/5be4efbefafcd5b81fe3d97e8395da1887b4902a) in which `prefix` is somewhat explained. diff --git a/git-pathspec/fuzz/.gitignore b/git-pathspec/fuzz/.gitignore new file mode 100644 index 00000000000..a0925114d61 --- /dev/null +++ b/git-pathspec/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +artifacts diff --git a/git-pathspec/fuzz/Cargo.lock b/git-pathspec/fuzz/Cargo.lock new file mode 100644 index 00000000000..b4f41b924a5 --- /dev/null +++ b/git-pathspec/fuzz/Cargo.lock @@ -0,0 +1,268 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "btoi" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c0869a9faa81f8bbf8102371105d6d0a7b79167a04c340b04ab16892246a11" +dependencies = [ + "num-traits", +] + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "compact_str" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b5c3ee2b4ffa00ac2b00d1645cd9229ade668139bccf95f15fadcf374127b" +dependencies = [ + "castaway", + "itoa", + "ryu", +] + +[[package]] +name = "git-attributes" +version = "0.3.0" +dependencies = [ + "bstr", + "compact_str", + "git-features", + "git-glob", + "git-path", + "git-quote", + "thiserror", + "unicode-bom", +] + +[[package]] +name = "git-features" +version = "0.22.0" +dependencies = [ + "git-hash", + "libc", +] + +[[package]] +name = "git-glob" +version = "0.3.1" +dependencies = [ + "bitflags", + "bstr", +] + +[[package]] +name = "git-hash" +version = "0.9.6" +dependencies = [ + "hex", + "quick-error", +] + +[[package]] +name = "git-path" +version = "0.4.0" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "git-pathspec" +version = "0.0.0" +dependencies = [ + "bitflags", + "bstr", + "git-attributes", + "git-glob", + "thiserror", +] + +[[package]] +name = "git-pathspec-fuzz" +version = "0.0.0" +dependencies = [ + "git-pathspec", + "libfuzzer-sys", +] + +[[package]] +name = "git-quote" +version = "0.2.0" +dependencies = [ + "bstr", + "btoi", + "quick-error", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336244aaeab6a12df46480dc585802aa743a72d66b11937844c61bbca84c991d" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-bom" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63ec69f541d875b783ca40184d655f2927c95f0bffd486faa83cd3ac3529ec32" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" diff --git a/git-pathspec/fuzz/Cargo.toml b/git-pathspec/fuzz/Cargo.toml new file mode 100644 index 00000000000..16240362973 --- /dev/null +++ b/git-pathspec/fuzz/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "git-pathspec-fuzz" +version = "0.0.0" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" + +[dependencies.git-pathspec] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[[bin]] +name = "parse" +path = "fuzz_targets/parse.rs" +test = false +doc = false diff --git a/git-pathspec/fuzz/fuzz_targets/parse.rs b/git-pathspec/fuzz/fuzz_targets/parse.rs new file mode 100644 index 00000000000..738cf24b183 --- /dev/null +++ b/git-pathspec/fuzz/fuzz_targets/parse.rs @@ -0,0 +1,6 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let _a = git_pathspec::parse(data); +}); diff --git a/git-pathspec/src/lib.rs b/git-pathspec/src/lib.rs index d7a83e4f525..ec3b57dd55b 100644 --- a/git-pathspec/src/lib.rs +++ b/git-pathspec/src/lib.rs @@ -1 +1,64 @@ +//! Parse [path specifications](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefpathspecapathspec) and +//! see if a path matches. #![forbid(unsafe_code, rust_2018_idioms)] +#![deny(missing_docs)] + +use bitflags::bitflags; +use bstr::BString; + +/// +pub mod parse; + +/// The output of a pathspec [parsing][parse()] operation. It can be used to match against a one or more paths. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct Pattern { + /// The path part of a pathspec. + /// + /// `:(top,literal,icase,attr,exclude)some/path` would yield `some/path`. + pub path: BString, + /// All magic signatures that were included in the pathspec. + pub signature: MagicSignature, + /// The search mode of the pathspec. + pub search_mode: MatchMode, + /// All attributes that were included in the `ATTR` part of the pathspec, if present. + /// + /// `:(attr:a=one b=):path` would yield attribute `a` and `b`. + pub attributes: Vec, +} + +bitflags! { + /// Flags to represent 'magic signatures' which are parsed behind colons, like `:top:`. + pub struct MagicSignature: u32 { + /// Matches patterns from the root of the repository + const TOP = 1 << 0; + /// Matches patterns in case insensitive mode + const ICASE = 1 << 1; + /// Excludes the matching patterns from the previous results + const EXCLUDE = 1 << 2; + } +} + +/// Parts of [magic signatures][MagicSignature] which don't stack as they all configure +/// the way path specs are matched. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum MatchMode { + /// Expand special characters like `*` similar to how the shell would do it. + /// + /// See [`PathAwareGlob`][MatchMode::PathAwareGlob] for the alternative. + ShellGlob, + /// Special characters in the pattern, like `*` or `?`, are treated literally, effectively turning off globbing. + Literal, + /// A single `*` will not match a `/` in the pattern, but a `**` will + PathAwareGlob, +} + +impl Default for MatchMode { + fn default() -> Self { + MatchMode::ShellGlob + } +} + +/// Parse a git-style pathspec into a [`Pattern`][Pattern]. +pub fn parse(input: &[u8]) -> Result { + Pattern::from_bytes(input) +} diff --git a/git-pathspec/src/parse.rs b/git-pathspec/src/parse.rs new file mode 100644 index 00000000000..469b26ea92a --- /dev/null +++ b/git-pathspec/src/parse.rs @@ -0,0 +1,229 @@ +use crate::{MagicSignature, MatchMode, Pattern}; +use bstr::{BStr, BString, ByteSlice, ByteVec}; +use std::borrow::Cow; + +/// The error returned by [parse()][crate::parse()]. +#[derive(thiserror::Error, Debug)] +#[allow(missing_docs)] +pub enum Error { + #[error("An empty string is not a valid pathspec")] + EmptyString, + #[error("Found {keyword:?} in signature, which is not a valid keyword")] + InvalidKeyword { keyword: BString }, + #[error("Unimplemented short keyword: {short_keyword:?}")] + Unimplemented { short_keyword: char }, + #[error("Missing ')' at the end of pathspec signature")] + MissingClosingParenthesis, + #[error("Attribute has non-ascii characters or starts with '-': {attribute:?}")] + InvalidAttribute { attribute: BString }, + #[error("Invalid character in attribute value: {character:?}")] + InvalidAttributeValue { character: char }, + #[error("Escape character '\\' is not allowed as the last character in an attribute value")] + TrailingEscapeCharacter, + #[error("Attribute specification cannot be empty")] + EmptyAttribute, + #[error("Only one attribute specification is allowed in the same pathspec")] + MultipleAttributeSpecifications, + #[error("'literal' and 'glob' keywords cannot be used together in the same pathspec")] + IncompatibleSearchModes, +} + +impl Pattern { + /// Try to parse a path-spec pattern from the given `input` bytes. + pub fn from_bytes(input: &[u8]) -> Result { + if input.is_empty() { + return Err(Error::EmptyString); + } + + let mut p = Pattern { + path: BString::default(), + signature: MagicSignature::empty(), + search_mode: MatchMode::ShellGlob, + attributes: Vec::new(), + }; + + let mut cursor = 0; + if input.first() == Some(&b':') { + cursor += 1; + p.signature |= parse_short_keywords(input, &mut cursor)?; + if let Some(b'(') = input.get(cursor) { + cursor += 1; + parse_long_keywords(input, &mut p, &mut cursor)?; + } + } + + p.path = BString::from(&input[cursor..]); + Ok(p) + } +} + +fn parse_short_keywords(input: &[u8], cursor: &mut usize) -> Result { + let unimplemented_chars = b"\"#%&'-',;<=>@_`~"; + + let mut signature = MagicSignature::empty(); + while let Some(&b) = input.get(*cursor) { + *cursor += 1; + signature |= match b { + b'/' => MagicSignature::TOP, + b'^' | b'!' => MagicSignature::EXCLUDE, + b':' => break, + _ if unimplemented_chars.contains(&b) => { + return Err(Error::Unimplemented { + short_keyword: b.into(), + }); + } + _ => { + *cursor -= 1; + break; + } + } + } + + Ok(signature) +} + +fn parse_long_keywords(input: &[u8], p: &mut Pattern, cursor: &mut usize) -> Result<(), Error> { + let end = input.find(")").ok_or(Error::MissingClosingParenthesis)?; + + let input = &input[*cursor..end]; + *cursor = end + 1; + + debug_assert_eq!(p.search_mode, MatchMode::default()); + + if input.is_empty() { + return Ok(()); + } + + split_on_non_escaped_char(input, b',', |keyword| { + let attr_prefix = b"attr:"; + match keyword { + b"attr" => {} + b"top" => p.signature |= MagicSignature::TOP, + b"icase" => p.signature |= MagicSignature::ICASE, + b"exclude" => p.signature |= MagicSignature::EXCLUDE, + b"literal" => match p.search_mode { + MatchMode::PathAwareGlob => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = MatchMode::Literal, + }, + b"glob" => match p.search_mode { + MatchMode::Literal => return Err(Error::IncompatibleSearchModes), + _ => p.search_mode = MatchMode::PathAwareGlob, + }, + _ if keyword.starts_with(attr_prefix) => { + if p.attributes.is_empty() { + p.attributes = parse_attributes(&keyword[attr_prefix.len()..])?; + } else { + return Err(Error::MultipleAttributeSpecifications); + } + } + _ => { + return Err(Error::InvalidKeyword { + keyword: BString::from(keyword), + }); + } + }; + Ok(()) + }) +} + +fn split_on_non_escaped_char( + input: &[u8], + split_char: u8, + mut f: impl FnMut(&[u8]) -> Result<(), Error>, +) -> Result<(), Error> { + let mut i = 0; + let mut last = 0; + for window in input.windows(2) { + i += 1; + if window[0] != b'\\' && window[1] == split_char { + let keyword = &input[last..i]; + f(keyword)?; + last = i + 1; + } + } + let last_keyword = &input[last..]; + f(last_keyword) +} + +fn parse_attributes(input: &[u8]) -> Result, Error> { + if input.is_empty() { + return Err(Error::EmptyAttribute); + } + + let unescaped = unescape_attribute_values(input.into())?; + + git_attributes::parse::Iter::new(unescaped.as_bstr()) + .map(|res| res.map(|v| v.to_owned())) + .collect::, _>>() + .map_err(|e| Error::InvalidAttribute { attribute: e.attribute }) +} + +fn unescape_attribute_values(input: &BStr) -> Result, Error> { + if !input.contains(&b'=') { + return Ok(Cow::Borrowed(input)); + } + + let mut out: Cow<'_, BStr> = Cow::Borrowed("".into()); + + for attr in input.split(|&c| c == b' ') { + let split_point = attr.find_byte(b'=').map_or_else(|| attr.len(), |i| i + 1); + let (name, value) = attr.split_at(split_point); + + if value.contains(&b'\\') { + let out = out.to_mut(); + out.push_str(name); + out.push_str(unescape_and_check_attr_value(value.into())?); + out.push(b' '); + } else { + check_attribute_value(value.as_bstr())?; + match out { + Cow::Borrowed(_) => { + let end = out.len() + attr.len() + 1; + out = Cow::Borrowed(&input[0..end.min(input.len())]); + } + Cow::Owned(_) => { + let out = out.to_mut(); + out.push_str(name); + out.push_str(value); + out.push(b' '); + } + } + } + } + + Ok(out) +} + +fn unescape_and_check_attr_value(value: &BStr) -> Result { + let mut out = BString::from(Vec::with_capacity(value.len())); + let mut bytes = value.iter(); + while let Some(mut b) = bytes.next().copied() { + if b == b'\\' { + b = *bytes.next().ok_or(Error::TrailingEscapeCharacter)?; + } + + out.push(validated_attr_value_byte(b)?); + } + Ok(out) +} + +fn check_attribute_value(input: &BStr) -> Result<(), Error> { + match input.iter().copied().find(|b| !is_valid_attr_value(*b)) { + Some(b) => Err(Error::InvalidAttributeValue { character: b as char }), + None => Ok(()), + } +} + +fn is_valid_attr_value(byte: u8) -> bool { + byte.is_ascii_alphanumeric() || b",-_".contains(&byte) +} + +fn validated_attr_value_byte(byte: u8) -> Result { + if is_valid_attr_value(byte) { + Ok(byte) + } else { + Err(Error::InvalidAttributeValue { + character: byte as char, + }) + } +} diff --git a/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh new file mode 100644 index 00000000000..b8092dee586 --- /dev/null +++ b/git-pathspec/tests/fixtures/generate_pathspec_baseline.sh @@ -0,0 +1,145 @@ +#!/bin/bash +set -eu -o pipefail + +git init; + +function baseline() { + local pathspec=$1 # first argument is the pathspec to test + + git ls-files "$pathspec" && status=0 || status=$? + { + echo "$pathspec" + echo "$status" + } >> baseline.git +} + +# success + +# special 'there is no pathspec' spec +baseline ':' + +# repeated_matcher_keywords +baseline ':(glob,glob)' +baseline ':(literal,literal)' +baseline ':(top,top)' +baseline ':(icase,icase)' +baseline ':(attr,attr)' +baseline ':!^(exclude,exclude)' + +# empty_signatures +baseline '.' +baseline 'some/path' +baseline ':some/path' +baseline ':()some/path' +baseline '::some/path' +baseline ':::some/path' +baseline ':():some/path' + +# whitespace_in_pathspec +baseline ' some/path' +baseline 'some/ path' +baseline 'some/path ' +baseline ': some/path' +baseline ': !some/path' +baseline ': :some/path' +baseline ': ()some/path' +baseline ':! some/path' + +# short_signatures +baseline ':/some/path' +baseline ':^some/path' +baseline ':!some/path' +baseline ':/!some/path' +baseline ':!/^/:some/path' + +# signatures_and_searchmodes +baseline ':(top)' +baseline ':(icase)' +baseline ':(attr)' +baseline ':(exclude)' +baseline ':(literal)' +baseline ':(glob)' +baseline ':(top,exclude)' +baseline ':(icase,literal)' +baseline ':!(literal)some/*path' +baseline ':(top,literal,icase,attr,exclude)some/path' +baseline ':(top,glob,icase,attr,exclude)some/path' + +# attributes_in_signature +baseline ':(attr:someAttr)' +baseline ':(attr:!someAttr)' +baseline ':(attr:-someAttr)' +baseline ':(attr:someAttr=value)' +baseline ':(attr:a=one b=)' +baseline ':(attr:a= b=two)' +baseline ':(attr:a=one b=two)' +baseline ':(attr:a=one b=two)' +baseline ':(attr:someAttr anotherAttr)' + +# attributes_with_escape_chars_in_state_values +baseline ':(attr:v=one\-)' +baseline ':(attr:v=one\_)' +baseline ':(attr:v=one\,)' +baseline ':(attr:v=one\,two\,three)' +baseline ':(attr:a=\d b= c=\d)' + +# failing + +#empty_input +baseline "" + +# invalid_short_signatures +baseline ':"()' +baseline ':#()' +baseline ':%()' +baseline ':&()' +baseline ":'()" +baseline ':,()' +baseline ':-()' +baseline ':;()' +baseline ':<()' +baseline ':=()' +baseline ':>()' +baseline ':@()' +baseline ':_()' +baseline ':`()' +baseline ':~()' + +# invalid_keywords +baseline ':( )some/path' +baseline ':(tp)some/path' +baseline ':(top, exclude)some/path' +baseline ':(top,exclude,icse)some/path' + +# invalid_attributes +baseline ':(attr:+invalidAttr)some/path' +baseline ':(attr:validAttr +invalidAttr)some/path' +baseline ':(attr:+invalidAttr,attr:valid)some/path' +baseline ':(attr:inva\lid)some/path' + +# invalid_attribute_values +baseline ':(attr:v=inva#lid)some/path' +baseline ':(attr:v=inva\\lid)some/path' +baseline ':(attr:v=invalid\\)some/path' +baseline ':(attr:v=invalid\#)some/path' +baseline ':(attr:v=inva\=lid)some/path' +baseline ':(attr:a=valid b=inva\#lid)some/path' +baseline ':(attr:v=val��)' +baseline ':(attr:pr=pre��x:,)�' + +# escape_character_at_end_of_attribute_value +baseline ':(attr:v=invalid\)some/path' +baseline ':(attr:v=invalid\ )some/path' +baseline ':(attr:v=invalid\ valid)some/path' + +# empty_attribute_specification +baseline ':(attr:)' + +# multiple_attribute_specifications +baseline ':(attr:one,attr:two)some/path' + +# missing_parentheses +baseline ':(top' + +# glob_and_literal_keywords_present +baseline ':(glob,literal)some/path' diff --git a/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz new file mode 100644 index 00000000000..35e00f42ded --- /dev/null +++ b/git-pathspec/tests/fixtures/generated-archives/generate_pathspec_baseline.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:505e0eed20808737e0f3f8b993e54e3b2b9b9d124e1615a003336ce34c60b939 +size 9484 diff --git a/git-pathspec/tests/pathspec.rs b/git-pathspec/tests/pathspec.rs new file mode 100644 index 00000000000..784562e3576 --- /dev/null +++ b/git-pathspec/tests/pathspec.rs @@ -0,0 +1,512 @@ +pub use git_testtools::Result; + +mod parse { + use bstr::{BStr, BString, ByteSlice}; + use git_attributes::State; + use git_pathspec::{MagicSignature, MatchMode, Pattern}; + use once_cell::sync::Lazy; + use std::collections::HashMap; + + #[derive(Debug, Clone, PartialEq, Eq)] + struct PatternForTesting { + path: BString, + signature: MagicSignature, + search_mode: MatchMode, + attributes: Vec<(BString, State)>, + } + + impl From for PatternForTesting { + fn from(p: Pattern) -> Self { + PatternForTesting { + path: p.path, + signature: p.signature, + search_mode: p.search_mode, + attributes: p + .attributes + .into_iter() + .map(|attr| (attr.name.as_str().into(), attr.state)) + .collect(), + } + } + } + + static BASELINE: Lazy> = Lazy::new(|| { + let base = git_testtools::scripted_fixture_repo_read_only("generate_pathspec_baseline.sh").unwrap(); + + (|| -> crate::Result<_> { + let mut map = HashMap::new(); + let baseline = std::fs::read(base.join("baseline.git"))?; + let mut lines = baseline.lines(); + while let Some(spec) = lines.next() { + let exit_code = lines.next().expect("two lines per baseline").to_str()?.parse()?; + map.insert(spec.into(), exit_code); + } + Ok(map) + })() + .unwrap() + }); + + #[test] + fn baseline() { + for (pattern, exit_code) in BASELINE.iter() { + let res = git_pathspec::parse(pattern); + assert_eq!( + res.is_ok(), + *exit_code == 0, + "{pattern:?} disagrees with baseline: {res:?}" + ) + } + } + + mod succeed { + use crate::parse::{ + check_valid_inputs, pat, pat_with_attrs, pat_with_path, pat_with_path_and_sig, pat_with_search_mode, + pat_with_sig, + }; + use git_attributes::State; + use git_pathspec::{MagicSignature, MatchMode}; + + #[test] + fn there_is_no_pathspec_pathspec() { + check_valid_inputs(Some((":", pat_with_attrs(vec![])))); + } + + #[test] + fn repeated_matcher_keywords() { + let input = vec![ + (":(glob,glob)", pat_with_search_mode(MatchMode::PathAwareGlob)), + (":(literal,literal)", pat_with_search_mode(MatchMode::Literal)), + (":(top,top)", pat_with_sig(MagicSignature::TOP)), + (":(icase,icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr,attr)", pat_with_attrs(vec![])), + (":!^(exclude,exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + ]; + + check_valid_inputs(input); + } + + #[test] + fn empty_signatures() { + let inputs = vec![ + (".", pat_with_path(".")), + ("some/path", pat_with_path("some/path")), + (":some/path", pat_with_path("some/path")), + (":()some/path", pat_with_path("some/path")), + ("::some/path", pat_with_path("some/path")), + (":::some/path", pat_with_path(":some/path")), + (":():some/path", pat_with_path(":some/path")), + ]; + + check_valid_inputs(inputs) + } + + #[test] + fn whitespace_in_pathspec() { + let inputs = vec![ + (" some/path", pat_with_path(" some/path")), + ("some/ path", pat_with_path("some/ path")), + ("some/path ", pat_with_path("some/path ")), + (": some/path", pat_with_path(" some/path")), + (": !some/path", pat_with_path(" !some/path")), + (": :some/path", pat_with_path(" :some/path")), + (": ()some/path", pat_with_path(" ()some/path")), + ( + ":! some/path", + pat_with_path_and_sig(" some/path", MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) + } + + #[test] + fn short_signatures() { + let inputs = vec![ + (":/some/path", pat_with_path_and_sig("some/path", MagicSignature::TOP)), + ( + ":^some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":!some/path", + pat_with_path_and_sig("some/path", MagicSignature::EXCLUDE), + ), + ( + ":/!some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":!/^/:some/path", + pat_with_path_and_sig("some/path", MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ]; + + check_valid_inputs(inputs) + } + + #[test] + fn signatures_and_searchmodes() { + let inputs = vec![ + (":(top)", pat_with_sig(MagicSignature::TOP)), + (":(icase)", pat_with_sig(MagicSignature::ICASE)), + (":(attr)", pat_with_path("")), + (":(exclude)", pat_with_sig(MagicSignature::EXCLUDE)), + (":(literal)", pat_with_search_mode(MatchMode::Literal)), + (":(glob)", pat_with_search_mode(MatchMode::PathAwareGlob)), + ( + ":(top,exclude)", + pat_with_sig(MagicSignature::TOP | MagicSignature::EXCLUDE), + ), + ( + ":(icase,literal)", + pat("", MagicSignature::ICASE, MatchMode::Literal, vec![]), + ), + ( + ":!(literal)some/*path", + pat("some/*path", MagicSignature::EXCLUDE, MatchMode::Literal, vec![]), + ), + ( + ":(top,literal,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), MatchMode::Literal, vec![]), + ), + ( + ":(top,glob,icase,attr,exclude)some/path", + pat("some/path", MagicSignature::all(), MatchMode::PathAwareGlob, vec![]), + ), + ]; + + check_valid_inputs(inputs); + } + + #[test] + fn attributes_in_signature() { + let inputs = vec![ + (":(attr:someAttr)", pat_with_attrs(vec![("someAttr", State::Set)])), + ( + ":(attr:!someAttr)", + pat_with_attrs(vec![("someAttr", State::Unspecified)]), + ), + (":(attr:-someAttr)", pat_with_attrs(vec![("someAttr", State::Unset)])), + ( + ":(attr:someAttr=value)", + pat_with_attrs(vec![("someAttr", State::Value("value".into()))]), + ), + ( + ":(attr:a=one b=)", + pat_with_attrs(vec![("a", State::Value("one".into())), ("b", State::Value("".into()))]), + ), + ( + ":(attr:a= b=two)", + pat_with_attrs(vec![("a", State::Value("".into())), ("b", State::Value("two".into()))]), + ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), + ( + ":(attr:a=one b=two)", + pat_with_attrs(vec![ + ("a", State::Value("one".into())), + ("b", State::Value("two".into())), + ]), + ), + ( + ":(attr:someAttr anotherAttr)", + pat_with_attrs(vec![("someAttr", State::Set), ("anotherAttr", State::Set)]), + ), + ]; + + check_valid_inputs(inputs) + } + + #[test] + fn attributes_with_escape_chars_in_state_values() { + let inputs = vec![ + ( + r":(attr:v=one\-)", + pat_with_attrs(vec![("v", State::Value(r"one-".into()))]), + ), + ( + r":(attr:v=one\_)", + pat_with_attrs(vec![("v", State::Value(r"one_".into()))]), + ), + ( + r":(attr:v=one\,)", + pat_with_attrs(vec![("v", State::Value(r"one,".into()))]), + ), + ( + r":(attr:v=one\,two\,three)", + pat_with_attrs(vec![("v", State::Value(r"one,two,three".into()))]), + ), + ( + r":(attr:a=\d b= c=\d)", + pat_with_attrs(vec![ + ("a", State::Value(r"d".into())), + ("b", State::Value(r"".into())), + ("c", State::Value(r"d".into())), + ]), + ), + ]; + + check_valid_inputs(inputs) + } + } + + mod fail { + use crate::parse::check_against_baseline; + use git_pathspec::parse::Error; + + #[test] + fn empty_input() { + let input = ""; + + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyString)); + } + + #[test] + fn invalid_short_signatures() { + let inputs = vec![ + ":\"()", ":#()", ":%()", ":&()", ":'()", ":,()", ":-()", ":;()", ":<()", ":=()", ":>()", ":@()", + ":_()", ":`()", ":~()", + ]; + + inputs.into_iter().for_each(|input| { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::Unimplemented { .. })); + }); + } + + #[test] + fn invalid_keywords() { + let inputs = vec![ + ":( )some/path", + ":(tp)some/path", + ":(top, exclude)some/path", + ":(top,exclude,icse)some/path", + ]; + + inputs.into_iter().for_each(|input| { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::InvalidKeyword { .. })); + }); + } + + #[test] + fn invalid_attributes() { + let inputs = vec![ + ":(attr:+invalidAttr)some/path", + ":(attr:validAttr +invalidAttr)some/path", + ":(attr:+invalidAttr,attr:valid)some/path", + r":(attr:inva\lid)some/path", + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!(matches!(output.unwrap_err(), Error::InvalidAttribute { .. })); + } + } + + #[test] + fn invalid_attribute_values() { + let inputs = vec![ + r":(attr:v=inva#lid)some/path", + r":(attr:v=inva\\lid)some/path", + r":(attr:v=invalid\\)some/path", + r":(attr:v=invalid\#)some/path", + r":(attr:v=inva\=lid)some/path", + r":(attr:a=valid b=inva\#lid)some/path", + ":(attr:v=val��)", + ":(attr:pr=pre��x:,)�", + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!( + matches!(output.unwrap_err(), Error::InvalidAttributeValue { .. }), + "Errors did not match for pathspec: {}", + input + ); + } + } + + #[test] + fn escape_character_at_end_of_attribute_value() { + let inputs = vec![ + r":(attr:v=invalid\)some/path", + r":(attr:v=invalid\ )some/path", + r":(attr:v=invalid\ valid)some/path", + ]; + + for input in inputs { + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err(), "This pathspec did not produce an error {}", input); + assert!(matches!(output.unwrap_err(), Error::TrailingEscapeCharacter)); + } + } + + #[test] + fn empty_attribute_specification() { + let input = ":(attr:)"; + + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::EmptyAttribute)); + } + + #[test] + fn multiple_attribute_specifications() { + let input = ":(attr:one,attr:two)some/path"; + + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MultipleAttributeSpecifications)); + } + + #[test] + fn missing_parentheses() { + let input = ":(top"; + + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::MissingClosingParenthesis { .. })); + } + + #[test] + fn glob_and_literal_keywords_present() { + let input = ":(glob,literal)some/path"; + + assert!( + !check_against_baseline(input), + "This pathspec is valid in git: {}", + input + ); + + let output = git_pathspec::parse(input.as_bytes()); + assert!(output.is_err()); + assert!(matches!(output.unwrap_err(), Error::IncompatibleSearchModes)); + } + } + + fn check_valid_inputs<'a>(inputs: impl IntoIterator) { + inputs.into_iter().for_each(|(input, expected)| { + assert!( + check_against_baseline(input), + "This pathspec is invalid in git: {}", + input + ); + + let pattern: PatternForTesting = git_pathspec::parse(input.as_bytes()) + .unwrap_or_else(|_| panic!("parsing should not fail with pathspec {}", input)) + .into(); + assert_eq!(pattern, expected, "while checking input: \"{}\"", input); + }); + } + + fn pat_with_path(path: &str) -> PatternForTesting { + pat_with_path_and_sig(path, MagicSignature::empty()) + } + + fn pat_with_path_and_sig(path: &str, signature: MagicSignature) -> PatternForTesting { + pat(path, signature, MatchMode::ShellGlob, vec![]) + } + + fn pat_with_sig(signature: MagicSignature) -> PatternForTesting { + pat("", signature, MatchMode::ShellGlob, vec![]) + } + + fn pat_with_attrs(attrs: Vec<(&'static str, State)>) -> PatternForTesting { + pat("", MagicSignature::empty(), MatchMode::ShellGlob, attrs) + } + + fn pat_with_search_mode(search_mode: MatchMode) -> PatternForTesting { + pat("", MagicSignature::empty(), search_mode, vec![]) + } + + fn pat( + path: &str, + signature: MagicSignature, + search_mode: MatchMode, + attributes: Vec<(&str, State)>, + ) -> PatternForTesting { + PatternForTesting { + path: path.into(), + signature, + search_mode, + attributes: attributes + .into_iter() + .map(|(attr, state)| (attr.into(), state)) + .collect(), + } + } + + fn check_against_baseline(pathspec: &str) -> bool { + let key: &BStr = pathspec.into(); + let base = BASELINE + .get(key) + .unwrap_or_else(|| panic!("missing baseline for pathspec: {:?}", pathspec)); + *base == 0 + } +} diff --git a/git-protocol/CHANGELOG.md b/git-protocol/CHANGELOG.md index 4498a1869dd..69195434b25 100644 --- a/git-protocol/CHANGELOG.md +++ b/git-protocol/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.18.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.17.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +38,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +50,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-protocol/Cargo.toml b/git-protocol/Cargo.toml index 43ab77525f4..5d8874e3616 100644 --- a/git-protocol/Cargo.toml +++ b/git-protocol/Cargo.toml @@ -39,9 +39,9 @@ path = "tests/async-protocol.rs" required-features = ["async-client"] [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["progress"] } +git-features = { version = "^0.22.0", path = "../git-features", features = ["progress"] } git-transport = { version = "^0.19.0", path = "../git-transport" } -git-hash = { version = "^0.9.5", path = "../git-hash" } +git-hash = { version = "^0.9.6", path = "../git-hash" } git-credentials = { version = "^0.3.0", path = "../git-credentials" } quick-error = "2.0.0" diff --git a/git-protocol/src/fetch/response/async_io.rs b/git-protocol/src/fetch/response/async_io.rs index b68e8a160d5..6472b5b67a0 100644 --- a/git-protocol/src/fetch/response/async_io.rs +++ b/git-protocol/src/fetch/response/async_io.rs @@ -46,7 +46,7 @@ impl Response { line.clear(); let peeked_line = match reader.peek_data_line().await { Some(Ok(Ok(line))) => String::from_utf8_lossy(line), - // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes + // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know // the arguments sent to the server and count response lines based on intricate knowledge on how the diff --git a/git-protocol/src/fetch/response/blocking_io.rs b/git-protocol/src/fetch/response/blocking_io.rs index 8befc72e64b..f63143b3794 100644 --- a/git-protocol/src/fetch/response/blocking_io.rs +++ b/git-protocol/src/fetch/response/blocking_io.rs @@ -45,7 +45,7 @@ impl Response { line.clear(); let peeked_line = match reader.peek_data_line() { Some(Ok(Ok(line))) => String::from_utf8_lossy(line), - // This special case (hang/block forver) deals with a single NAK being a legitimate EOF sometimes + // This special case (hang/block forever) deals with a single NAK being a legitimate EOF sometimes // Note that this might block forever in stateful connections as there it's not really clear // if something will be following or not by just looking at the response. Instead you have to know // the arguments sent to the server and count response lines based on intricate knowledge on how the diff --git a/git-protocol/src/fetch/tests/arguments.rs b/git-protocol/src/fetch/tests/arguments.rs index c2c102246b7..76c8bd0acaf 100644 --- a/git-protocol/src/fetch/tests/arguments.rs +++ b/git-protocol/src/fetch/tests/arguments.rs @@ -239,7 +239,7 @@ mod v2 { 0009done 0000" .as_bstr(), - "we filter features/capabilities without value as these apparently sholdn't be listed (remote dies otherwise)" + "we filter features/capabilities without value as these apparently shouldn't be listed (remote dies otherwise)" ); } diff --git a/git-ref/CHANGELOG.md b/git-ref/CHANGELOG.md index 30053692158..6f541f5f00e 100644 --- a/git-ref/CHANGELOG.md +++ b/git-ref/CHANGELOG.md @@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.15.0 (2022-07-22) + +### New Features + + - Add `store::WriteRefLog::Always` to unconditionally write reflogs. + +### Changed (BREAKING) + + - `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. + That way, the name is actually directly usable in most methods that + require a validated name as input. + +### Commit Statistics + + + + - 10 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - `Target(Ref)?::try_name()` now returns `Option<&FullNameRef>`. ([`0f753e9`](https://github.com/Byron/gitoxide/commit/0f753e922e313f735ed267f913366771e9de1111)) + - Add `store::WriteRefLog::Always` to unconditionally write reflogs. ([`4607a18`](https://github.com/Byron/gitoxide/commit/4607a18e24b8270c182663a434b79dff8761db0e)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - generally avoid using `target_os = "windows"` in favor of `cfg(windows)` and negations ([`91d5402`](https://github.com/Byron/gitoxide/commit/91d54026a61c2aae5e3e1341d271acf16478cd83)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.14.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +60,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +72,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608))
diff --git a/git-ref/Cargo.toml b/git-ref/Cargo.toml index 5c53df38570..96519173520 100644 --- a/git-ref/Cargo.toml +++ b/git-ref/Cargo.toml @@ -25,12 +25,12 @@ required-features = ["internal-testing-git-features-parallel"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features", features = ["walkdir"]} -git-path = { version = "^0.3.0", path = "../git-path" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-features = { version = "^0.22.0", path = "../git-features", features = ["walkdir"]} +git-path = { version = "^0.4.0", path = "../git-path" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } git-validate = { version = "^0.5.4", path = "../git-validate" } -git-actor = { version = "^0.10.1", path = "../git-actor" } +git-actor = { version = "^0.11.0", path = "../git-actor" } git-lock = { version = "^2.0.0", path = "../git-lock" } git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } diff --git a/git-ref/src/peel.rs b/git-ref/src/peel.rs index 503b94f1044..361c7d4d7b6 100644 --- a/git-ref/src/peel.rs +++ b/git-ref/src/peel.rs @@ -32,7 +32,7 @@ pub mod to_id { display("Refusing to follow more than {} levels of indirection", max_depth) } Find(err: Box) { - display("An error occurred when trying to resolve an object a refererence points to") + display("An error occurred when trying to resolve an object a reference points to") from() source(&**err) } diff --git a/git-ref/src/store/file/find.rs b/git-ref/src/store/file/find.rs index 27ee7c4b695..c672cc72941 100644 --- a/git-ref/src/store/file/find.rs +++ b/git-ref/src/store/file/find.rs @@ -315,7 +315,7 @@ pub mod existing { #[allow(missing_docs)] pub enum Error { Find(err: find::Error) { - display("An error occured while trying to find a reference") + display("An error occurred while trying to find a reference") from() source(err) } diff --git a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs index e5ff078607f..410523db504 100644 --- a/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs +++ b/git-ref/src/store/file/loose/reflog/create_or_update/tests.rs @@ -60,7 +60,7 @@ fn missing_reflog_creates_it_even_if_similarly_named_empty_dir_exists_and_append let new = hex_to_id("28ce6a8b26aa170e1de65536fe8abe1832bd3242"); let committer = Signature { name: "committer".into(), - email: "commiter@example.com".into(), + email: "committer@example.com".into(), time: Time { seconds_since_unix_epoch: 1234, offset_in_seconds: 1800, diff --git a/git-ref/src/store/file/transaction/prepare.rs b/git-ref/src/store/file/transaction/prepare.rs index d83f66ff820..a2c4f57f663 100644 --- a/git-ref/src/store/file/transaction/prepare.rs +++ b/git-ref/src/store/file/transaction/prepare.rs @@ -59,7 +59,7 @@ impl<'s> Transaction<'s> { ) .map_err(|err| Error::LockAcquire { err, - full_name: "borrowchk wont allow change.name()".into(), + full_name: "borrowchk won't allow change.name()".into(), })?; let existing_ref = existing_ref?; match (&expected, &existing_ref) { @@ -105,7 +105,7 @@ impl<'s> Transaction<'s> { ) .map_err(|err| Error::LockAcquire { err, - full_name: "borrowchk wont allow change.name() and this will be corrected by caller".into(), + full_name: "borrowchk won't allow change.name() and this will be corrected by caller".into(), })?; let existing_ref = existing_ref?; diff --git a/git-ref/src/store/packed/transaction.rs b/git-ref/src/store/packed/transaction.rs index a53efaf1428..45f88debd6c 100644 --- a/git-ref/src/store/packed/transaction.rs +++ b/git-ref/src/store/packed/transaction.rs @@ -260,7 +260,7 @@ pub mod commit { #[allow(missing_docs)] pub enum Error { Commit(err: git_lock::commit::Error) { - display("Changes to the resource could not be comitted") + display("Changes to the resource could not be committed") from() source(err) } diff --git a/git-ref/src/target.rs b/git-ref/src/target.rs index d2bfabd6020..8e38d75a1fd 100644 --- a/git-ref/src/target.rs +++ b/git-ref/src/target.rs @@ -1,9 +1,8 @@ use std::{convert::TryFrom, fmt}; use git_hash::{oid, ObjectId}; -use git_object::bstr::BStr; -use crate::{FullName, Kind, Target, TargetRef}; +use crate::{FullName, FullNameRef, Kind, Target, TargetRef}; impl<'a> TargetRef<'a> { /// Returns the kind of the target the ref is pointing to. @@ -28,14 +27,14 @@ impl<'a> TargetRef<'a> { } } /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. - pub fn try_name(&self) -> Option<&BStr> { + pub fn try_name(&self) -> Option<&FullNameRef> { match self { - TargetRef::Symbolic(path) => Some(path.as_bstr()), + TargetRef::Symbolic(name) => Some(name), TargetRef::Peeled(_) => None, } } /// Convert this instance into an owned version, without consuming it. - pub fn into_owned(self) -> crate::Target { + pub fn into_owned(self) -> Target { self.into() } } @@ -58,10 +57,10 @@ impl Target { } /// Interpret this owned Target as shared Target - pub fn to_ref(&self) -> crate::TargetRef<'_> { + pub fn to_ref(&self) -> TargetRef<'_> { match self { - Target::Peeled(oid) => crate::TargetRef::Peeled(oid), - Target::Symbolic(name) => crate::TargetRef::Symbolic(name.as_ref()), + Target::Peeled(oid) => TargetRef::Peeled(oid), + Target::Symbolic(name) => TargetRef::Symbolic(name.as_ref()), } } @@ -95,28 +94,28 @@ impl Target { } } /// Interpret this target as name of the reference it points to which maybe `None` if it an object id. - pub fn try_name(&self) -> Option<&BStr> { + pub fn try_name(&self) -> Option<&FullNameRef> { match self { - Target::Symbolic(name) => Some(name.as_bstr()), + Target::Symbolic(name) => Some(name.as_ref()), Target::Peeled(_) => None, } } } -impl<'a> From> for Target { - fn from(src: crate::TargetRef<'a>) -> Self { +impl<'a> From> for Target { + fn from(src: TargetRef<'a>) -> Self { match src { - crate::TargetRef::Peeled(oid) => Target::Peeled(oid.to_owned()), - crate::TargetRef::Symbolic(name) => Target::Symbolic(name.to_owned()), + TargetRef::Peeled(oid) => Target::Peeled(oid.to_owned()), + TargetRef::Symbolic(name) => Target::Symbolic(name.to_owned()), } } } -impl<'a> PartialEq> for Target { - fn eq(&self, other: &crate::TargetRef<'a>) -> bool { +impl<'a> PartialEq> for Target { + fn eq(&self, other: &TargetRef<'a>) -> bool { match (self, other) { - (Target::Peeled(lhs), crate::TargetRef::Peeled(rhs)) => lhs == rhs, - (Target::Symbolic(lhs), crate::TargetRef::Symbolic(rhs)) => lhs.as_bstr() == rhs.as_bstr(), + (Target::Peeled(lhs), TargetRef::Peeled(rhs)) => lhs == rhs, + (Target::Symbolic(lhs), TargetRef::Symbolic(rhs)) => lhs.as_bstr() == rhs.as_bstr(), _ => false, } } diff --git a/git-ref/tests/file/reference.rs b/git-ref/tests/file/reference.rs index 222d009907c..70a910dc48f 100644 --- a/git-ref/tests/file/reference.rs +++ b/git-ref/tests/file/reference.rs @@ -193,7 +193,10 @@ mod parse { Reference::try_from_path("HEAD".try_into().expect("valid static name"), $input).unwrap(); assert_eq!(reference.kind(), $kind); assert_eq!(reference.target.to_ref().try_id(), $id); - assert_eq!(reference.target.to_ref().try_name(), $ref); + assert_eq!( + reference.target.to_ref().try_name().map(|n| n.as_bstr()), + $ref + ); } }; } diff --git a/git-ref/tests/file/store/iter.rs b/git-ref/tests/file/store/iter.rs index 5323b4ce697..5566d6aebb2 100644 --- a/git-ref/tests/file/store/iter.rs +++ b/git-ref/tests/file/store/iter.rs @@ -265,7 +265,7 @@ fn loose_iter_with_broken_refs() -> crate::Result { #[cfg(windows)] let msg = "The reference at 'refs\\broken' could not be instantiated"; assert_eq!( - actual[first_error].as_ref().expect_err("unparseable ref").to_string(), + actual[first_error].as_ref().expect_err("unparsable ref").to_string(), msg ); let ref_paths: Vec<_> = actual diff --git a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs index 546d8fb90d9..9efaf76b7c5 100644 --- a/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +++ b/git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs @@ -443,7 +443,10 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { let head = store.find_loose(&edits[0].name)?; assert_eq!(head.name.as_bstr(), "HEAD"); assert_eq!(head.kind(), git_ref::Kind::Symbolic); - assert_eq!(head.target.to_ref().try_name(), Some(referent.as_bytes().as_bstr())); + assert_eq!( + head.target.to_ref().try_name().map(|n| n.as_bstr()), + Some(referent.as_bytes().as_bstr()) + ); assert!(!head.log_exists(&store), "no reflog is written for symbolic ref"); assert!(store.try_find_loose(referent)?.is_none(), "referent wasn't created"); @@ -506,7 +509,7 @@ fn symbolic_head_missing_referent_then_update_referent() -> crate::Result { "head is still symbolic, not detached" ); assert_eq!( - head.target.to_ref().try_name(), + head.target.to_ref().try_name().map(|n| n.as_bstr()), Some(referent.as_bytes().as_bstr()), "it still points to the referent" ); diff --git a/git-repository/CHANGELOG.md b/git-repository/CHANGELOG.md index 4b72c184571..073bc13b9cd 100644 --- a/git-repository/CHANGELOG.md +++ b/git-repository/CHANGELOG.md @@ -5,6 +5,153 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.20.0 (2022-07-22) + +### New Features + + - respect `safe.directory`. + In practice, this code will rarely be hit as it would require very + strict settings that forbid any operation within a non-owned git + directory. + - permissions for configuration. + It provides fine-grained control over what sources to load. + - `git-config` is now accessible in `git-repository::config`. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. + - repository now initializes global configuration files and resolves includes + - resolve includes in local repository configuration + - `config::Snapshot::trusted_path()` to obtain trustworthy paths. + We also apply trust-based config query during initialization to assure + we don't use paths which aren't owned by the current user. + - `Repository::config_snapshot()` to access configuration values. + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. + - respect `core.logallrefupdates` configuration setting. + +### Changed (BREAKING) + + - Make `SignatureRef<'_>` mandatory for editing reference changelogs. + If defaults are desired, these can be set by the caller. + - `Repository::committer()` now returns an `Option`, see `::committer_or_default()` for a method that doesn't. + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + +### New Features (BREAKING) + + - Support for `lossy` load mode. + There is a lot of breaking changes as `file::from_paths::Options` now + became `file::init::Options`, and the same goes for the error type. + - change mostily internal uses of [u8] to BString/BStr + +### Commit Statistics + + + + - 72 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 16 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 5 times to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - Make lossy-configuration configurable ([`b0e4da6`](https://github.com/Byron/gitoxide/commit/b0e4da621114d188a73b9f40757f59564da3c079)) + - tests for author/committer/user ([`6d2e53c`](https://github.com/Byron/gitoxide/commit/6d2e53c32145770e8314f0879d6d769090667f90)) + - refactor ([`4dc6594`](https://github.com/Byron/gitoxide/commit/4dc6594686478d9d6cd09e2ba02048624c3577e7)) + - default user signature now with 'now' time, like advertised. ([`ad40202`](https://github.com/Byron/gitoxide/commit/ad4020224114127612eaf5d1e732baf81818812d)) + - Make `SignatureRef<'_>` mandatory for editing reference changelogs. ([`68f4bc2`](https://github.com/Byron/gitoxide/commit/68f4bc2570d455c762da7e3d675b9b507cec69bb)) + - `Repository::committer()` now returns an `Option`, see `::committer_or_default()` for a method that doesn't. ([`f932cea`](https://github.com/Byron/gitoxide/commit/f932cea68ece997f711add3368db53aeb8cdf064)) + - first sketch of using configuration and environment variables for author/committer ([`330d0a1`](https://github.com/Byron/gitoxide/commit/330d0a19d54aabac868b76ef6281fffdbdcde53c)) + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - a first sketch on how identity management could look like. ([`780f14f`](https://github.com/Byron/gitoxide/commit/780f14f5c270802e51cf039639c2fbdb5ac5a85e)) + - refactor ([`4f61312`](https://github.com/Byron/gitoxide/commit/4f613120f9f761b86fc7eb16227d08fc5b9828d8)) + - respect `safe.directory`. ([`1b765ec`](https://github.com/Byron/gitoxide/commit/1b765ec6ae70d1f4cc5a885b3c68d6f3335ba827)) + - permissions for configuration. ([`840d9a3`](https://github.com/Byron/gitoxide/commit/840d9a3018d11146bb8e80fc92693c65eb534d91)) + - `git-config` is now accessible in `git-repository::config`. ([`6570808`](https://github.com/Byron/gitoxide/commit/657080829867d9dcb0c9b9cb6c1c8126c4df3783)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - adapt to changes in `git-config` ([`b52b540`](https://github.com/Byron/gitoxide/commit/b52b5407638adef2216aeb4215a7c0437d6ee2d5)) + - adapt to changes in `git-config` ([`3c57344`](https://github.com/Byron/gitoxide/commit/3c57344325ad20ae891824cd8791d2d17f4148e5)) + - adjust to changes in `git-config` for greater efficiency ([`e9afede`](https://github.com/Byron/gitoxide/commit/e9afedeebafb70d81a8fa2e6dc320b387e6ee926)) + - adapt to changes in git-config ([`14ba883`](https://github.com/Byron/gitoxide/commit/14ba8834b8738817d2bfb0ca66d1fb86fc8f3075)) + - refactor ([`95ed219`](https://github.com/Byron/gitoxide/commit/95ed219c5f414b6fa96d80eacf297f24d823a4fe)) + - repository now initializes global configuration files and resolves includes ([`ebedd03`](https://github.com/Byron/gitoxide/commit/ebedd03e119aa5d46da07e577bfccad621eaecb5)) + - adapt to changes in git-config ([`627a0e1`](https://github.com/Byron/gitoxide/commit/627a0e1e12e15a060a70d880ffdfb05f1f7db36c)) + - only a select few early config attributes must be repo-local ([`be0971c`](https://github.com/Byron/gitoxide/commit/be0971c5191f7866063ebcc0407331e683cf7d68)) + - resolve includes in local repository configuration ([`de8572f`](https://github.com/Byron/gitoxide/commit/de8572ff2ced9422832e1ba433955c33f0994675)) + - Adjust to changes in `git-config` ([`30cbe29`](https://github.com/Byron/gitoxide/commit/30cbe299860d84b5aeffced54839529dc068a8c7)) + - solve cycle between config and ref-store ([`1679d56`](https://github.com/Byron/gitoxide/commit/1679d5684cec852b39a0d51d5001fbcecafc6748)) + - adapt to changes in `git-config` ([`7f41f1e`](https://github.com/Byron/gitoxide/commit/7f41f1e267c9cbf87061821dd2f0edb6b0984226)) + - prepare for resolving a complete config… ([`9be1dd6`](https://github.com/Byron/gitoxide/commit/9be1dd6f7cdb9aea7c85df896e370b3c40f5e4ec)) + - Allow to configure a different filter for configuration section. ([`e512ab0`](https://github.com/Byron/gitoxide/commit/e512ab09477629957e469719f05e7de65955f3db)) + - adjust to changes in `git-config` ([`ca89d0d`](https://github.com/Byron/gitoxide/commit/ca89d0d4785ec4d66a0a4316fbc74be63dcc0f48)) + - refactor ([`5723730`](https://github.com/Byron/gitoxide/commit/57237303d9ae8a746c64d05ecedf3d43a0d041f6)) + - load configuration with trust information, needs cleanup ([`d8e41e2`](https://github.com/Byron/gitoxide/commit/d8e41e20de741c3d4701d862033cf50582a0d015)) + - Add remaining config access, and an escape hatch. ([`81715ff`](https://github.com/Byron/gitoxide/commit/81715ffca33e40cb6e37fff25baa68fca70c4844)) + - `config::Snapshot::trusted_path()` to obtain trustworthy paths. ([`d5a48b8`](https://github.com/Byron/gitoxide/commit/d5a48b82230b047434610550aacd2dc741b4b5f0)) + - `Debug` for `config::Snapshot`. ([`2c21956`](https://github.com/Byron/gitoxide/commit/2c2195640818319795a93e73bed79174fa358f55)) + - `Repository::config_snapshot()` to access configuration values. ([`5f9bfa8`](https://github.com/Byron/gitoxide/commit/5f9bfa89ceb61f484be80575b0379bbf9d7a36b3)) + - adapt to changes in `git-config` ([`c9423db`](https://github.com/Byron/gitoxide/commit/c9423db5381064296d22f48b532f29d3e8162ce9)) + - Support for `lossy` load mode. ([`d003c0f`](https://github.com/Byron/gitoxide/commit/d003c0f139d61e3bd998a0283a9c7af25a60db02)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - adjust to changes in `git-config` ([`81e63cc`](https://github.com/Byron/gitoxide/commit/81e63cc3590301ca32c1172b358ffb45a13b6a8f)) + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. ([`7f67b23`](https://github.com/Byron/gitoxide/commit/7f67b23b9462b805591b1fe5a8406f8d7404f372)) + - respect `core.logallrefupdates` configuration setting. ([`e263e13`](https://github.com/Byron/gitoxide/commit/e263e13d312e41aa1481d104fa79ede509fbe1c5)) + - adapt to breaking changes in `git-config` ([`a02d575`](https://github.com/Byron/gitoxide/commit/a02d5759c14eb1d42fe24e61afc32a4cd463d1b7)) + - adapt to changes in `git-config` ([`858dc8b`](https://github.com/Byron/gitoxide/commit/858dc8b1b721ce5a45a76d9a97935cb0daf61e1a)) + - adjustments due to breaking changes in `git-config` ([`924f148`](https://github.com/Byron/gitoxide/commit/924f14879bd14ca1ff13fdd6ccafe43d6de01b68)) + - adjustments for breaking changes in `git-config` ([`d3841ee`](https://github.com/Byron/gitoxide/commit/d3841ee752e426bf58130cde1e4e40215ccb8f33)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + - adjustments due to breaking changes in `git-config` ([`07bf647`](https://github.com/Byron/gitoxide/commit/07bf647c788afbe5a595ed3091744459e3623f13)) + - adapt to changes in `git-config` ([`363a826`](https://github.com/Byron/gitoxide/commit/363a826144ad59518b5c1a3dbbc82d04e4fc062d)) + - adjust to changes in `git-config` ([`920d56e`](https://github.com/Byron/gitoxide/commit/920d56e4f5141eeb536956cdc5fac042ddee3525)) + - adjustments required due to changed in `git-config` ([`41bfd3b`](https://github.com/Byron/gitoxide/commit/41bfd3b4122e37370d268608b60cb00a671a8879)) + - adjust to breaking changes in `git-config` ([`5b66202`](https://github.com/Byron/gitoxide/commit/5b66202d96bf664ed84755afc3ec49c301ecd62c)) + - adjustments due to breaking changes in `git_path` ([`4420ae9`](https://github.com/Byron/gitoxide/commit/4420ae932d5b20a9662a6d36353a27111b5cd672)) + * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - thanks clippy ([`fddc720`](https://github.com/Byron/gitoxide/commit/fddc7206476423a6964d61acd060305572ecd02b)) + - thanks clippy ([`0346aaa`](https://github.com/Byron/gitoxide/commit/0346aaaeccfe18a443410652cada7b14eb34d8b9)) + - thanks clippy ([`b630543`](https://github.com/Byron/gitoxide/commit/b630543669af5289508ce066bd026e2b9a9d5044)) + - thanks clippy ([`d9eb34c`](https://github.com/Byron/gitoxide/commit/d9eb34cad7a69b56f10eec5b88b86ebd6a9a74af)) + - avoid extra copies of paths using `PathCursor` tool during repo init ([`5771721`](https://github.com/Byron/gitoxide/commit/5771721ff5f86dd808d9961126c9c4a61867507c)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - change mostily internal uses of [u8] to BString/BStr ([`311d4b4`](https://github.com/Byron/gitoxide/commit/311d4b447daf8d4364670382a20901468748d34d)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - Use git_path::realpath in all places that allow it right now ([`229dc91`](https://github.com/Byron/gitoxide/commit/229dc917fc7d9241b85e5818260a6fbdd3a5daaa)) + - fix build warnings ([`84109f5`](https://github.com/Byron/gitoxide/commit/84109f54877d045f8ccc7a380c012802708c2f1e)) + - Make a note to be sure we use the home-dir correctly in git-repository; avoid `dirs` crate ([`0e8cf19`](https://github.com/Byron/gitoxide/commit/0e8cf19d7f742f9400afa4863d302ba18a452adc)) + - adjust to changes in git-config ([`7a1678d`](https://github.com/Byron/gitoxide/commit/7a1678d8da0c361e0a0cc4380a04ebfb3ce5035d)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.19.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +163,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 10 commits contributed to the release over the course of 20 calendar days. + - 13 commits contributed to the release over the course of 20 calendar days. - 20 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -28,14 +175,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-worktree v0.3.0, git-repository v0.19.0 ([`0d8e856`](https://github.com/Byron/gitoxide/commit/0d8e8566dc5c6955487d67e235f86fbc75a3a88a)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) - fix docs ([`daef221`](https://github.com/Byron/gitoxide/commit/daef2215cc6c4fddded5229951e8ac71c395468d)) - refactor ([`b27a8c2`](https://github.com/Byron/gitoxide/commit/b27a8c243cdc14730478c2a94cafdc8ccf5c60d3)) - refactor ([`06e96a4`](https://github.com/Byron/gitoxide/commit/06e96a435d820a1ef1e567bf93e7b9ca5fa74829)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) - Make `realpath()` easier to use by introducing `realpath_opt()`. ([`266d437`](https://github.com/Byron/gitoxide/commit/266d4379e9132fd7dd21e6c8fccb36e125069d6e)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - Add discovery opt env-overrides & env discovery helpers ([`e521d39`](https://github.com/Byron/gitoxide/commit/e521d39e1b0f4849280bae1527bf28977eec5093)) - Merge branch 'davidkna-admin-sec' ([`3d0e2c2`](https://github.com/Byron/gitoxide/commit/3d0e2c2d4ebdbe3dff01846aac3375128353a2e1))
diff --git a/git-repository/Cargo.toml b/git-repository/Cargo.toml index 4adf5161d0c..3b8fa8fab84 100644 --- a/git-repository/Cargo.toml +++ b/git-repository/Cargo.toml @@ -36,7 +36,7 @@ blocking-http-transport = ["git-transport/http-client-curl"] ## Provide additional non-networked functionality like `git-url` and `git-diff`. local = [ "git-url", "git-diff" ] ## Turns on access to all stable features that are unrelated to networking. -one-stop-shop = [ "local", "local-time-support" ] +one-stop-shop = [ "local" ] #! ### Other @@ -45,8 +45,6 @@ serde1 = ["git-pack/serde1", "git-object/serde1", "git-protocol/serde1", "git-tr ## Activate other features that maximize performance, like usage of threads, `zlib-ng` and access to caching in object databases. ## **Note** that max-performance = ["git-features/parallel", "git-features/zlib-ng-compat", "git-pack/pack-cache-lru-static", "git-pack/pack-cache-lru-dynamic"] -## Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. -local-time-support = ["git-actor/local-time-support"] ## Re-export stability tier 2 crates for convenience and make `Repository` struct fields with types from these crates publicly accessible. ## Doing so is less stable than the stability tier 1 that `git-repository` is a member of. unstable = ["git-index", "git-mailmap", "git-glob", "git-credentials", "git-attributes"] @@ -62,29 +60,30 @@ git-tempfile = { version = "^2.0.0", path = "../git-tempfile" } git-lock = { version = "^2.0.0", path = "../git-lock" } git-validate = { version = "^0.5.4", path = "../git-validate" } git-sec = { version = "^0.3.0", path = "../git-sec", features = ["thiserror"] } +git-date = { version = "^0.0.2", path = "../git-date" } git-config = { version = "^0.6.0", path = "../git-config" } git-odb = { version = "^0.31.0", path = "../git-odb" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-actor = { version = "^0.10.1", path = "../git-actor" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-actor = { version = "^0.11.0", path = "../git-actor" } git-pack = { version = "^0.21.0", path = "../git-pack", features = ["object-cache-dynamic"] } -git-revision = { version = "^0.2.1", path = "../git-revision" } +git-revision = { version = "^0.3.0", path = "../git-revision" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } git-url = { version = "^0.7.0", path = "../git-url", optional = true } -git-traverse = { version = "^0.15.0", path = "../git-traverse" } +git-traverse = { version = "^0.16.0", path = "../git-traverse" } git-protocol = { version = "^0.18.0", path = "../git-protocol", optional = true } git-transport = { version = "^0.19.0", path = "../git-transport", optional = true } -git-diff = { version = "^0.16.0", path = "../git-diff", optional = true } -git-mailmap = { version = "^0.2.0", path = "../git-mailmap", optional = true } -git-features = { version = "^0.21.1", path = "../git-features", features = ["progress"] } +git-diff = { version = "^0.17.0", path = "../git-diff", optional = true } +git-mailmap = { version = "^0.3.0", path = "../git-mailmap", optional = true } +git-features = { version = "^0.22.0", path = "../git-features", features = ["progress", "once_cell"] } # unstable only git-attributes = { version = "^0.3.0", path = "../git-attributes", optional = true } -git-glob = { version = "^0.3.0", path = "../git-glob", optional = true } +git-glob = { version = "^0.3.1", path = "../git-glob", optional = true } git-credentials = { version = "^0.3.0", path = "../git-credentials", optional = true } -git-index = { version = "^0.3.0", path = "../git-index", optional = true } +git-index = { version = "^0.4.0", path = "../git-index", optional = true } git-worktree = { version = "^0.4.0", path = "../git-worktree" } signal-hook = { version = "0.3.9", default-features = false } @@ -103,6 +102,7 @@ git-testtools = { path = "../tests/tools" } is_ci = "1.1.1" anyhow = "1" tempfile = "3.2.0" +serial_test = "0.8.0" [package.metadata.docs.rs] features = ["document-features", "max-performance", "one-stop-shop", "unstable", "blocking-network-client"] diff --git a/git-repository/src/config.rs b/git-repository/src/config.rs deleted file mode 100644 index 9776e3629dc..00000000000 --- a/git-repository/src/config.rs +++ /dev/null @@ -1,199 +0,0 @@ -use crate::{bstr::BString, permission}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Could not open repository conifguration file")] - Open(#[from] git_config::file::from_paths::Error), - #[error("Cannot handle objects formatted as {:?}", .name)] - UnsupportedObjectFormat { name: BString }, - #[error("The value for '{}' cannot be empty", .key)] - EmptyValue { key: &'static str }, - #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] - CoreAbbrev { value: BString, max: u8 }, - #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] - DecodeBoolean { key: String, value: BString }, - #[error(transparent)] - PathInterpolation(#[from] git_config::path::interpolate::Error), -} - -/// Utility type to keep pre-obtained configuration values. -#[derive(Debug, Clone)] -pub(crate) struct Cache { - pub resolved: crate::Config, - /// The hex-length to assume when shortening object ids. If `None`, it should be computed based on the approximate object count. - pub hex_len: Option, - /// true if the repository is designated as 'bare', without work tree. - pub is_bare: bool, - /// The type of hash to use. - pub object_hash: git_hash::Kind, - /// If true, multi-pack indices, whether present or not, may be used by the object database. - pub use_multi_pack_index: bool, - /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. - pub reflog: Option, - /// If true, we are on a case-insensitive file system. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub ignore_case: bool, - /// The path to the user-level excludes file to ignore certain files in the worktree. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub excludes_file: Option, - /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - xdg_config_home_env: permission::env_var::Resource, - /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - home_env: permission::env_var::Resource, - // TODO: make core.precomposeUnicode available as well. -} - -mod cache { - use std::{convert::TryFrom, path::PathBuf}; - - use git_config::{path, Boolean, File, Integer}; - - use super::{Cache, Error}; - use crate::{bstr::ByteSlice, permission}; - - impl Cache { - pub fn new( - git_dir: &std::path::Path, - xdg_config_home_env: permission::env_var::Resource, - home_env: permission::env_var::Resource, - git_install_dir: Option<&std::path::Path>, - ) -> Result { - let home = std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|home| home_env.check(home).ok().flatten()); - // TODO: don't forget to use the canonicalized home for initializing the stacked config. - // like git here: https://github.com/git/git/blob/master/config.c#L208:L208 - let config = { - let mut buf = Vec::with_capacity(512); - File::from_path_with_buf(&git_dir.join("config"), &mut buf)? - }; - - let is_bare = config_bool(&config, "core.bare", false)?; - let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; - let ignore_case = config_bool(&config, "core.ignoreCase", false)?; - let excludes_file = config - .path("core", None, "excludesFile") - .map(|p| { - p.interpolate(path::interpolate::Options { - git_install_dir, - home_dir: home.as_deref(), - home_for_user: Some(git_config::path::interpolate::home_for_user), - }) - .map(|p| p.into_owned()) - }) - .transpose()?; - let repo_format_version = config - .value::("core", None, "repositoryFormatVersion") - .map_or(0, |v| v.to_decimal().unwrap_or_default()); - let object_hash = (repo_format_version != 1) - .then(|| Ok(git_hash::Kind::Sha1)) - .or_else(|| { - config.string("extensions", None, "objectFormat").map(|format| { - if format.as_ref().eq_ignore_ascii_case(b"sha1") { - Ok(git_hash::Kind::Sha1) - } else { - Err(Error::UnsupportedObjectFormat { - name: format.to_vec().into(), - }) - } - }) - }) - .transpose()? - .unwrap_or(git_hash::Kind::Sha1); - let reflog = config.string("core", None, "logallrefupdates").map(|val| { - (val.eq_ignore_ascii_case(b"always")) - .then(|| git_ref::store::WriteReflog::Always) - .or_else(|| { - git_config::Boolean::try_from(val) - .ok() - .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) - }) - .unwrap_or(git_ref::store::WriteReflog::Disable) - }); - - let mut hex_len = None; - if let Some(hex_len_str) = config.string("core", None, "abbrev") { - if hex_len_str.trim().is_empty() { - return Err(Error::EmptyValue { key: "core.abbrev" }); - } - if !hex_len_str.eq_ignore_ascii_case(b"auto") { - let value_bytes = hex_len_str.as_ref(); - if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { - hex_len = object_hash.len_in_hex().into(); - } else { - let value = Integer::try_from(value_bytes) - .map_err(|_| Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - })? - .to_decimal() - .ok_or_else(|| Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - })?; - if value < 4 || value as usize > object_hash.len_in_hex() { - return Err(Error::CoreAbbrev { - value: hex_len_str.clone().into_owned(), - max: object_hash.len_in_hex() as u8, - }); - } - hex_len = Some(value as usize); - } - } - } - - Ok(Cache { - resolved: config.into(), - use_multi_pack_index, - object_hash, - reflog, - is_bare, - ignore_case, - hex_len, - excludes_file, - xdg_config_home_env, - home_env, - }) - } - - /// Return a path by using the `$XDF_CONFIG_HOME` or `$HOME/.config/…` environment variables locations. - #[cfg_attr(not(feature = "git-index"), allow(dead_code))] - pub fn xdg_config_path( - &self, - resource_file_name: &str, - ) -> Result, git_sec::permission::Error> { - std::env::var_os("XDG_CONFIG_HOME") - .map(|path| (path, &self.xdg_config_home_env)) - .or_else(|| std::env::var_os("HOME").map(|path| (path, &self.home_env))) - .and_then(|(base, permission)| { - let resource = std::path::PathBuf::from(base).join("git").join(resource_file_name); - permission.check(resource).transpose() - }) - .transpose() - } - - /// Return the home directory if we are allowed to read it and if it is set in the environment. - /// - /// We never fail for here even if the permission is set to deny as we `git-config` will fail later - /// if it actually wants to use the home directory - we don't want to fail prematurely. - #[cfg(feature = "git-mailmap")] - pub fn home_dir(&self) -> Option { - std::env::var_os("HOME") - .map(PathBuf::from) - .and_then(|path| self.home_env.check(path).ok().flatten()) - } - } - - fn config_bool(config: &File<'_>, key: &str, default: bool) -> Result { - let (section, key) = key.split_once('.').expect("valid section.key format"); - config - .boolean(section, None, key) - .unwrap_or(Ok(default)) - .map_err(|err| Error::DecodeBoolean { - value: err.input, - key: key.into(), - }) - } -} diff --git a/git-repository/src/config/cache.rs b/git-repository/src/config/cache.rs new file mode 100644 index 00000000000..c38d0835793 --- /dev/null +++ b/git-repository/src/config/cache.rs @@ -0,0 +1,307 @@ +use std::{convert::TryFrom, path::PathBuf}; + +use git_config::{Boolean, Integer}; + +use super::{Cache, Error}; +use crate::{bstr::ByteSlice, repository, repository::identity}; + +/// A utility to deal with the cyclic dependency between the ref store and the configuration. The ref-store needs the +/// object hash kind, and the configuration needs the current branch name to resolve conditional includes with `onbranch`. +#[allow(dead_code)] +pub(crate) struct StageOne { + git_dir_config: git_config::File<'static>, + buf: Vec, + + is_bare: bool, + lossy: Option, + pub object_hash: git_hash::Kind, + pub reflog: Option, +} + +/// Initialization +impl StageOne { + pub fn new(git_dir: &std::path::Path, git_dir_trust: git_sec::Trust, lossy: Option) -> Result { + let mut buf = Vec::with_capacity(512); + let config = { + let config_path = git_dir.join("config"); + std::io::copy(&mut std::fs::File::open(&config_path)?, &mut buf)?; + + git_config::File::from_bytes_owned( + &mut buf, + git_config::file::Metadata::from(git_config::Source::Local) + .at(config_path) + .with(git_dir_trust), + git_config::file::init::Options { + includes: git_config::file::includes::Options::no_follow(), + ..base_options(lossy) + }, + )? + }; + + let is_bare = config_bool(&config, "core.bare", false)?; + let repo_format_version = config + .value::("core", None, "repositoryFormatVersion") + .map_or(0, |v| v.to_decimal().unwrap_or_default()); + let object_hash = (repo_format_version != 1) + .then(|| Ok(git_hash::Kind::Sha1)) + .or_else(|| { + config.string("extensions", None, "objectFormat").map(|format| { + if format.as_ref().eq_ignore_ascii_case(b"sha1") { + Ok(git_hash::Kind::Sha1) + } else { + Err(Error::UnsupportedObjectFormat { + name: format.to_vec().into(), + }) + } + }) + }) + .transpose()? + .unwrap_or(git_hash::Kind::Sha1); + + let reflog = query_refupdates(&config); + Ok(StageOne { + git_dir_config: config, + buf, + is_bare, + lossy, + object_hash, + reflog, + }) + } +} + +/// Initialization +impl Cache { + #[allow(clippy::too_many_arguments)] + pub fn from_stage_one( + StageOne { + git_dir_config, + mut buf, + lossy, + is_bare, + object_hash, + reflog: _, + }: StageOne, + git_dir: &std::path::Path, + branch_name: Option<&git_ref::FullNameRef>, + mut filter_config_section: fn(&git_config::file::Metadata) -> bool, + git_install_dir: Option<&std::path::Path>, + home: Option<&std::path::Path>, + repository::permissions::Environment { + git_prefix, + home: home_env, + xdg_config_home: xdg_config_home_env, + }: repository::permissions::Environment, + repository::permissions::Config { + system: use_system, + git: use_git, + user: use_user, + env: use_env, + includes: use_includes, + }: repository::permissions::Config, + ) -> Result { + let options = git_config::file::init::Options { + includes: if use_includes { + git_config::file::includes::Options::follow( + interpolate_context(git_install_dir, home), + git_config::file::includes::conditional::Context { + git_dir: git_dir.into(), + branch_name, + }, + ) + } else { + git_config::file::includes::Options::no_follow() + }, + ..base_options(lossy) + }; + + let config = { + let home_env = &home_env; + let xdg_config_home_env = &xdg_config_home_env; + let git_prefix = &git_prefix; + let metas = [git_config::source::Kind::System, git_config::source::Kind::Global] + .iter() + .flat_map(|kind| kind.sources()) + .filter_map(|source| { + match source { + git_config::Source::System if !use_system => return None, + git_config::Source::Git if !use_git => return None, + git_config::Source::User if !use_user => return None, + _ => {} + } + let path = source + .storage_location(&mut |name| { + match name { + git_ if git_.starts_with("GIT_") => Some(git_prefix), + "XDG_CONFIG_HOME" => Some(xdg_config_home_env), + "HOME" => Some(home_env), + _ => None, + } + .and_then(|perm| std::env::var_os(name).and_then(|val| perm.check(val).ok().flatten())) + }) + .map(|p| p.into_owned()); + + git_config::file::Metadata { + path, + source: *source, + level: 0, + trust: git_sec::Trust::Full, + } + .into() + }); + + let err_on_nonexisting_paths = false; + let mut globals = git_config::File::from_paths_metadata_buf( + metas, + &mut buf, + err_on_nonexisting_paths, + git_config::file::init::Options { + includes: git_config::file::includes::Options::no_follow(), + ..options + }, + ) + .map_err(|err| match err { + git_config::file::init::from_paths::Error::Init(err) => Error::from(err), + git_config::file::init::from_paths::Error::Io(err) => err.into(), + })? + .unwrap_or_default(); + + globals.append(git_dir_config); + globals.resolve_includes(options)?; + if use_env { + globals.append(git_config::File::from_env(options)?.unwrap_or_default()); + } + globals + }; + + let excludes_file = config + .path_filter("core", None, "excludesFile", &mut filter_config_section) + .map(|p| p.interpolate(options.includes.interpolate).map(|p| p.into_owned())) + .transpose()?; + + let mut hex_len = None; + if let Some(hex_len_str) = config.string("core", None, "abbrev") { + if hex_len_str.trim().is_empty() { + return Err(Error::EmptyValue { key: "core.abbrev" }); + } + if !hex_len_str.eq_ignore_ascii_case(b"auto") { + let value_bytes = hex_len_str.as_ref(); + if let Ok(false) = Boolean::try_from(value_bytes).map(Into::into) { + hex_len = object_hash.len_in_hex().into(); + } else { + let value = Integer::try_from(value_bytes) + .map_err(|_| Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + })? + .to_decimal() + .ok_or_else(|| Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + })?; + if value < 4 || value as usize > object_hash.len_in_hex() { + return Err(Error::CoreAbbrev { + value: hex_len_str.clone().into_owned(), + max: object_hash.len_in_hex() as u8, + }); + } + hex_len = Some(value as usize); + } + } + } + + let reflog = query_refupdates(&config); + let ignore_case = config_bool(&config, "core.ignoreCase", false)?; + let use_multi_pack_index = config_bool(&config, "core.multiPackIndex", true)?; + Ok(Cache { + resolved: config.into(), + use_multi_pack_index, + object_hash, + reflog, + is_bare, + ignore_case, + hex_len, + excludes_file, + xdg_config_home_env, + home_env, + personas: Default::default(), + git_prefix, + }) + } + + /// Return a path by using the `$XDF_CONFIG_HOME` or `$HOME/.config/…` environment variables locations. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub fn xdg_config_path( + &self, + resource_file_name: &str, + ) -> Result, git_sec::permission::Error> { + std::env::var_os("XDG_CONFIG_HOME") + .map(|path| (path, &self.xdg_config_home_env)) + .or_else(|| std::env::var_os("HOME").map(|path| (path, &self.home_env))) + .and_then(|(base, permission)| { + let resource = std::path::PathBuf::from(base).join("git").join(resource_file_name); + permission.check(resource).transpose() + }) + .transpose() + } + + /// Return the home directory if we are allowed to read it and if it is set in the environment. + /// + /// We never fail for here even if the permission is set to deny as we `git-config` will fail later + /// if it actually wants to use the home directory - we don't want to fail prematurely. + pub fn home_dir(&self) -> Option { + std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|path| self.home_env.check(path).ok().flatten()) + } +} + +/// Access +impl Cache { + pub fn personas(&self) -> &identity::Personas { + self.personas + .get_or_init(|| identity::Personas::from_config_and_env(&self.resolved, &self.git_prefix)) + } +} + +pub(crate) fn interpolate_context<'a>( + git_install_dir: Option<&'a std::path::Path>, + home_dir: Option<&'a std::path::Path>, +) -> git_config::path::interpolate::Context<'a> { + git_config::path::interpolate::Context { + git_install_dir, + home_dir, + home_for_user: Some(git_config::path::interpolate::home_for_user), // TODO: figure out how to configure this + } +} + +fn base_options(lossy: Option) -> git_config::file::init::Options<'static> { + git_config::file::init::Options { + lossy: lossy.unwrap_or(!cfg!(debug_assertions)), + ..Default::default() + } +} + +fn config_bool(config: &git_config::File<'_>, key: &str, default: bool) -> Result { + let (section, key) = key.split_once('.').expect("valid section.key format"); + config + .boolean(section, None, key) + .unwrap_or(Ok(default)) + .map_err(|err| Error::DecodeBoolean { + value: err.input, + key: key.into(), + }) +} + +fn query_refupdates(config: &git_config::File<'static>) -> Option { + config.string("core", None, "logallrefupdates").map(|val| { + (val.eq_ignore_ascii_case(b"always")) + .then(|| git_ref::store::WriteReflog::Always) + .or_else(|| { + git_config::Boolean::try_from(val) + .ok() + .and_then(|b| b.is_true().then(|| git_ref::store::WriteReflog::Normal)) + }) + .unwrap_or(git_ref::store::WriteReflog::Disable) + }) +} diff --git a/git-repository/src/config/mod.rs b/git-repository/src/config/mod.rs new file mode 100644 index 00000000000..627a558ff02 --- /dev/null +++ b/git-repository/src/config/mod.rs @@ -0,0 +1,76 @@ +pub use git_config::*; +use git_features::threading::OnceCell; + +use crate::{bstr::BString, permission, repository::identity, Repository}; + +pub(crate) mod cache; +mod snapshot; + +/// A platform to access configuration values as read from disk. +/// +/// Note that these values won't update even if the underlying file(s) change. +pub struct Snapshot<'repo> { + pub(crate) repo: &'repo Repository, +} + +pub(crate) mod section { + pub fn is_trusted(meta: &git_config::file::Metadata) -> bool { + meta.trust == git_sec::Trust::Full || meta.source.kind() != git_config::source::Kind::Repository + } +} + +/// The error returned when failing to initialize the repository configuration. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Could not read configuration file")] + Io(#[from] std::io::Error), + #[error(transparent)] + Init(#[from] git_config::file::init::Error), + #[error(transparent)] + ResolveIncludes(#[from] git_config::file::includes::Error), + #[error(transparent)] + FromEnv(#[from] git_config::file::init::from_env::Error), + #[error("Cannot handle objects formatted as {:?}", .name)] + UnsupportedObjectFormat { name: BString }, + #[error("The value for '{}' cannot be empty", .key)] + EmptyValue { key: &'static str }, + #[error("Invalid value for 'core.abbrev' = '{}'. It must be between 4 and {}", .value, .max)] + CoreAbbrev { value: BString, max: u8 }, + #[error("Value '{}' at key '{}' could not be decoded as boolean", .value, .key)] + DecodeBoolean { key: String, value: BString }, + #[error(transparent)] + PathInterpolation(#[from] git_config::path::interpolate::Error), +} + +/// Utility type to keep pre-obtained configuration values. +#[derive(Debug, Clone)] +pub(crate) struct Cache { + pub resolved: crate::Config, + /// The hex-length to assume when shortening object ids. If `None`, it should be computed based on the approximate object count. + pub hex_len: Option, + /// true if the repository is designated as 'bare', without work tree. + pub is_bare: bool, + /// The type of hash to use. + pub object_hash: git_hash::Kind, + /// If true, multi-pack indices, whether present or not, may be used by the object database. + pub use_multi_pack_index: bool, + /// The representation of `core.logallrefupdates`, or `None` if the variable wasn't set. + pub reflog: Option, + /// identities for later use, lazy initialization. + pub personas: OnceCell, + /// If true, we are on a case-insensitive file system. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub ignore_case: bool, + /// The path to the user-level excludes file to ignore certain files in the worktree. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + pub excludes_file: Option, + /// Define how we can use values obtained with `xdg_config(…)` and its `XDG_CONFIG_HOME` variable. + #[cfg_attr(not(feature = "git-index"), allow(dead_code))] + xdg_config_home_env: permission::env_var::Resource, + /// Define how we can use values obtained with `xdg_config(…)`. and its `HOME` variable. + home_env: permission::env_var::Resource, + /// How to use git-prefixed environment variables + git_prefix: permission::env_var::Resource, + // TODO: make core.precomposeUnicode available as well. +} diff --git a/git-repository/src/config/snapshot.rs b/git-repository/src/config/snapshot.rs new file mode 100644 index 00000000000..7bbcb31a88d --- /dev/null +++ b/git-repository/src/config/snapshot.rs @@ -0,0 +1,105 @@ +use std::{ + borrow::Cow, + fmt::{Debug, Formatter}, +}; + +use crate::{ + bstr::BStr, + config::{cache::interpolate_context, Snapshot}, +}; + +/// Access configuration values, frozen in time, using a `key` which is a `.` separated string of up to +/// three tokens, namely `section_name.[subsection_name.]value_name`, like `core.bare` or `remote.origin.url`. +/// +/// Note that single-value methods always return the last value found, which is the one set most recently in the +/// hierarchy of configuration files, aka 'last one wins'. +impl<'repo> Snapshot<'repo> { + /// Return the boolean at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// boolean. + /// + /// For a non-degenerating version, use [`try_boolean(…)`][Self::try_boolean()]. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn boolean(&self, key: &str) -> Option { + self.try_boolean(key).and_then(Result::ok) + } + + /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_boolean(&self, key: &str) -> Option> { + let key = git_config::parse::key(key)?; + self.repo + .config + .resolved + .boolean(key.section_name, key.subsection_name, key.value_name) + } + + /// Return the resolved integer at `key`, or `None` if there is no such value or if the value can't be interpreted as + /// integer or exceeded the value range. + /// + /// For a non-degenerating version, use [`try_integer(…)`][Self::try_integer()]. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn integer(&self, key: &str) -> Option { + self.try_integer(key).and_then(Result::ok) + } + + /// Like [`integer()`][Self::integer()], but it will report an error if the value couldn't be interpreted as boolean. + pub fn try_integer(&self, key: &str) -> Option> { + let key = git_config::parse::key(key)?; + self.repo + .config + .resolved + .integer(key.section_name, key.subsection_name, key.value_name) + } + + /// Return the string at `key`, or `None` if there is no such value. + /// + /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + pub fn string(&self, key: &str) -> Option> { + let key = git_config::parse::key(key)?; + self.repo + .config + .resolved + .string(key.section_name, key.subsection_name, key.value_name) + } + + /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value + /// or if no value was found in a trusted file. + /// An error occurs if the path could not be interpolated to its final value. + pub fn trusted_path( + &self, + key: &str, + ) -> Option, git_config::path::interpolate::Error>> { + let key = git_config::parse::key(key)?; + let path = self.repo.config.resolved.path_filter( + key.section_name, + key.subsection_name, + key.value_name, + &mut self + .repo + .options + .filter_config_section + .unwrap_or(crate::config::section::is_trusted), + )?; + + let install_dir = self.repo.install_dir().ok(); + let home = self.repo.config.home_dir(); + Some(path.interpolate(interpolate_context(install_dir.as_deref(), home.as_deref()))) + } +} + +/// Utilities and additional access +impl<'repo> Snapshot<'repo> { + /// Returns the underlying configuration implementation for a complete API, despite being a little less convenient. + /// + /// It's expected that more functionality will move up depending on demand. + pub fn plumbing(&self) -> &git_config::File<'static> { + &self.repo.config.resolved + } +} + +impl Debug for Snapshot<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.repo.config.resolved.to_string()) + } +} diff --git a/git-repository/src/create.rs b/git-repository/src/create.rs index 8d53336d54d..8fb1c2ed5f0 100644 --- a/git-repository/src/create.rs +++ b/git-repository/src/create.rs @@ -1,12 +1,13 @@ -use git_config::parse::section; -use git_discover::DOT_GIT_DIR; -use std::convert::TryFrom; use std::{ + convert::TryFrom, fs::{self, OpenOptions}, io::Write, path::{Path, PathBuf}, }; +use git_config::parse::section; +use git_discover::DOT_GIT_DIR; + /// The error used in [`into()`]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] @@ -102,7 +103,7 @@ pub struct Options { /// If true, the repository will be a bare repository without a worktree. pub bare: bool, - /// If set, use these filesytem capabilities to populate the respective git-config fields. + /// If set, use these filesystem capabilities to populate the respective git-config fields. /// If `None`, the directory will be probed. pub fs_capabilities: Option, } diff --git a/git-repository/src/id.rs b/git-repository/src/id.rs index 34e74d35ab1..db10f534c3d 100644 --- a/git-repository/src/id.rs +++ b/git-repository/src/id.rs @@ -147,7 +147,7 @@ pub mod ancestors { error_on_missing_commit: bool, // TODO: tests /// After iteration this flag is true if the iteration was stopped prematurely due to missing parent commits. - /// Note that this flag won't be `Some` if any iteration error occours, which is the case if + /// Note that this flag won't be `Some` if any iteration error occurs, which is the case if /// [`error_on_missing_commit()`][Iter::error_on_missing_commit()] was called. /// /// This happens if a repository is a shallow clone. @@ -157,7 +157,7 @@ pub mod ancestors { impl<'repo> Iter<'repo> { // TODO: tests - /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typicall happens + /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typically happens /// when operating on a shallow clone and thus is non-critical by default. /// /// Check the [`is_shallow`][Iter::is_shallow] field once the iteration ended otherwise to learn if a shallow commit graph diff --git a/git-repository/src/lib.rs b/git-repository/src/lib.rs index ea100128695..76da8548b3e 100644 --- a/git-repository/src/lib.rs +++ b/git-repository/src/lib.rs @@ -88,6 +88,7 @@ //! * [`url`] //! * [`actor`] //! * [`bstr`][bstr] +//! * [`date`] //! * [`mod@discover`] //! * [`index`] //! * [`glob`] @@ -129,6 +130,8 @@ pub use git_actor as actor; pub use git_attributes as attrs; #[cfg(all(feature = "unstable", feature = "git-credentials"))] pub use git_credentials as credentials; +#[cfg(feature = "unstable")] +pub use git_date as date; #[cfg(all(feature = "unstable", feature = "git-diff"))] pub use git_diff as diff; use git_features::threading::OwnShared; @@ -261,6 +264,11 @@ pub fn open(directory: impl Into) -> Result, options: open::Options) -> Result { + ThreadSafeRepository::open_opts(directory, options).map(Into::into) +} + /// pub mod permission { /// @@ -278,7 +286,7 @@ pub mod permission { } /// pub mod permissions { - pub use crate::repository::permissions::Environment; + pub use crate::repository::permissions::{Config, Environment}; } pub use repository::permissions::Permissions; @@ -289,7 +297,7 @@ pub mod create; pub mod open; /// -mod config; +pub mod config; /// pub mod mailmap { @@ -418,6 +426,12 @@ pub mod discover { /// Try to open a git repository in `directory` and search upwards through its parents until one is found, /// while applying `options`. Then use the `trust_map` to determine which of our own repository options to use /// for instantiations. + /// + /// Note that [trust overrides](crate::open::Options::with()) in the `trust_map` are not effective here and we will + /// always override it with the determined trust value. This is a precaution as the API user is unable to actually know + /// if the directory that is discovered can indeed be trusted (or else they'd have to implement the discovery themselves + /// and be sure that no attacker ever gets access to a directory structure. The cost of this is a permission check, which + /// seems acceptable). pub fn discover_opts( directory: impl AsRef, options: upwards::Options, @@ -425,7 +439,8 @@ pub mod discover { ) -> Result { let (path, trust) = upwards_opts(directory, options)?; let (git_dir, worktree_dir) = path.into_repository_and_work_tree_directories(); - let options = trust_map.into_value_by_level(trust); + let mut options = trust_map.into_value_by_level(trust); + options.git_dir_trust = trust.into(); Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into) } diff --git a/git-repository/src/open.rs b/git-repository/src/open.rs index e2f34292e37..1cdf8d58f15 100644 --- a/git-repository/src/open.rs +++ b/git-repository/src/open.rs @@ -1,9 +1,8 @@ use std::path::PathBuf; use git_features::threading::OwnShared; -use git_sec::Trust; -use crate::{Permissions, ThreadSafeRepository}; +use crate::{config, config::cache::interpolate_context, permission, permissions, Permissions, ThreadSafeRepository}; /// A way to configure the usage of replacement objects, see `git replace`. #[derive(Debug, Clone)] @@ -66,6 +65,10 @@ pub struct Options { pub(crate) object_store_slots: git_odb::store::init::Slots, pub(crate) replacement_objects: ReplacementObjects, pub(crate) permissions: Permissions, + pub(crate) git_dir_trust: Option, + pub(crate) filter_config_section: Option bool>, + pub(crate) lossy_config: Option, + pub(crate) bail_if_untrusted: bool, } #[derive(Default, Clone)] @@ -82,7 +85,7 @@ pub(crate) struct EnvironmentOverrides { } impl EnvironmentOverrides { - fn from_env() -> Result { + fn from_env() -> Result { let mut worktree_dir = None; if let Some(path) = std::env::var_os("GIT_WORK_TREE") { worktree_dir = PathBuf::from(path).into(); @@ -95,6 +98,32 @@ impl EnvironmentOverrides { } } +/// Instantiation +impl Options { + /// Options configured to prevent accessing anything else than the repository configuration file, prohibiting + /// accessing the environment or spreading beyond the git repository location. + pub fn isolated() -> Self { + Options::default().permissions(Permissions { + config: permissions::Config { + system: false, + git: false, + user: false, + env: false, + includes: false, + }, + env: { + let deny = permission::env_var::Resource::resource(git_sec::Permission::Deny); + permissions::Environment { + xdg_config_home: deny.clone(), + home: deny.clone(), + git_prefix: deny, + } + }, + }) + } +} + +/// Builder methods impl Options { /// Set the amount of slots to use for the object database. It's a value that doesn't need changes on the client, typically, /// but should be controlled on the server. @@ -117,24 +146,82 @@ impl Options { self } + /// Set the trust level of the `.git` directory we are about to open. + /// + /// This can be set manually to force trust even though otherwise it might + /// not be fully trusted, leading to limitations in how configuration files + /// are interpreted. + /// + /// If not called explicitly, it will be determined by looking at its + /// ownership via [`git_sec::Trust::from_path_ownership()`]. + /// + /// # Security Warning + /// + /// Use with extreme care and only if it's absolutely known that the repository + /// is always controlled by the desired user. Using this capability _only_ saves + /// a permission check and only so if the [`open()`][Self::open()] method is used, + /// as opposed to discovery. + pub fn with(mut self, trust: git_sec::Trust) -> Self { + self.git_dir_trust = trust.into(); + self + } + + /// If true, default false, and if the repository's trust level is not `Full` + /// (see [`with()`][Self::with()] for more), then the open operation will fail. + /// + /// Use this to mimic `git`s way of handling untrusted repositories. Note that `gitoxide` solves + /// this by not using configuration from untrusted sources and by generally being secured against + /// doctored input files which at worst could cause out-of-memory at the time of writing. + pub fn bail_if_untrusted(mut self, toggle: bool) -> Self { + self.bail_if_untrusted = toggle; + self + } + + /// Set the filter which determines if a configuration section can be used to read values from, + /// hence it returns true if it is eligible. + /// + /// The default filter selects sections whose trust level is [`full`][git_sec::Trust::Full] or + /// whose source is not [`repository-local`][git_config::source::Kind::Repository]. + pub fn filter_config_section(mut self, filter: fn(&git_config::file::Metadata) -> bool) -> Self { + self.filter_config_section = Some(filter); + self + } + + /// By default, in release mode configuration will be read without retaining non-essential information like + /// comments or whitespace to optimize lookup performance. + /// + /// Some application might want to toggle this to false in they want to display or edit configuration losslessly. + pub fn lossy_config(mut self, toggle: bool) -> Self { + self.lossy_config = toggle.into(); + self + } + /// Open a repository at `path` with the options set so far. - pub fn open(self, path: impl Into) -> Result { + pub fn open(self, path: impl Into) -> Result { ThreadSafeRepository::open_opts(path, self) } } impl git_sec::trust::DefaultForLevel for Options { - fn default_for_level(level: Trust) -> Self { + fn default_for_level(level: git_sec::Trust) -> Self { match level { git_sec::Trust::Full => Options { object_store_slots: Default::default(), replacement_objects: Default::default(), - permissions: Permissions::all(), + permissions: Permissions::default_for_level(level), + git_dir_trust: git_sec::Trust::Full.into(), + filter_config_section: Some(config::section::is_trusted), + lossy_config: None, + bail_if_untrusted: false, }, git_sec::Trust::Reduced => Options { object_store_slots: git_odb::store::init::Slots::Given(32), // limit resource usage replacement_objects: ReplacementObjects::Disable, // don't be tricked into seeing manufactured objects - permissions: Default::default(), + permissions: Permissions::default_for_level(level), + git_dir_trust: git_sec::Trust::Reduced.into(), + filter_config_section: Some(config::section::is_trusted), + bail_if_untrusted: false, + lossy_config: None, }, } } @@ -145,20 +232,20 @@ impl git_sec::trust::DefaultForLevel for Options { #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Config(#[from] crate::config::Error), + Config(#[from] config::Error), #[error(transparent)] NotARepository(#[from] git_discover::is_git::Error), #[error(transparent)] ObjectStoreInitialization(#[from] std::io::Error), #[error("The git directory at '{}' is considered unsafe as it's not owned by the current user.", .path.display())] - UnsafeGitDir { path: std::path::PathBuf }, + UnsafeGitDir { path: PathBuf }, #[error(transparent)] - EnvironmentAccessDenied(#[from] crate::permission::env_var::resource::Error), + EnvironmentAccessDenied(#[from] permission::env_var::resource::Error), } impl ThreadSafeRepository { /// Open a git repository at the given `path`, possibly expanding it to `path/.git` if `path` is a work tree dir. - pub fn open(path: impl Into) -> Result { + pub fn open(path: impl Into) -> Result { Self::open_opts(path, Options::default()) } @@ -166,7 +253,7 @@ impl ThreadSafeRepository { /// `options` for fine-grained control. /// /// Note that you should use [`crate::discover()`] if security should be adjusted by ownership. - pub fn open_opts(path: impl Into, options: Options) -> Result { + pub fn open_opts(path: impl Into, mut options: Options) -> Result { let (path, kind) = { let path = path.into(); match git_discover::is_git(&path) { @@ -179,6 +266,9 @@ impl ThreadSafeRepository { }; let (git_dir, worktree_dir) = git_discover::repository::Path::from_dot_git_dir(path, kind).into_repository_and_work_tree_directories(); + if options.git_dir_trust.is_none() { + options.git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?.into(); + } ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) } @@ -208,27 +298,27 @@ impl ThreadSafeRepository { .into_repository_and_work_tree_directories(); let worktree_dir = worktree_dir.or(overrides.worktree_dir); - let trust = git_sec::Trust::from_path_ownership(&git_dir)?; - let options = trust_map.into_value_by_level(trust); + let git_dir_trust = git_sec::Trust::from_path_ownership(&git_dir)?; + let options = trust_map.into_value_by_level(git_dir_trust); ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options) } pub(crate) fn open_from_paths( git_dir: PathBuf, mut worktree_dir: Option, - Options { - object_store_slots, - replacement_objects, - permissions: Permissions { - git_dir: git_dir_perm, - env, - }, - }: Options, + options: Options, ) -> Result { - if *git_dir_perm != git_sec::ReadWrite::all() { - // TODO: respect `save.directory`, which needs more support from git-config to do properly. - return Err(Error::UnsafeGitDir { path: git_dir }); - } + let Options { + git_dir_trust, + object_store_slots, + filter_config_section, + ref replacement_objects, + lossy_config, + bail_if_untrusted, + permissions: Permissions { ref env, config }, + } = options; + let git_dir_trust = git_dir_trust.expect("trust must be been determined by now"); + // TODO: assure we handle the worktree-dir properly as we can have config per worktree with an extension. // This would be something read in later as have to first check for extensions. Also this means // that each worktree, even if accessible through this instance, has to come in its own Repository instance @@ -237,12 +327,36 @@ impl ThreadSafeRepository { .transpose()? .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); - let config = crate::config::Cache::new( + + let repo_config = config::cache::StageOne::new(common_dir_ref, git_dir_trust, lossy_config)?; + let mut refs = { + let reflog = repo_config.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); + let object_hash = repo_config.object_hash; + match &common_dir { + Some(common_dir) => crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, object_hash), + None => crate::RefStore::at(&git_dir, reflog, object_hash), + } + }; + let head = refs.find("HEAD").ok(); + let git_install_dir = crate::path::install_dir().ok(); + let home = std::env::var_os("HOME") + .map(PathBuf::from) + .and_then(|home| env.home.check(home).ok().flatten()); + let config = config::Cache::from_stage_one( + repo_config, common_dir_ref, - env.xdg_config_home.clone(), - env.home.clone(), - crate::path::install_dir().ok().as_deref(), + head.as_ref().and_then(|head| head.target.try_name()), + filter_config_section.unwrap_or(config::section::is_trusted), + git_install_dir.as_deref(), + home.as_deref(), + env.clone(), + config, )?; + + if bail_if_untrusted && git_dir_trust != git_sec::Trust::Full { + check_safe_directories(&git_dir, git_install_dir.as_deref(), home.as_deref(), &config)?; + } + match worktree_dir { None if !config.is_bare => { worktree_dir = Some(git_dir.parent().expect("parent is always available").to_owned()); @@ -254,21 +368,13 @@ impl ThreadSafeRepository { None => {} } - let refs = { - let reflog = config.reflog.unwrap_or_else(|| { - if worktree_dir.is_none() { - git_ref::store::WriteReflog::Disable - } else { - git_ref::store::WriteReflog::Normal - } - }); - match &common_dir { - Some(common_dir) => { - crate::RefStore::for_linked_worktree(&git_dir, common_dir, reflog, config.object_hash) - } - None => crate::RefStore::at(&git_dir, reflog, config.object_hash), + refs.write_reflog = config.reflog.unwrap_or_else(|| { + if worktree_dir.is_none() { + git_ref::store::WriteReflog::Disable + } else { + git_ref::store::WriteReflog::Normal } - }; + }); let replacements = replacement_objects .clone() @@ -290,16 +396,6 @@ impl ThreadSafeRepository { }) .unwrap_or_default(); - // used when spawning new repositories off this one when following worktrees - let linked_worktree_options = Options { - object_store_slots, - replacement_objects, - permissions: Permissions { - env, - git_dir: git_dir_perm, - }, - }; - Ok(ThreadSafeRepository { objects: OwnShared::new(git_odb::Store::at_opts( common_dir_ref.join("objects"), @@ -314,8 +410,56 @@ impl ThreadSafeRepository { refs, work_tree: worktree_dir, config, - linked_worktree_options, + // used when spawning new repositories off this one when following worktrees + linked_worktree_options: options, + }) + } +} + +fn check_safe_directories( + git_dir: &std::path::Path, + git_install_dir: Option<&std::path::Path>, + home: Option<&std::path::Path>, + config: &config::Cache, +) -> Result<(), Error> { + let mut is_safe = false; + let git_dir = match git_path::realpath(git_dir) { + Ok(p) => p, + Err(_) => git_dir.to_owned(), + }; + for safe_dir in config + .resolved + .strings_filter("safe", None, "directory", &mut |meta| { + let kind = meta.source.kind(); + kind == git_config::source::Kind::System || kind == git_config::source::Kind::Global }) + .unwrap_or_default() + { + if safe_dir.as_ref() == "*" { + is_safe = true; + continue; + } + if safe_dir.is_empty() { + is_safe = false; + continue; + } + if !is_safe { + let safe_dir = match git_config::Path::from(std::borrow::Cow::Borrowed(safe_dir.as_ref())) + .interpolate(interpolate_context(git_install_dir, home)) + { + Ok(path) => path, + Err(_) => git_path::from_bstr(safe_dir), + }; + if safe_dir == git_dir { + is_safe = true; + continue; + } + } + } + if is_safe { + Ok(()) + } else { + Err(Error::UnsafeGitDir { path: git_dir }) } } @@ -327,7 +471,7 @@ mod tests { fn size_of_options() { assert_eq!( std::mem::size_of::(), - 56, + 72, "size shouldn't change without us knowing" ); } diff --git a/git-repository/src/repository/config.rs b/git-repository/src/repository/config.rs new file mode 100644 index 00000000000..dd5cf205356 --- /dev/null +++ b/git-repository/src/repository/config.rs @@ -0,0 +1,20 @@ +use crate::config; + +/// Configuration +impl crate::Repository { + /// Return + /// Return a snapshot of the configuration as seen upon opening the repository. + pub fn config_snapshot(&self) -> config::Snapshot<'_> { + config::Snapshot { repo: self } + } + + /// The options used to open the repository. + pub fn open_options(&self) -> &crate::open::Options { + &self.options + } + + /// The kind of object hash the repository is configured to use. + pub fn object_hash(&self) -> git_hash::Kind { + self.config.object_hash + } +} diff --git a/git-repository/src/repository/identity.rs b/git-repository/src/repository/identity.rs new file mode 100644 index 00000000000..a5f9207fe7f --- /dev/null +++ b/git-repository/src/repository/identity.rs @@ -0,0 +1,155 @@ +use std::borrow::Cow; + +use git_actor::SignatureRef; +use git_config::File; + +use crate::{bstr::BString, permission}; + +/// Identity handling. +impl crate::Repository { + /// Return a crate-specific constant signature with [`Time`][git_actor::Time] set to now, + /// in a similar vein as the default that git chooses if there is nothing configured. + /// + /// This can be useful as fallback for an unset `committer` or `author`. + /// + /// # Note + /// + /// The values are cached when the repository is instantiated. + pub fn user_default(&self) -> SignatureRef<'_> { + SignatureRef { + name: "gitoxide".into(), + email: "gitoxide@localhost".into(), + time: git_date::Time::now_local_or_utc(), + } + } + + /// Return the committer as configured by this repository, which is determined by… + /// + /// * …the git configuration `committer.name|email`… + /// * …the `GIT_COMMITTER_(NAME|EMAIL|DATE)` environment variables… + /// * …the configuration for `user.name|email` as fallback… + /// + /// …and in that order, or `None` if there was nothing configured. In that case, one may use the + /// [`committer_or_default()`][Self::committer_or_default()] method. + /// + /// # Note + /// + /// The values are cached when the repository is instantiated. + pub fn committer(&self) -> Option> { + let p = self.config.personas(); + + git_actor::SignatureRef { + name: p.committer.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, + email: p + .committer + .email + .as_ref() + .or(p.user.email.as_ref()) + .map(|v| v.as_ref())?, + time: p.committer.time.unwrap_or_else(git_date::Time::now_local_or_utc), + } + .into() + } + + /// Like [`committer()`][Self::committer()], but may use a default value in case nothing is configured. + pub fn committer_or_default(&self) -> git_actor::SignatureRef<'_> { + self.committer().unwrap_or_else(|| self.user_default()) + } + + /// Return the author as configured by this repository, which is determined by… + /// + /// * …the git configuration `author.name|email`… + /// * …the `GIT_AUTHOR_(NAME|EMAIL|DATE)` environment variables… + /// * …the configuration for `user.name|email` as fallback… + /// + /// …and in that order, or `None` if there was nothing configured. In that case, one may use the + /// [`author_or_default()`][Self::author_or_default()] method. + /// + /// # Note + /// + /// The values are cached when the repository is instantiated. + pub fn author(&self) -> Option> { + let p = self.config.personas(); + + git_actor::SignatureRef { + name: p.author.name.as_ref().or(p.user.name.as_ref()).map(|v| v.as_ref())?, + email: p.author.email.as_ref().or(p.user.email.as_ref()).map(|v| v.as_ref())?, + time: p.author.time.unwrap_or_else(git_date::Time::now_local_or_utc), + } + .into() + } + + /// Like [`author()`][Self::author()], but may use a default value in case nothing is configured. + pub fn author_or_default(&self) -> git_actor::SignatureRef<'_> { + self.author().unwrap_or_else(|| self.user_default()) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct Entity { + pub name: Option, + pub email: Option, + /// A time parsed from an environment variable. + pub time: Option, +} + +#[derive(Debug, Clone)] +pub(crate) struct Personas { + user: Entity, + committer: Entity, + author: Entity, +} + +impl Personas { + pub fn from_config_and_env(config: &git_config::File<'_>, git_env: &permission::env_var::Resource) -> Self { + fn env_var(name: &str) -> Option { + std::env::var_os(name).map(|value| git_path::into_bstr(Cow::Owned(value.into())).into_owned()) + } + fn entity_in_section(name: &str, config: &File<'_>) -> (Option, Option) { + config + .section(name, None) + .map(|section| { + ( + section.value("name").map(|v| v.into_owned()), + section.value("email").map(|v| v.into_owned()), + ) + }) + .unwrap_or_default() + } + + let (mut committer_name, mut committer_email) = entity_in_section("committer", config); + let mut committer_date = None; + let (mut author_name, mut author_email) = entity_in_section("author", config); + let mut author_date = None; + let (user_name, mut user_email) = entity_in_section("user", config); + + if git_env.eq(&git_sec::Permission::Allow) { + committer_name = committer_name.or_else(|| env_var("GIT_COMMITTER_NAME")); + committer_email = committer_email.or_else(|| env_var("GIT_COMMITTER_EMAIL")); + committer_date = env_var("GIT_COMMITTER_DATE").and_then(|date| git_date::parse(date.as_ref())); + + author_name = author_name.or_else(|| env_var("GIT_AUTHOR_NAME")); + author_email = author_email.or_else(|| env_var("GIT_AUTHOR_EMAIL")); + author_date = env_var("GIT_AUTHOR_DATE").and_then(|date| git_date::parse(date.as_ref())); + + user_email = user_email.or_else(|| env_var("EMAIL")); // NOTE: we don't have permission for this specific one… + } + Personas { + user: Entity { + name: user_name, + email: user_email, + time: None, + }, + committer: Entity { + name: committer_name, + email: committer_email, + time: committer_date, + }, + author: Entity { + name: author_name, + email: author_email, + time: author_date, + }, + } + } +} diff --git a/git-repository/src/repository/impls.rs b/git-repository/src/repository/impls.rs index b8104465aca..aaafab1f824 100644 --- a/git-repository/src/repository/impls.rs +++ b/git-repository/src/repository/impls.rs @@ -6,7 +6,7 @@ impl Clone for crate::Repository { self.work_tree.clone(), self.common_dir.clone(), self.config.clone(), - self.linked_worktree_options.clone(), + self.options.clone(), ) } } @@ -63,7 +63,7 @@ impl From for crate::ThreadSafeRepository { work_tree: r.work_tree, common_dir: r.common_dir, config: r.config, - linked_worktree_options: r.linked_worktree_options, + linked_worktree_options: r.options, } } } diff --git a/git-repository/src/repository/init.rs b/git-repository/src/repository/init.rs index d757ab1e3a7..eaebcce4ae8 100644 --- a/git-repository/src/repository/init.rs +++ b/git-repository/src/repository/init.rs @@ -25,7 +25,7 @@ impl crate::Repository { }, refs, config, - linked_worktree_options, + options: linked_worktree_options, } } diff --git a/git-repository/src/repository/location.rs b/git-repository/src/repository/location.rs index 0e23cbc0a1a..a324c6688dd 100644 --- a/git-repository/src/repository/location.rs +++ b/git-repository/src/repository/location.rs @@ -65,4 +65,9 @@ impl crate::Repository { pub fn git_dir(&self) -> &std::path::Path { self.refs.git_dir() } + + /// The trust we place in the git-dir, with lower amounts of trust causing access to configuration to be limited. + pub fn git_dir_trust(&self) -> git_sec::Trust { + self.options.git_dir_trust.expect("definitely set by now") + } } diff --git a/git-repository/src/repository/mod.rs b/git-repository/src/repository/mod.rs index b0b6fb36b72..c8d7f59bf81 100644 --- a/git-repository/src/repository/mod.rs +++ b/git-repository/src/repository/mod.rs @@ -19,47 +19,17 @@ impl crate::Repository { } } -/// Everything else -impl crate::Repository { - // TODO: actual implementation - /// Return the committer as configured by this repository, which is determined by… - /// - /// * …the git configuration… - /// * …the GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE) environment variables… - /// - /// …and in that order. - pub fn committer(&self) -> git_actor::Signature { - // TODO: actually do the work, probably that should be cached and be refreshable - git_actor::Signature::empty() - } - - /// The kind of object hash the repository is configured to use. - pub fn object_hash(&self) -> git_hash::Kind { - self.config.object_hash - } -} - -mod worktree; - -/// Various permissions for parts of git repositories. -pub(crate) mod permissions; - +mod cache; +mod config; +pub(crate) mod identity; +mod impls; mod init; - mod location; - +mod object; +pub(crate) mod permissions; +mod reference; +mod remote; mod snapshots; - mod state; - -mod impls; - -mod cache; - -mod reference; - -mod object; - mod thread_safe; - -mod remote; +mod worktree; diff --git a/git-repository/src/repository/object.rs b/git-repository/src/repository/object.rs index a09bdf56702..4e5f8cd219a 100644 --- a/git-repository/src/repository/object.rs +++ b/git-repository/src/repository/object.rs @@ -171,7 +171,7 @@ impl crate::Repository { deref: true, }, git_lock::acquire::Fail::Immediately, - Some(&commit.committer), + commit.committer.to_ref(), )?; Ok(commit_id) } diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index 52dee4393a0..32fab1a1a00 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -1,16 +1,60 @@ -use git_sec::{permission::Resource, Access, Trust}; +use git_sec::{Access, Trust}; use crate::permission; /// Permissions associated with various resources of a git repository #[derive(Debug, Clone)] pub struct Permissions { - /// Control how a git-dir can be used. - /// - /// Note that a repository won't be usable at all unless read and write permissions are given. - pub git_dir: Access, /// Permissions related to the environment pub env: Environment, + /// Permissions related to the handling of git configuration. + pub config: Config, +} + +/// Configure security relevant options when loading a git configuration. +#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] +pub struct Config { + /// Whether to use the system configuration. + /// This is defined as `$(prefix)/etc/gitconfig` on unix. + pub system: bool, + /// Whether to use the git application configuration. + /// + /// A platform defined location for where a user's git application configuration should be located. + /// If `$XDG_CONFIG_HOME` is not set or empty, `$HOME/.config/git/config` will be used + /// on unix. + pub git: bool, + /// Whether to use the user configuration. + /// This is usually `~/.gitconfig` on unix. + pub user: bool, + /// Whether to use worktree configuration from `config.worktree`. + // TODO: figure out how this really applies and provide more information here. + // pub worktree: bool, + /// Whether to use the configuration from environment variables. + pub env: bool, + /// Whether to follow include files are encountered in loaded configuration, + /// via `include` and `includeIf` sections. + /// + /// Note that this needs access to `GIT_*` prefixed environment variables. + pub includes: bool, +} + +impl Config { + /// Allow everything which usually relates to a fully trusted environment + pub fn all() -> Self { + Config { + system: true, + git: true, + user: true, + env: true, + includes: true, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self::all() + } } /// Permissions related to the usage of environment variables @@ -26,17 +70,24 @@ pub struct Environment { pub git_prefix: permission::env_var::Resource, } +impl Environment { + /// Allow access to the entire environment. + pub fn all() -> Self { + Environment { + xdg_config_home: Access::resource(git_sec::Permission::Allow), + home: Access::resource(git_sec::Permission::Allow), + git_prefix: Access::resource(git_sec::Permission::Allow), + } + } +} + impl Permissions { /// Return permissions similar to what git does when the repository isn't owned by the current user, /// thus refusing all operations in it. pub fn strict() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::READ), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::all(), + config: Config::all(), } } @@ -47,12 +98,8 @@ impl Permissions { /// anything else that could cause us to write into unknown locations or use programs beyond our `PATH`. pub fn secure() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::all(), + config: Config::all(), } } @@ -60,12 +107,8 @@ impl Permissions { /// does with owned repositories. pub fn all() -> Self { Permissions { - git_dir: Access::resource(git_sec::ReadWrite::all()), - env: Environment { - xdg_config_home: Access::resource(git_sec::Permission::Allow), - home: Access::resource(git_sec::Permission::Allow), - git_prefix: Access::resource(git_sec::Permission::Allow), - }, + env: Environment::all(), + config: Config::all(), } } } diff --git a/git-repository/src/repository/reference.rs b/git-repository/src/repository/reference.rs index 87826b85b35..279a6de9d5d 100644 --- a/git-repository/src/repository/reference.rs +++ b/git-repository/src/repository/reference.rs @@ -36,11 +36,11 @@ impl crate::Repository { deref: false, }, DEFAULT_LOCK_MODE, - None, + self.committer_or_default(), )?; assert_eq!(edits.len(), 1, "reference splits should ever happen"); let edit = edits.pop().expect("exactly one item"); - Ok(crate::Reference { + Ok(Reference { inner: git_ref::Reference { name: edit.name, target: id.into(), @@ -110,7 +110,7 @@ impl crate::Repository { deref: false, }, DEFAULT_LOCK_MODE, - None, + self.committer_or_default(), )?; assert_eq!( edits.len(), @@ -134,7 +134,7 @@ impl crate::Repository { &self, edit: RefEdit, lock_mode: lock::acquire::Fail, - log_committer: Option<&actor::Signature>, + log_committer: actor::SignatureRef<'_>, ) -> Result, reference::edit::Error> { self.edit_references(Some(edit), lock_mode, log_committer) } @@ -148,20 +148,12 @@ impl crate::Repository { &self, edits: impl IntoIterator, lock_mode: lock::acquire::Fail, - log_committer: Option<&actor::Signature>, + log_committer: actor::SignatureRef<'_>, ) -> Result, reference::edit::Error> { - let committer_storage; - let committer = match log_committer { - Some(c) => c, - None => { - committer_storage = self.committer(); - &committer_storage - } - }; self.refs .transaction() .prepare(edits, lock_mode)? - .commit(committer.to_ref()) + .commit(log_committer) .map_err(Into::into) } diff --git a/git-repository/src/repository/snapshots.rs b/git-repository/src/repository/snapshots.rs index ecd4e219c2a..c2927e3bb63 100644 --- a/git-repository/src/repository/snapshots.rs +++ b/git-repository/src/repository/snapshots.rs @@ -98,10 +98,10 @@ impl crate::Repository { .and_then(|path| { let install_dir = self.install_dir().ok()?; let home = self.config.home_dir(); - match path.interpolate(git_config::path::interpolate::Options { + match path.interpolate(git_config::path::interpolate::Context { git_install_dir: Some(install_dir.as_path()), home_dir: home.as_deref(), - home_for_user: if self.linked_worktree_options.permissions.git_dir.is_all() { + home_for_user: if self.options.git_dir_trust.expect("trust is set") == git_sec::Trust::Full { Some(git_config::path::interpolate::home_for_user) } else { None diff --git a/git-repository/src/repository/worktree.rs b/git-repository/src/repository/worktree.rs index 5826e0fde2b..12e953a49bc 100644 --- a/git-repository/src/repository/worktree.rs +++ b/git-repository/src/repository/worktree.rs @@ -41,10 +41,10 @@ impl crate::Repository { impl crate::Repository { /// Return the repository owning the main worktree. /// - /// Note that it might be the one that is currently open if this repository dosn't point to a linked worktree. + /// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree. /// Also note that the main repo might be bare. pub fn main_repo(&self) -> Result { - crate::open(self.common_dir()) + crate::ThreadSafeRepository::open_opts(self.common_dir(), self.options.clone()).map(Into::into) } /// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path. diff --git a/git-repository/src/types.rs b/git-repository/src/types.rs index dee5c78aa39..ec1ee0a247e 100644 --- a/git-repository/src/types.rs +++ b/git-repository/src/types.rs @@ -132,8 +132,10 @@ pub struct Repository { pub(crate) bufs: RefCell>>, /// A pre-assembled selection of often-accessed configuration values for quick access. pub(crate) config: crate::config::Cache, - /// options obtained when instantiating this repository for use when following linked worktrees. - pub(crate) linked_worktree_options: crate::open::Options, + /// the options obtained when instantiating this repository. + /// + /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. + pub(crate) options: crate::open::Options, } /// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_ @@ -155,9 +157,6 @@ pub struct ThreadSafeRepository { pub work_tree: Option, /// The path to the common directory if this is a linked worktree repository or it is otherwise set. pub common_dir: Option, - // TODO: git-config should be here - it's read a lot but not written much in must applications, so shouldn't be in `State`. - // Probably it's best reload it on signal (in servers) or refresh it when it's known to have been changed similar to how - // packs are refreshed. This would be `git_config::fs::Config` when ready. pub(crate) config: crate::config::Cache, /// options obtained when instantiating this repository for use when following linked worktrees. pub(crate) linked_worktree_options: crate::open::Options, diff --git a/git-repository/src/worktree/proxy.rs b/git-repository/src/worktree/proxy.rs index a2bb7ebdc4e..80240001634 100644 --- a/git-repository/src/worktree/proxy.rs +++ b/git-repository/src/worktree/proxy.rs @@ -81,8 +81,7 @@ impl<'repo> Proxy<'repo> { /// a lot of information if work tree access is avoided. pub fn into_repo_with_possibly_inaccessible_worktree(self) -> Result { let base = self.base().ok(); - let repo = - ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.linked_worktree_options.clone())?; + let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base, self.parent.options.clone())?; Ok(repo.into()) } @@ -95,11 +94,7 @@ impl<'repo> Proxy<'repo> { if !base.is_dir() { return Err(into_repo::Error::MissingWorktree { base }); } - let repo = ThreadSafeRepository::open_from_paths( - self.git_dir, - base.into(), - self.parent.linked_worktree_options.clone(), - )?; + let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base.into(), self.parent.options.clone())?; Ok(repo.into()) } } diff --git a/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz new file mode 100644 index 00000000000..43e28efd2a4 --- /dev/null +++ b/git-repository/tests/fixtures/generated-archives/make_config_repo.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:035cf83cc4e212ac2df20c905dd09f27142832fc5ad5c7950d9b85ea51a672cd +size 9360 diff --git a/git-repository/tests/fixtures/make_config_repo.sh b/git-repository/tests/fixtures/make_config_repo.sh new file mode 100644 index 00000000000..04958fa98c6 --- /dev/null +++ b/git-repository/tests/fixtures/make_config_repo.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q + +cat <>.git/config +[a] + bool = on + bad-bool = zero + int = 42 + int-overflowing = 9999999999999g + relative-path = ./something + absolute-path = /etc/man.conf + bad-home-path = ~/repo + bad-user-path = ~noname/repo + single-string = hello world + local-override = base + env-override = base + +[include] + path = ../a.config + +[user] + name = user + email = user@email +EOF + + +cat <>a.config +[a] + local-override = from-a.config + +[committer] + name = committer + email = committer@email +EOF +cat <>b.config +[a] + system-override = from-b.config +EOF + +cat <>c.config +[a] + env-override = from-c.config +EOF + +cat <>system.config +[a] + system = from-system.config + system-override = base +[include] + path = ./b.config +EOF diff --git a/git-repository/tests/git.rs b/git-repository/tests/git.rs index 8c9280a29c4..fbda34ca007 100644 --- a/git-repository/tests/git.rs +++ b/git-repository/tests/git.rs @@ -1,30 +1,42 @@ -use git_repository::{Repository, ThreadSafeRepository}; +use git_repository::{open, Repository, ThreadSafeRepository}; type Result = std::result::Result>; -fn repo(name: &str) -> crate::Result { +fn repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; - Ok(ThreadSafeRepository::open(repo_path)?) + Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?) } -fn named_repo(name: &str) -> crate::Result { +fn named_repo(name: &str) -> Result { let repo_path = git_testtools::scripted_fixture_repo_read_only(name)?; - Ok(ThreadSafeRepository::open(repo_path)?.to_thread_local()) + Ok(ThreadSafeRepository::open_opts(repo_path, restricted())?.to_thread_local()) } -fn repo_rw(name: &str) -> crate::Result<(Repository, tempfile::TempDir)> { +fn restricted() -> open::Options { + open::Options::isolated() +} + +fn repo_rw(name: &str) -> Result<(Repository, tempfile::TempDir)> { let repo_path = git_testtools::scripted_fixture_repo_writable(name)?; Ok(( - ThreadSafeRepository::discover(repo_path.path())?.to_thread_local(), + ThreadSafeRepository::discover_opts( + repo_path.path(), + Default::default(), + git_sec::trust::Mapping { + full: restricted(), + reduced: restricted(), + }, + )? + .to_thread_local(), repo_path, )) } -fn basic_repo() -> crate::Result { +fn basic_repo() -> Result { repo("make_basic_repo.sh").map(|r| r.to_thread_local()) } -fn basic_rw_repo() -> crate::Result<(Repository, tempfile::TempDir)> { +fn basic_rw_repo() -> Result<(Repository, tempfile::TempDir)> { repo_rw("make_basic_repo.sh") } diff --git a/git-repository/tests/repository/config.rs b/git-repository/tests/repository/config.rs new file mode 100644 index 00000000000..1a3c1954c51 --- /dev/null +++ b/git-repository/tests/repository/config.rs @@ -0,0 +1,132 @@ +use std::path::Path; + +use git_repository as git; +use git_sec::{Access, Permission}; +use git_testtools::Env; +use serial_test::serial; + +use crate::named_repo; + +#[test] +#[serial] +fn access_values() { + for trust in [git_sec::Trust::Full, git_sec::Trust::Reduced] { + let repo = named_repo("make_config_repo.sh").unwrap(); + let work_dir = repo.work_dir().expect("present").canonicalize().unwrap(); + let _env = Env::new() + .set( + "GIT_CONFIG_SYSTEM", + work_dir.join("system.config").display().to_string(), + ) + .set("GIT_AUTHOR_NAME", "author") + .set("GIT_AUTHOR_EMAIL", "author@email") + .set("GIT_AUTHOR_DATE", "1979-02-26 18:30:00") + .set("GIT_CONFIG_COUNT", "1") + .set("GIT_CONFIG_KEY_0", "include.path") + .set("GIT_CONFIG_VALUE_0", work_dir.join("c.config").display().to_string()); + let repo = git::open_opts( + repo.git_dir(), + repo.open_options().clone().with(trust).permissions(git::Permissions { + env: git::permissions::Environment { + xdg_config_home: Access::resource(Permission::Deny), + home: Access::resource(Permission::Deny), + ..git::permissions::Environment::all() + }, + ..Default::default() + }), + ) + .unwrap(); + + assert_eq!( + repo.author(), + Some(git_actor::SignatureRef { + name: "author".into(), + email: "author@email".into(), + time: git_date::Time { + seconds_since_unix_epoch: 42, + offset_in_seconds: 1800, + sign: git_date::time::Sign::Plus + } + }), + "the only parsesable marker time we know right now, indicating time parse success" + ); + assert_eq!( + repo.committer(), + Some(git_actor::SignatureRef { + name: "committer".into(), + email: "committer@email".into(), + time: git_date::Time::now_local_or_utc() + }) + ); + assert_eq!( + repo.user_default(), + git_actor::SignatureRef { + name: "gitoxide".into(), + email: "gitoxide@localhost".into(), + time: git_date::Time::now_local_or_utc() + } + ); + + let config = repo.config_snapshot(); + + assert_eq!(config.boolean("core.bare"), Some(false)); + assert_eq!(config.boolean("a.bad-bool"), None); + assert_eq!(config.try_boolean("core.bare"), Some(Ok(false))); + assert!(matches!(config.try_boolean("a.bad-bool"), Some(Err(_)))); + + assert_eq!(config.integer("a.int"), Some(42)); + assert_eq!(config.integer("a.int-overflowing"), None); + assert_eq!(config.integer("a.int-overflowing"), None); + assert!(config.try_integer("a.int-overflowing").expect("present").is_err()); + + assert_eq!( + config.string("a.single-string").expect("present").as_ref(), + "hello world" + ); + + assert_eq!( + config.string("a.local-override").expect("present").as_ref(), + "from-a.config" + ); + assert_eq!( + config.string("a.system").expect("present").as_ref(), + "from-system.config" + ); + assert_eq!( + config.string("a.system-override").expect("present").as_ref(), + "from-b.config" + ); + + assert_eq!( + config.string("a.env-override").expect("present").as_ref(), + "from-c.config" + ); + + assert_eq!(config.boolean("core.missing"), None); + assert_eq!(config.try_boolean("core.missing"), None); + + let relative_path_key = "a.relative-path"; + if trust == git_sec::Trust::Full { + assert_eq!( + config + .trusted_path(relative_path_key) + .expect("exists") + .expect("no error"), + Path::new("./something") + ); + assert_eq!( + config + .trusted_path("a.absolute-path") + .expect("exists") + .expect("no error"), + Path::new("/etc/man.conf") + ); + assert!(config.trusted_path("a.bad-user-path").expect("exists").is_err()); + } else { + assert!( + config.trusted_path(relative_path_key).is_none(), + "trusted paths need full trust" + ); + } + } +} diff --git a/git-repository/tests/repository/mod.rs b/git-repository/tests/repository/mod.rs index 5d1a173dcf3..f36fefa3fed 100644 --- a/git-repository/tests/repository/mod.rs +++ b/git-repository/tests/repository/mod.rs @@ -1,5 +1,17 @@ +use git_repository::Repository; + +mod config; mod object; mod reference; mod remote; mod state; mod worktree; + +#[test] +fn size_in_memory() { + assert_eq!( + std::mem::size_of::(), + 688, + "size of Repository shouldn't change without us noticing, it's meant to be cloned" + ); +} diff --git a/git-repository/tests/repository/object.rs b/git-repository/tests/repository/object.rs index d6944fe46ef..b9d789abe31 100644 --- a/git-repository/tests/repository/object.rs +++ b/git-repository/tests/repository/object.rs @@ -57,7 +57,7 @@ mod tag { "v1.0.0", ¤t_head_id, git_object::Kind::Commit, - Some(repo.committer().to_ref()), + Some(repo.committer_or_default()), message, git_ref::transaction::PreviousValue::MustNotExist, )?; @@ -68,7 +68,7 @@ mod tag { assert_eq!(tag.name, "v1.0.0"); assert_eq!(current_head_id, tag.target(), "the tag points to the commit"); assert_eq!(tag.target_kind, git_object::Kind::Commit); - assert_eq!(*tag.tagger.as_ref().expect("tagger"), repo.committer().to_ref()); + assert_eq!(*tag.tagger.as_ref().expect("tagger"), repo.committer_or_default()); assert_eq!(tag.message, message); Ok(()) } diff --git a/git-revision/CHANGELOG.md b/git-revision/CHANGELOG.md index 7f776d51865..99bf3f9dac1 100644 --- a/git-revision/CHANGELOG.md +++ b/git-revision/CHANGELOG.md @@ -5,6 +5,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 9 commits contributed to the release over the course of 38 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - Support for explaining all navitation ([`ace9c89`](https://github.com/Byron/gitoxide/commit/ace9c8953bebc4a808c639e365010ed53c031622)) + - Handle lonely tilde gracefully ([`6fb834e`](https://github.com/Byron/gitoxide/commit/6fb834e06639febbe67a46e702cd523c4e7bd2a7)) + - refactor ([`1a15e12`](https://github.com/Byron/gitoxide/commit/1a15e120a75d29b3d3f7615af1a66a033dfd3c8b)) + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 0.2.1 (2022-06-13) ### New Features @@ -15,7 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 75 commits contributed to the release over the course of 5 calendar days. + - 76 commits contributed to the release over the course of 5 calendar days. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#427](https://github.com/Byron/gitoxide/issues/427) @@ -99,6 +137,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - refactor ([`efc05e1`](https://github.com/Byron/gitoxide/commit/efc05e11fa2ec11952b06080ba76387a4c11c3b4)) - A basis for 'pure' parsing of rev-specs ([`29ab704`](https://github.com/Byron/gitoxide/commit/29ab7049fd180fac2e443a99908db066c67938db)) * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - make fmt ([`c665aef`](https://github.com/Byron/gitoxide/commit/c665aef4270c5ee54da89ee015cc0affd6337608)) - thanks clippy ([`1bbd3f4`](https://github.com/Byron/gitoxide/commit/1bbd3f471d78e53a76b3e708c755fc9d72fc28fe)) diff --git a/git-revision/Cargo.toml b/git-revision/Cargo.toml index 2d2e11636ef..f1490626e65 100644 --- a/git-revision/Cargo.toml +++ b/git-revision/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-revision" -version = "0.2.1" +version = "0.3.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project dealing with finding names for revisions and parsing specifications" @@ -17,9 +17,9 @@ serde1 = [ "serde", "git-hash/serde1", "git-object/serde1" ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-date = { version = "^0.0.1", path = "../git-date" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-date = { version = "^0.0.2", path = "../git-date" } bstr = { version = "0.2.13", default-features = false, features = ["std"]} hash_hasher = "2.0.3" diff --git a/git-sec/CHANGELOG.md b/git-sec/CHANGELOG.md index 0c6766a3163..1f888e2cacc 100644 --- a/git-sec/CHANGELOG.md +++ b/git-sec/CHANGELOG.md @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.0 (2022-07-22) + +### New Features + + - Support for SUDO_UID as fallback for ownership check on unix. + +### Bug Fixes + + - on windows, emit a `NotFound` io error, similar to what happens on unix. + That way code relying on this behaviour will work the same on both + platforms. + + On windows, this costs at an additional metadata check. + +### Commit Statistics + + + + - 6 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 2 commits where understood as [conventional](https://www.conventionalcommits.org). + - 1 unique issue was worked on: [#331](https://github.com/Byron/gitoxide/issues/331) + +### Commit Details + + + +
view details + + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - on windows, emit a `NotFound` io error, similar to what happens on unix. ([`9a1e982`](https://github.com/Byron/gitoxide/commit/9a1e9828e813ec1de68ac2e83a986c49c71c5dbe)) + - fix build after breaking changes in `git-path` ([`34aed2f`](https://github.com/Byron/gitoxide/commit/34aed2fb608df79bdc56b244f7ac216f46322e5f)) + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - Support for SUDO_UID as fallback for ownership check on unix. ([`3d16c36`](https://github.com/Byron/gitoxide/commit/3d16c36d7288d9a5fae5b9d23715e043d4d8ce76)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.2.0 (2022-06-13) ### New Features (BREAKING) @@ -16,7 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 4 commits contributed to the release over the course of 15 calendar days. + - 5 commits contributed to the release over the course of 15 calendar days. - 16 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -28,6 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) - dependency upgrades ([`a1981d4`](https://github.com/Byron/gitoxide/commit/a1981d48e98e51445d8413c615c6eccfb91cf05a)) - Merge branch 'main' into davidkna-envopen ([`bc0abc6`](https://github.com/Byron/gitoxide/commit/bc0abc643d3329f885f250b6880560dec861150f)) diff --git a/git-sec/Cargo.toml b/git-sec/Cargo.toml index 17ee4c48a44..c9eae44d451 100644 --- a/git-sec/Cargo.toml +++ b/git-sec/Cargo.toml @@ -28,7 +28,7 @@ document-features = { version = "0.2.1", optional = true } libc = "0.2.123" [target.'cfg(windows)'.dependencies] -git-path = { version = "^0.3.0", path = "../git-path" } +git-path = { version = "^0.4.0", path = "../git-path" } dirs = "4" windows = { version = "0.37.0", features = [ "alloc", "Win32_Foundation", diff --git a/git-sec/src/identity.rs b/git-sec/src/identity.rs index d6523da4284..d38a29e8b04 100644 --- a/git-sec/src/identity.rs +++ b/git-sec/src/identity.rs @@ -80,6 +80,13 @@ mod impl_ { let mut is_owned = false; let path = path.as_ref(); + if !path.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("{:?} does not exist.", path), + )); + } + // Home is not actually owned by the corresponding user // but it can be considered de-facto owned by the user // Ignore errors here and just do the regular checks below diff --git a/git-tempfile/CHANGELOG.md b/git-tempfile/CHANGELOG.md index bc17e2ca4b0..701dbf85139 100644 --- a/git-tempfile/CHANGELOG.md +++ b/git-tempfile/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.0.2 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 3 commits contributed to the release over the course of 21 calendar days. + - 110 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) +
+ ## 2.0.1 (2022-04-03) A maintenance release without any changes on the surface. @@ -13,7 +44,7 @@ A maintenance release without any changes on the surface. - - 4 commits contributed to the release over the course of 42 calendar days. + - 5 commits contributed to the release over the course of 42 calendar days. - 44 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#298](https://github.com/Byron/gitoxide/issues/298), [#328](https://github.com/Byron/gitoxide/issues/328), [#364](https://github.com/Byron/gitoxide/issues/364) @@ -31,6 +62,7 @@ A maintenance release without any changes on the surface. * **[#364](https://github.com/Byron/gitoxide/issues/364)** - update changelogs prior to release ([`746a676`](https://github.com/Byron/gitoxide/commit/746a676056cd4907da7137a00798344b5bdb4419)) * **Uncategorized** + - Release git-diff v0.14.0, git-bitmap v0.1.0, git-index v0.2.0, git-tempfile v2.0.1, git-lock v2.0.0, git-mailmap v0.1.0, git-traverse v0.13.0, git-pack v0.17.0, git-quote v0.2.0, git-odb v0.27.0, git-packetline v0.12.4, git-url v0.4.0, git-transport v0.16.0, git-protocol v0.15.0, git-ref v0.12.0, git-worktree v0.1.0, git-repository v0.15.0, cargo-smart-release v0.9.0, safety bump 5 crates ([`e58dc30`](https://github.com/Byron/gitoxide/commit/e58dc3084cf17a9f618ae3a6554a7323e44428bf)) - make fmt ([`7cf3545`](https://github.com/Byron/gitoxide/commit/7cf354509b545f7e7c99e159b5989ddfbe86273d))
diff --git a/git-tempfile/Cargo.toml b/git-tempfile/Cargo.toml index f2b4f4344fd..f3956b0eb4f 100644 --- a/git-tempfile/Cargo.toml +++ b/git-tempfile/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-tempfile" -version = "2.0.1" +version = "2.0.2" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A tempfile implementation with a global registry to assure cleanup" diff --git a/git-tempfile/README.md b/git-tempfile/README.md index 83a7cc64905..45821724e36 100644 --- a/git-tempfile/README.md +++ b/git-tempfile/README.md @@ -4,7 +4,7 @@ in a signal-safe way, making the change atomic. Tempfiles can also be used as locks as only one tempfile can exist at a given path at a time. * [x] registered temporary files which are deleted automatically as the process terminates or on drop - * [x] write to temorary file and persist it under new name + * [x] write to temporary file and persist it under new name * [x] close temporary files to convert them into a marker while saving system resources * [x] mark paths with a closed temporary file * [x] persist temporary files to prevent them from perishing. diff --git a/git-tempfile/examples/try-deadlock-on-cleanup.rs b/git-tempfile/examples/try-deadlock-on-cleanup.rs index c6fb6222061..30a38ca20e4 100644 --- a/git-tempfile/examples/try-deadlock-on-cleanup.rs +++ b/git-tempfile/examples/try-deadlock-on-cleanup.rs @@ -52,7 +52,7 @@ fn main() -> Result<(), Box> { let signal_raised = Arc::clone(&signal_raised); move || { eprintln!( - "If a deadlock occours tempfiles will be left in '{}'", + "If a deadlock occurs tempfiles will be left in '{}'", tmp.path().display() ); for ttl in (1..=secs_to_run).rev() { diff --git a/git-tempfile/src/handler.rs b/git-tempfile/src/handler.rs index 5081285a92d..ebccbe5a760 100644 --- a/git-tempfile/src/handler.rs +++ b/git-tempfile/src/handler.rs @@ -7,7 +7,7 @@ use crate::{SignalHandlerMode, NEXT_MAP_INDEX, REGISTER, SIGNAL_HANDLER_MODE}; /// /// # Safety /// Note that Mutexes of any kind are not allowed, and so aren't allocation or deallocation of memory. -/// We are usign lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating. +/// We are using lock-free datastructures and sprinkle in `std::mem::forget` to avoid deallocating. pub fn cleanup_tempfiles() { let current_pid = std::process::id(); let one_past_last_index = NEXT_MAP_INDEX.load(Ordering::SeqCst); diff --git a/git-transport/CHANGELOG.md b/git-transport/CHANGELOG.md index 5940e2e5ba2..149ad91f1e3 100644 --- a/git-transport/CHANGELOG.md +++ b/git-transport/CHANGELOG.md @@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.19.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Thanks Clippy + + + +[Clippy](https://github.com/rust-lang/rust-clippy) helped 1 time to make code idiomatic. + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - Merge branch 'normalize-values' ([`4e8cc7a`](https://github.com/Byron/gitoxide/commit/4e8cc7a5b447656c744cd84e6521e620d0479acb)) + - thanks clippy ([`e1003d5`](https://github.com/Byron/gitoxide/commit/e1003d5fdee5d4439c0cf0286c67dec9b5e34f53)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.18.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +46,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +58,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-transport/Cargo.toml b/git-transport/Cargo.toml index 084313fd2cb..8c380ebc31b 100644 --- a/git-transport/Cargo.toml +++ b/git-transport/Cargo.toml @@ -50,7 +50,7 @@ required-features = ["async-client"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } git-url = { version = "^0.7.0", path = "../git-url" } git-sec = { version = "^0.3.0", path = "../git-sec" } git-packetline = { version = "^0.12.5", path = "../git-packetline" } diff --git a/git-transport/tests/client/git.rs b/git-transport/tests/client/git.rs index da0c89215ee..10e4ebbd005 100644 --- a/git-transport/tests/client/git.rs +++ b/git-transport/tests/client/git.rs @@ -199,9 +199,9 @@ async fn handshake_v2_and_request() -> crate::Result { // This monstrosity simulates how one can process a pack received in async-io by transforming it into // blocking io::BufRead, while still handling the whole operation in a way that won't block the executor. // It's a way of `spawn_blocking()` in other executors. Currently this can only be done on a per-command basis. - // Thinking about it, it's most certainly fine to do `fetch' commands on another thread and move the entire conenction + // Thinking about it, it's most certainly fine to do `fetch' commands on another thread and move the entire connection // there as it's always the end of an operation and a lot of IO is required that is blocking anyway, like accessing - // commit graph information for fetch negotiations, and of cource processing a received pack. + // commit graph information for fetch negotiations, and of course processing a received pack. #[cfg(feature = "async-client")] Ok( blocking::unblock(|| futures_lite::future::block_on(handshake_v2_and_request_inner()).expect("no failure")) diff --git a/git-traverse/CHANGELOG.md b/git-traverse/CHANGELOG.md index c181e035002..9ab6470cf56 100644 --- a/git-traverse/CHANGELOG.md +++ b/git-traverse/CHANGELOG.md @@ -5,6 +5,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.16.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 2 commits contributed to the release. + - 64 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) +
+ ## 0.15.0 (2022-05-18) A maintenance release without user-facing changes. @@ -13,7 +37,7 @@ A maintenance release without user-facing changes. - - 5 commits contributed to the release over the course of 34 calendar days. + - 6 commits contributed to the release over the course of 34 calendar days. - 43 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 2 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#384](https://github.com/Byron/gitoxide/issues/384) @@ -31,6 +55,7 @@ A maintenance release without user-facing changes. - add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2)) - auto-set commit.gpgsign=false when executing git ([`c23feb6`](https://github.com/Byron/gitoxide/commit/c23feb64ad157180cfba8a11c882b829733ea8f6)) * **Uncategorized** + - Release git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0 ([`349c590`](https://github.com/Byron/gitoxide/commit/349c5904b0dac350838a896759d51576b66880a7)) - Release git-hash v0.9.4, git-features v0.21.0, git-actor v0.10.0, git-glob v0.3.0, git-path v0.1.1, git-attributes v0.1.0, git-sec v0.1.0, git-config v0.3.0, git-credentials v0.1.0, git-validate v0.5.4, git-object v0.19.0, git-diff v0.16.0, git-lock v2.1.0, git-ref v0.13.0, git-discover v0.1.0, git-index v0.3.0, git-mailmap v0.2.0, git-traverse v0.15.0, git-pack v0.19.0, git-odb v0.29.0, git-packetline v0.12.5, git-url v0.5.0, git-transport v0.17.0, git-protocol v0.16.0, git-revision v0.2.0, git-worktree v0.2.0, git-repository v0.17.0, safety bump 20 crates ([`654cf39`](https://github.com/Byron/gitoxide/commit/654cf39c92d5aa4c8d542a6cadf13d4acef6a78e))
@@ -86,7 +111,7 @@ A maintenance release without user-facing changes. - - 12 commits contributed to the release over the course of 68 calendar days. + - 11 commits contributed to the release over the course of 68 calendar days. - 69 days passed between releases. - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - 1 unique issue was worked on: [#364](https://github.com/Byron/gitoxide/issues/364) @@ -98,10 +123,10 @@ A maintenance release without user-facing changes.
view details * **[#364](https://github.com/Byron/gitoxide/issues/364)** - - fix docs ([`0a1caeb`](https://github.com/Byron/gitoxide/commit/0a1caebfcb5fe019fc8db3b51d16908da37be59f)) - - require `Ancestors` traversal `find()` to return `Result` ([`83746f6`](https://github.com/Byron/gitoxide/commit/83746f613a559a86a0ea81370fca3f094bc81e35)) - Full error handling for CommitRefIter ([`b94471a`](https://github.com/Byron/gitoxide/commit/b94471a0ced50204156cf5d4126c676f0258a5eb)) - More speedy access to author/committer ([`6129607`](https://github.com/Byron/gitoxide/commit/61296077cebaaf2eb939fa6082121304bc6cf39b)) + - fix docs ([`0a1caeb`](https://github.com/Byron/gitoxide/commit/0a1caebfcb5fe019fc8db3b51d16908da37be59f)) + - require `Ancestors` traversal `find()` to return `Result` ([`83746f6`](https://github.com/Byron/gitoxide/commit/83746f613a559a86a0ea81370fca3f094bc81e35)) * **Uncategorized** - Release git-diff v0.14.0, git-bitmap v0.1.0, git-index v0.2.0, git-tempfile v2.0.1, git-lock v2.0.0, git-mailmap v0.1.0, git-traverse v0.13.0, git-pack v0.17.0, git-quote v0.2.0, git-odb v0.27.0, git-packetline v0.12.4, git-url v0.4.0, git-transport v0.16.0, git-protocol v0.15.0, git-ref v0.12.0, git-worktree v0.1.0, git-repository v0.15.0, cargo-smart-release v0.9.0, safety bump 5 crates ([`e58dc30`](https://github.com/Byron/gitoxide/commit/e58dc3084cf17a9f618ae3a6554a7323e44428bf)) - Merge branch 'for-onefetch' ([`8e5cb65`](https://github.com/Byron/gitoxide/commit/8e5cb65da75036a13ed469334e7ae6c527d9fff6)) @@ -109,7 +134,6 @@ A maintenance release without user-facing changes. - Merge branch 'svetli-n-refactor_git_config_tests' ([`babaa9f`](https://github.com/Byron/gitoxide/commit/babaa9f5725ab8cdf14e0c7e002c2e1de09de103)) - adapt to breaking changes in git-actor ([`40c48c3`](https://github.com/Byron/gitoxide/commit/40c48c390eb796b427ebd516dde92e9538ce5fb7)) - Merge branch 'AP2008-implement-worktree' ([`f32c669`](https://github.com/Byron/gitoxide/commit/f32c669bc519d59a1f1d90d61cc48a422c86aede)) - - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - Merge branch 'index-information' ([`025f157`](https://github.com/Byron/gitoxide/commit/025f157de10a509a4b36a9aed41de80487e8c15c))
@@ -142,7 +166,7 @@ A maintenance release without user-facing changes. - - 15 commits contributed to the release over the course of 51 calendar days. + - 16 commits contributed to the release over the course of 51 calendar days. - 55 days passed between releases. - 5 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#215](https://github.com/Byron/gitoxide/issues/215), [#266](https://github.com/Byron/gitoxide/issues/266), [#270](https://github.com/Byron/gitoxide/issues/270) @@ -175,6 +199,7 @@ A maintenance release without user-facing changes. - Release git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0 ([`d78aab7`](https://github.com/Byron/gitoxide/commit/d78aab7b9c4b431d437ac70a0ef96263acb64e46)) - Release git-hash v0.9.1, git-features v0.19.1, git-actor v0.8.0, git-config v0.1.10, git-object v0.17.0, git-diff v0.13.0, git-tempfile v1.0.4, git-chunk v0.3.0, git-traverse v0.12.0, git-pack v0.16.0, git-odb v0.26.0, git-packetline v0.12.3, git-url v0.3.5, git-transport v0.15.0, git-protocol v0.14.0, git-ref v0.11.0, git-repository v0.14.0, cargo-smart-release v0.8.0, safety bump 4 crates ([`373cbc8`](https://github.com/Byron/gitoxide/commit/373cbc877f7ad60dac682e57c52a7b90f108ebe3)) - prepar changelogs for cargo-smart-release release ([`8900d69`](https://github.com/Byron/gitoxide/commit/8900d699226eb0995be70d66249827ce348261df)) + - Release git-bitmap v0.0.1, git-hash v0.9.0, git-features v0.19.0, git-index v0.1.0, safety bump 9 crates ([`4624725`](https://github.com/Byron/gitoxide/commit/4624725f54a34dd6b35d3632fb3516965922f60a)) - thanks clippy ([`03d0660`](https://github.com/Byron/gitoxide/commit/03d06609002933f23abe37a7208841cd152bd63d)) - Add sorting mode to ancestor traversal #270 ([`eb36a3d`](https://github.com/Byron/gitoxide/commit/eb36a3dda83a46ad59078a904f4e277f298a24e1)) - ensure tests use 'merge.ff false' and recreate fixtures on each run ([`1d5ab44`](https://github.com/Byron/gitoxide/commit/1d5ab44145ccbc2064ee8cc7acebb62db82c45aa)) @@ -294,7 +319,7 @@ Some module paths have been removed to avoid path duplication, possibly leading - - 24 commits contributed to the release over the course of 30 calendar days. + - 24 commits contributed to the release over the course of 32 calendar days. - 36 days passed between releases. - 4 commits where understood as [conventional](https://www.conventionalcommits.org). - 3 unique issues were worked on: [#164](https://github.com/Byron/gitoxide/issues/164), [#196](https://github.com/Byron/gitoxide/issues/196), [#198](https://github.com/Byron/gitoxide/issues/198) diff --git a/git-traverse/Cargo.toml b/git-traverse/Cargo.toml index d8a3bf4d630..2dfbded17c9 100644 --- a/git-traverse/Cargo.toml +++ b/git-traverse/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-traverse" -version = "0.15.0" +version = "0.16.0" repository = "https://github.com/Byron/gitoxide" license = "MIT/Apache-2.0" description = "A WIP crate of the gitoxide project" @@ -14,8 +14,8 @@ doctest = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.4", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } quick-error = "2.0.0" hash_hasher = "2.0.3" diff --git a/git-traverse/src/commit.rs b/git-traverse/src/commit.rs index bad52722a05..c13ec37467e 100644 --- a/git-traverse/src/commit.rs +++ b/git-traverse/src/commit.rs @@ -27,7 +27,7 @@ impl Default for Parents { pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. Topological, - /// Commits are sorted by their commit time in decending order, that is newest first. + /// Commits are sorted by their commit time in descending order, that is newest first. /// /// The sorting applies to all currently queued commit ids and thus is full. ByCommitTimeNewestFirst, diff --git a/git-url/CHANGELOG.md b/git-url/CHANGELOG.md index c081a4901f4..b0f0c698874 100644 --- a/git-url/CHANGELOG.md +++ b/git-url/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.7.0 (2022-07-22) + +### Changed (BREAKING) + + - `From<&[u8]>` is now `From<&BStr>` + This better represents the meaning of the input, and simplifies + interactions with `git-config`. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 1 commit where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - assure document-features are available in all 'usable' and 'early' crates ([`238581c`](https://github.com/Byron/gitoxide/commit/238581cc46c7288691eed37dc7de5069e3d86721)) + - `From<&[u8]>` is now `From<&BStr>` ([`ffc4a85`](https://github.com/Byron/gitoxide/commit/ffc4a85b9a914b685d7ab528b30f2a3eefb44094)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.6.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +44,7 @@ A maintenance release without user-facing changes. - - 1 commit contributed to the release. + - 2 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +56,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-url/Cargo.toml b/git-url/Cargo.toml index 47883dbc7fb..fc5cd573c68 100644 --- a/git-url/Cargo.toml +++ b/git-url/Cargo.toml @@ -19,8 +19,8 @@ serde1 = ["serde", "bstr/serde1"] [dependencies] serde = { version = "1.0.114", optional = true, default-features = false, features = ["std", "derive"]} -git-features = { version = "^0.21.1", path = "../git-features" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-features = { version = "^0.22.0", path = "../git-features" } +git-path = { version = "^0.4.0", path = "../git-path" } quick-error = "2.0.0" url = "2.1.1" bstr = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/git-worktree/CHANGELOG.md b/git-worktree/CHANGELOG.md index 61b0fc61fb3..ca4936d0072 100644 --- a/git-worktree/CHANGELOG.md +++ b/git-worktree/CHANGELOG.md @@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.4.0 (2022-07-22) + +This is a maintenance release with no functional changes. + +### Commit Statistics + + + + - 5 commits contributed to the release over the course of 33 calendar days. + - 39 days passed between releases. + - 0 commits where understood as [conventional](https://www.conventionalcommits.org). + - 0 issues like '(#ID)' where seen in commit messages + +### Commit Details + + + +
view details + + * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - make it harder to forget documentation in git-worktree ([`15d87ee`](https://github.com/Byron/gitoxide/commit/15d87ee99ef269985e8f378bb2ab9c8931e8fd7d)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) +
+ ## 0.3.0 (2022-06-13) A maintenance release without user-facing changes. @@ -13,7 +40,7 @@ A maintenance release without user-facing changes. - - 2 commits contributed to the release. + - 3 commits contributed to the release. - 25 days passed between releases. - 0 commits where understood as [conventional](https://www.conventionalcommits.org). - 0 issues like '(#ID)' where seen in commit messages @@ -25,6 +52,7 @@ A maintenance release without user-facing changes.
view details * **Uncategorized** + - Release git-worktree v0.3.0, git-repository v0.19.0 ([`0d8e856`](https://github.com/Byron/gitoxide/commit/0d8e8566dc5c6955487d67e235f86fbc75a3a88a)) - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980))
diff --git a/git-worktree/Cargo.toml b/git-worktree/Cargo.toml index 84dc5f543d3..0cc78de51e9 100644 --- a/git-worktree/Cargo.toml +++ b/git-worktree/Cargo.toml @@ -30,13 +30,13 @@ internal-testing-git-features-parallel = ["git-features/parallel"] internal-testing-to-avoid-being-run-by-cargo-test-all = [] [dependencies] -git-index = { version = "^0.3.0", path = "../git-index" } -git-hash = { version = "^0.9.5", path = "../git-hash" } -git-object = { version = "^0.19.0", path = "../git-object" } -git-glob = { version = "^0.3.0", path = "../git-glob" } -git-path = { version = "^0.3.0", path = "../git-path" } +git-index = { version = "^0.4.0", path = "../git-index" } +git-hash = { version = "^0.9.6", path = "../git-hash" } +git-object = { version = "^0.20.0", path = "../git-object" } +git-glob = { version = "^0.3.1", path = "../git-glob" } +git-path = { version = "^0.4.0", path = "../git-path" } git-attributes = { version = "^0.3.0", path = "../git-attributes" } -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"]} diff --git a/git-worktree/src/fs/cache/state.rs b/git-worktree/src/fs/cache/state.rs index 47d094b8044..5505b1ba49c 100644 --- a/git-worktree/src/fs/cache/state.rs +++ b/git-worktree/src/fs/cache/state.rs @@ -102,10 +102,10 @@ impl Ignore { if mapping.pattern.is_negative() { dir_match = Some(match_); } else { - // Note that returning here is wrong if this pattern _was_ preceeded by a negative pattern that + // Note that returning here is wrong if this pattern _was_ preceded by a negative pattern that // didn't match the directory, but would match now. // Git does it similarly so we do too even though it's incorrect. - // To fix this, one would probably keep track of whether there was a preceeding negative pattern, and + // To fix this, one would probably keep track of whether there was a preceding negative pattern, and // if so we check the path in full and only use the dir match if there was no match, similar to the negative // case above whose fix fortunately won't change the overall result. return match_.into(); diff --git a/gitoxide-core/CHANGELOG.md b/gitoxide-core/CHANGELOG.md index 8d10a08c40d..33b267910e0 100644 --- a/gitoxide-core/CHANGELOG.md +++ b/gitoxide-core/CHANGELOG.md @@ -5,7 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.15.0 (2022-07-22) + +### Changed (BREAKING) + + - remove local-time-support feature toggle. + We treat local time as default feature without a lot of fuzz, and + will eventually document that definitive support needs a compile + time switch in the compiler (`--cfg unsound_local_offset` or something). + + One day it will perish. Failure is possible anyway and we will write + code to deal with it while minimizing the amount of system time + fetches when asking for the current local time. + - Associate `file::Metadata` with each `File`. + This is the first step towards knowing more about the source of each + value to filter them based on some properties. + + This breaks various methods handling the instantiation of configuration + files as `file::Metadata` typically has to be provided by the caller + now or be associated with each path to read configuration from. + +### New Features + + - `gix config` with section and sub-section filtering. + - `gix config` lists all entries of all configuration files git considers. + Filters allow to narrow down the output. + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. ### Bug Fixes @@ -19,10 +44,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - - 35 commits contributed to the release over the course of 61 calendar days. - - 67 days passed between releases. - - 1 commit where understood as [conventional](https://www.conventionalcommits.org). - - 1 unique issue was worked on: [#301](https://github.com/Byron/gitoxide/issues/301) + - 63 commits contributed to the release over the course of 101 calendar days. + - 107 days passed between releases. + - 6 commits where understood as [conventional](https://www.conventionalcommits.org). + - 3 unique issues were worked on: [#301](https://github.com/Byron/gitoxide/issues/301), [#331](https://github.com/Byron/gitoxide/issues/331), [#427](https://github.com/Byron/gitoxide/issues/427) ### Thanks Clippy @@ -56,7 +81,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - fix build ([`ffe92ca`](https://github.com/Byron/gitoxide/commit/ffe92ca3cf066c09020bf6fa875bea06552cbd0d)) - Make attributes and ignore configuration possible, but… ([`8a75fd7`](https://github.com/Byron/gitoxide/commit/8a75fd745a194786f0da7c1fd660211446ea51f7)) - make fmt ([`50ff7aa`](https://github.com/Byron/gitoxide/commit/50ff7aa7fa86e5e2a94fb15aab86470532ac3f51)) + * **[#331](https://github.com/Byron/gitoxide/issues/331)** + - Group similarly named sections together more by not separating them with newline ([`4c69541`](https://github.com/Byron/gitoxide/commit/4c69541cd7192ebd5bdd696a833992d5a52cd9b6)) + - Make lossy-configuration configurable ([`b0e4da6`](https://github.com/Byron/gitoxide/commit/b0e4da621114d188a73b9f40757f59564da3c079)) + - update README with `gix config` information ([`c19d9fd`](https://github.com/Byron/gitoxide/commit/c19d9fdc569528972f7f6255760ae86ba99848cc)) + - remove local-time-support feature toggle. ([`89a41bf`](https://github.com/Byron/gitoxide/commit/89a41bf2b37db29b9983b4e5492cfd67ed490b23)) + - `gix config` with section and sub-section filtering. ([`eda39ec`](https://github.com/Byron/gitoxide/commit/eda39ec7d736d49af1ad9e2ad775e4aa12b264b7)) + - `gix config` lists all entries of all configuration files git considers. ([`d99453e`](https://github.com/Byron/gitoxide/commit/d99453ebeb970ed493be236def299d1e82b01f83)) + - Associate `file::Metadata` with each `File`. ([`6f4eea9`](https://github.com/Byron/gitoxide/commit/6f4eea936d64fb9827277c160f989168e7b1dba2)) + - Use `git-config` to write config file on initialization, including `logallrefupdates` and `precomposeunicode`. ([`7f67b23`](https://github.com/Byron/gitoxide/commit/7f67b23b9462b805591b1fe5a8406f8d7404f372)) + - adjust to changes in `git-config` ([`c52cb95`](https://github.com/Byron/gitoxide/commit/c52cb958f85b533e791ec6b38166a9d819f12dd4)) + * **[#427](https://github.com/Byron/gitoxide/issues/427)** + - Handle 'kind' changes which completes 'explain' ([`45022a0`](https://github.com/Byron/gitoxide/commit/45022a0efe6e71404868a7ba816c6972050098b9)) + - Support for explaining all navitation ([`ace9c89`](https://github.com/Byron/gitoxide/commit/ace9c8953bebc4a808c639e365010ed53c031622)) + - start navigation implementation ([`ea1c009`](https://github.com/Byron/gitoxide/commit/ea1c009e1b064deccf242fc60876a8535f4814b5)) + - Implement `Revision` anchors ([`a1f0e3d`](https://github.com/Byron/gitoxide/commit/a1f0e3d463397be201f4df40184ce38b830f3bde)) + - basic infrastructure for delegate implementation ([`d3c0bc6`](https://github.com/Byron/gitoxide/commit/d3c0bc6e8d7764728f4e10500bb895152ccd0b0b)) + - Hookup explain command ([`1049b00`](https://github.com/Byron/gitoxide/commit/1049b00eaa261a67f060eaca4eb50dcda831eafd)) * **Uncategorized** + - Release git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0 ([`aa639d8`](https://github.com/Byron/gitoxide/commit/aa639d8c43f3098cc4a5b50614c5ae94a8156928)) + - Release git-hash v0.9.6, git-features v0.22.0, git-date v0.0.2, git-actor v0.11.0, git-glob v0.3.1, git-path v0.4.0, git-attributes v0.3.0, git-tempfile v2.0.2, git-object v0.20.0, git-ref v0.15.0, git-sec v0.3.0, git-config v0.6.0, git-credentials v0.3.0, git-diff v0.17.0, git-discover v0.3.0, git-index v0.4.0, git-mailmap v0.3.0, git-traverse v0.16.0, git-pack v0.21.0, git-odb v0.31.0, git-url v0.7.0, git-transport v0.19.0, git-protocol v0.18.0, git-revision v0.3.0, git-worktree v0.4.0, git-repository v0.20.0, git-commitgraph v0.8.0, gitoxide-core v0.15.0, gitoxide v0.13.0, safety bump 22 crates ([`4737b1e`](https://github.com/Byron/gitoxide/commit/4737b1eea1d4c9a8d5a69fb63ecac5aa5d378ae5)) + - prepare changelog prior to release ([`3c50625`](https://github.com/Byron/gitoxide/commit/3c50625fa51350ec885b0f38ec9e92f9444df0f9)) + - make fmt ([`0700b09`](https://github.com/Byron/gitoxide/commit/0700b09d6828849fa2470df89af1f75a67bfb27d)) + - fix build after changes to `git-url` and `git-config` ([`1f02420`](https://github.com/Byron/gitoxide/commit/1f0242034071ce317743df75cc685e7428b604b0)) + - Merge branch 'main' into cont_include_if ([`daa71c3`](https://github.com/Byron/gitoxide/commit/daa71c3b753c6d76a3d652c29237906b3e28728f)) + - Merge branch 'main' into cont_include_if ([`0e9df36`](https://github.com/Byron/gitoxide/commit/0e9df364c4cddf006b1de18b8d167319b7cc1186)) + - Merge branch 'main' into cont_include_if ([`41ea8ba`](https://github.com/Byron/gitoxide/commit/41ea8ba78e74f5c988148367386a1f4f304cb951)) + - Release git-path v0.3.0, safety bump 14 crates ([`400c9be`](https://github.com/Byron/gitoxide/commit/400c9bec49e4ec5351dc9357b246e7677a63ea35)) + - Release git-date v0.0.1, git-hash v0.9.5, git-features v0.21.1, git-actor v0.10.1, git-path v0.2.0, git-attributes v0.2.0, git-ref v0.14.0, git-sec v0.2.0, git-config v0.5.0, git-credentials v0.2.0, git-discover v0.2.0, git-pack v0.20.0, git-odb v0.30.0, git-url v0.6.0, git-transport v0.18.0, git-protocol v0.17.0, git-revision v0.2.1, git-worktree v0.3.0, git-repository v0.19.0, safety bump 13 crates ([`a417177`](https://github.com/Byron/gitoxide/commit/a41717712578f590f04a33d27adaa63171f25267)) + - update changelogs prior to release ([`bb424f5`](https://github.com/Byron/gitoxide/commit/bb424f51068b8a8e762696890a55ab48900ab980)) + - Merge branch 'main' into svetli-n-cont_include_if ([`315c87e`](https://github.com/Byron/gitoxide/commit/315c87e18c6cac0fafa7b4e59fdd3c076a58a45a)) + - Refact. ([`a342e53`](https://github.com/Byron/gitoxide/commit/a342e53dac58cea1787a94eaa1a9d24fb1389df2)) - `ein tool organize` now ignores worktrees. ([`5667a7c`](https://github.com/Byron/gitoxide/commit/5667a7c1bafcfdff1a278b3ad0e1198cd0cc4653)) - Revert "ignore worktrees in 'organize', but…" ([`f59471f`](https://github.com/Byron/gitoxide/commit/f59471f0cf883176594ab4635248b4029bcb6caf)) - ignore worktrees in 'organize', but… ([`e501c9e`](https://github.com/Byron/gitoxide/commit/e501c9e6348e1595fee4a5e0bd712fc2433b10df)) diff --git a/gitoxide-core/Cargo.toml b/gitoxide-core/Cargo.toml index 2a9d754c968..388c5096708 100644 --- a/gitoxide-core/Cargo.toml +++ b/gitoxide-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gitoxide-core" -description = "The library implementating all capabilities of the gitoxide CLI" +description = "The library implementing all capabilities of the gitoxide CLI" repository = "https://github.com/Byron/gitoxide" version = "0.15.0" authors = ["Sebastian Thiel "] @@ -32,8 +32,6 @@ async-client = ["git-repository/async-network-client", "async-trait", "futures-i #! ### Other ## Data structures implement `serde::Serialize` and `serde::Deserialize`. serde1 = ["git-commitgraph/serde1", "git-repository/serde1", "serde_json", "serde"] -## Functions dealing with time may include the local timezone offset, not just UTC with the offset being zero. -local-time-support = ["git-repository/local-time-support"] [dependencies] @@ -42,7 +40,7 @@ git-repository = { version = "^0.20.0", path = "../git-repository", default-feat git-pack-for-configuration-only = { package = "git-pack", version = "^0.21.0", path = "../git-pack", default-features = false, features = ["pack-cache-lru-dynamic", "pack-cache-lru-static"] } git-commitgraph = { version = "^0.8.0", path = "../git-commitgraph" } git-config = { version = "^0.6.0", path = "../git-config" } -git-features = { version = "^0.21.1", path = "../git-features" } +git-features = { version = "^0.22.0", path = "../git-features" } serde = { version = "1.0.114", optional = true, default-features = false, features = ["derive"] } anyhow = "1.0.42" quick-error = "2.0.0" diff --git a/gitoxide-core/src/net.rs b/gitoxide-core/src/net.rs index 19d4122b102..96ed0137396 100644 --- a/gitoxide-core/src/net.rs +++ b/gitoxide-core/src/net.rs @@ -36,7 +36,7 @@ mod impls { impl Default for Protocol { fn default() -> Self { - // Note that it's very important this remains V2, as V1 may block forver in stateful (i.e. non-http) connections when fetching + // Note that it's very important this remains V2, as V1 may block forever in stateful (i.e. non-http) connections when fetching // as we chose not to complicate matters by counting which arguments where sent (just yet). Protocol::V2 } diff --git a/gitoxide-core/src/organize.rs b/gitoxide-core/src/organize.rs index d60f6cbe883..330c69880bc 100644 --- a/gitoxide-core/src/organize.rs +++ b/gitoxide-core/src/organize.rs @@ -101,8 +101,9 @@ where fn find_origin_remote(repo: &Path) -> anyhow::Result> { let non_bare = repo.join(".git").join("config"); - let config = File::from_path_with_buf(non_bare.as_path(), &mut Vec::new()) - .or_else(|_| File::from_path_with_buf(repo.join("config").as_path(), &mut Vec::new()))?; + let local = git_config::Source::Local; + let config = File::from_path_no_includes(non_bare.as_path(), local) + .or_else(|_| File::from_path_no_includes(repo.join("config").as_path(), local))?; Ok(config .string("remote", Some("origin"), "url") .map(|url| git_url::Url::from_bytes(url.as_ref())) diff --git a/gitoxide-core/src/pack/create.rs b/gitoxide-core/src/pack/create.rs index 76658024aff..ddb6b4a5b4c 100644 --- a/gitoxide-core/src/pack/create.rs +++ b/gitoxide-core/src/pack/create.rs @@ -66,7 +66,7 @@ impl From for pack::data::output::count::objects::ObjectExpansi pub struct Context { /// The way input objects should be handled pub expansion: ObjectExpansion, - /// If `Some(threads)`, use this amount of `threads` to accelerate the counting phase at the cost of loosing + /// If `Some(threads)`, use this amount of `threads` to accelerate the counting phase at the cost of losing /// determinism as the order of objects during expansion changes with multiple threads unless no expansion is performed. /// In the latter case, this flag has no effect. /// If `None`, counting will only use one thread and thus yield the same sequence of objects in any case. diff --git a/gitoxide-core/src/repository/config.rs b/gitoxide-core/src/repository/config.rs new file mode 100644 index 00000000000..a179e2c1db2 --- /dev/null +++ b/gitoxide-core/src/repository/config.rs @@ -0,0 +1,104 @@ +use anyhow::{bail, Result}; +use git_repository as git; + +use crate::OutputFormat; + +pub fn list( + repo: git::Repository, + filters: Vec, + format: OutputFormat, + mut out: impl std::io::Write, +) -> Result<()> { + if format != OutputFormat::Human { + bail!("Only human output format is supported at the moment"); + } + let repo = git::open_opts(repo.git_dir(), repo.open_options().clone().lossy_config(false))?; + let config = repo.config_snapshot(); + let config = config.plumbing(); + if let Some(frontmatter) = config.frontmatter() { + for event in frontmatter { + event.write_to(&mut out)?; + } + } + let filters: Vec<_> = filters.into_iter().map(Filter::new).collect(); + let mut last_meta = None; + let mut it = config.sections_and_postmatter().peekable(); + while let Some((section, matter)) = it.next() { + if !filters.is_empty() && !filters.iter().any(|filter| filter.matches_section(section)) { + continue; + } + + let meta = section.meta(); + if last_meta.map_or(true, |last| last != meta) { + write_meta(meta, &mut out)?; + } + last_meta = Some(meta); + + section.write_to(&mut out)?; + for event in matter { + event.write_to(&mut out)?; + } + if it.peek().map_or(false, |(next_section, _)| { + next_section.header().name() != section.header().name() + }) { + writeln!(&mut out)?; + } + } + Ok(()) +} + +struct Filter { + name: String, + subsection: Option, +} + +impl Filter { + fn new(input: String) -> Self { + match git::config::parse::key(&input) { + Some(key) => Filter { + name: key.section_name.into(), + subsection: key.subsection_name.map(ToOwned::to_owned), + }, + None => Filter { + name: input, + subsection: None, + }, + } + } + + fn matches_section(&self, section: &git::config::file::Section<'_>) -> bool { + let ignore_case = git::glob::wildmatch::Mode::IGNORE_CASE; + + if !git::glob::wildmatch(self.name.as_bytes().into(), section.header().name(), ignore_case) { + return false; + } + match (self.subsection.as_deref(), section.header().subsection_name()) { + (Some(filter), Some(name)) => { + if !git::glob::wildmatch(filter.as_bytes().into(), name, ignore_case) { + return false; + } + } + (None, None) | (None, Some(_)) => {} + _ => return false, + }; + true + } +} + +fn write_meta(meta: &git::config::file::Metadata, out: &mut impl std::io::Write) -> std::io::Result<()> { + writeln!( + out, + "# From '{}' ({:?}{}{})", + meta.path + .as_deref() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| "memory".into()), + meta.source, + (meta.level != 0) + .then(|| format!(", include level {}", meta.level)) + .unwrap_or_default(), + (meta.trust != git::sec::Trust::Full) + .then(|| ", untrusted") + .unwrap_or_default() + ) +} diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index d7d620fb28c..71ce148d319 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -14,6 +14,8 @@ pub fn init(directory: Option) -> Result Result<()> { let format = args.format; let cmd = args.cmd; let object_hash = args.object_hash; + use git_repository as git; + let repository = args.repository; + let repository = move || git::ThreadSafeRepository::discover(repository); let progress; let progress_keep_open; @@ -74,580 +75,582 @@ pub fn main() -> Result<()> { })?; match cmd { - Subcommands::Mailmap(mailmap::Platform { path, cmd }) => match cmd { - mailmap::Subcommands::Verify => prepare_and_run( - "mailmap-verify", - verbose, - progress, - progress_keep_open, - core::mailmap::PROGRESS_RANGE, - move |_progress, out, _err| core::mailmap::verify(path, format, out), - ), - }, - Subcommands::Index(index::Platform { - object_hash, - index_path, - cmd, - }) => match cmd { - index::Subcommands::CheckoutExclusive { - directory, - empty_files, - repository, - keep_going, - } => prepare_and_run( - "index-checkout", - verbose, - progress, - progress_keep_open, - None, - move |progress, _out, err| { - core::index::checkout_exclusive( - index_path, - directory, - repository, - err, - progress, - &should_interrupt, - core::index::checkout_exclusive::Options { - index: core::index::Options { object_hash, format }, - empty_files, - keep_going, + Subcommands::Config(config::Platform { filter }) => prepare_and_run( + "config-list", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::config::list(repository()?.into(), filter, format, out), + ) + .map(|_| ()), + Subcommands::Free(subcommands) => match subcommands { + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + free::Subcommands::Remote(subcommands) => match subcommands { + #[cfg(feature = "gitoxide-core-async-client")] + free::remote::Subcommands::RefList { protocol, url } => { + let (_handle, progress) = + async_util::prepare(verbose, "remote-ref-list", Some(core::remote::refs::PROGRESS_RANGE)); + let fut = core::remote::refs::list( + protocol, + &url, + git_features::progress::DoOrDiscard::from(progress), + core::remote::refs::Context { thread_limit, + format, + out: std::io::stdout(), }, - ) - }, - ), - index::Subcommands::Info { no_details } => prepare_and_run( - "index-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::index::information( - index_path, - out, - err, - core::index::information::Options { - index: core::index::Options { object_hash, format }, - extension_details: !no_details, - }, - ) - }, - ), - index::Subcommands::Entries => prepare_and_run( - "index-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::index::entries(index_path, out, core::index::Options { object_hash, format }) - }, - ), - index::Subcommands::Verify => prepare_and_run( - "index-verify", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::index::verify(index_path, out, core::index::Options { object_hash, format }) - }, - ), - }, - Subcommands::Repository(repo::Platform { repository, cmd }) => { - use git_repository as git; - let repository = git::ThreadSafeRepository::discover(repository)?; - match cmd { - repo::Subcommands::Revision { cmd } => match cmd { - repo::revision::Subcommands::Explain { spec } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::revision::explain(repository.into(), spec, out), - ), - }, - repo::Subcommands::Commit { cmd } => match cmd { - repo::commit::Subcommands::Describe { - annotated_tags, - all_refs, - first_parent, - always, - long, - statistics, - max_candidates, - rev_spec, - } => prepare_and_run( - "repository-commit-describe", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::commit::describe( - repository.into(), - rev_spec.as_deref(), - out, - err, - core::repository::commit::describe::Options { - all_tags: !annotated_tags, - all_refs, - long_format: long, - first_parent, - statistics, - max_candidates, - always, - }, - ) - }, - ), - }, - repo::Subcommands::Exclude { cmd } => match cmd { - repo::exclude::Subcommands::Query { - patterns, - pathspecs, - show_ignore_patterns, - } => prepare_and_run( - "repository-exclude-query", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - use git::bstr::ByteSlice; - core::repository::exclude::query( - repository.into(), - if pathspecs.is_empty() { - Box::new( - stdin_or_bail()? - .byte_lines() - .filter_map(Result::ok) - .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), - ) as Box> - } else { - Box::new(pathspecs.into_iter()) - }, - out, - core::repository::exclude::query::Options { - format, - show_ignore_patterns, - overrides: patterns, - }, - ) - }, - ), - }, - repo::Subcommands::Mailmap { cmd } => match cmd { - repo::mailmap::Subcommands::Entries => prepare_and_run( - "repository-mailmap-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::mailmap::entries(repository.into(), format, out, err) - }, - ), - }, - repo::Subcommands::Odb { cmd } => match cmd { - repo::odb::Subcommands::Entries => prepare_and_run( - "repository-odb-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| core::repository::odb::entries(repository.into(), format, out), - ), - repo::odb::Subcommands::Info => prepare_and_run( - "repository-odb-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| core::repository::odb::info(repository.into(), format, out, err), - ), - }, - repo::Subcommands::Tree { cmd } => match cmd { - repo::tree::Subcommands::Entries { - treeish, - recursive, - extended, - } => prepare_and_run( - "repository-tree-entries", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, _err| { - core::repository::tree::entries( - repository.into(), - treeish.as_deref(), - recursive, - extended, - format, - out, - ) - }, - ), - repo::tree::Subcommands::Info { treeish, extended } => prepare_and_run( - "repository-tree-info", - verbose, - progress, - progress_keep_open, - None, - move |_progress, out, err| { - core::repository::tree::info( - repository.into(), - treeish.as_deref(), - extended, + ); + return futures_lite::future::block_on(fut); + } + #[cfg(feature = "gitoxide-core-blocking-client")] + free::remote::Subcommands::RefList { protocol, url } => prepare_and_run( + "remote-ref-list", + verbose, + progress, + progress_keep_open, + core::remote::refs::PROGRESS_RANGE, + move |progress, out, _err| { + core::remote::refs::list( + protocol, + &url, + progress, + core::remote::refs::Context { + thread_limit, format, out, + }, + ) + }, + ), + }, + free::Subcommands::CommitGraph(subcommands) => match subcommands { + free::commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( + "commitgraph-verify", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + let output_statistics = if statistics { Some(format) } else { None }; + core::commitgraph::verify::graph_or_file( + path, + core::commitgraph::verify::Context { err, - ) - }, - ), - }, - repo::Subcommands::Verify { - args: - pack::VerifyOptions { - statistics, - algorithm, - decode, - re_encode, - }, + out, + output_statistics, + }, + ) + }, + ) + .map(|_| ()), + }, + free::Subcommands::Index(free::index::Platform { + object_hash, + index_path, + cmd, + }) => match cmd { + free::index::Subcommands::CheckoutExclusive { + directory, + empty_files, + repository, + keep_going, } => prepare_and_run( - "repository-verify", + "index-checkout", verbose, progress, progress_keep_open, - core::repository::verify::PROGRESS_RANGE, - move |progress, out, _err| { - core::repository::verify::integrity( - repository.into(), - out, + None, + move |progress, _out, err| { + core::index::checkout_exclusive( + index_path, + directory, + repository, + err, progress, &should_interrupt, - core::repository::verify::Context { - output_statistics: statistics.then(|| format), - algorithm, - verify_mode: verify_mode(decode, re_encode), + core::index::checkout_exclusive::Options { + index: core::index::Options { object_hash, format }, + empty_files, + keep_going, thread_limit, }, ) }, ), - } - } - Subcommands::Pack(subcommands) => match subcommands { - pack::Subcommands::Create { - repository, - expansion, - thin, - statistics, - nondeterministic_count, - tips, - pack_cache_size_mb, - counting_threads, - object_cache_size_mb, - output_directory, - } => { - let has_tips = !tips.is_empty(); - prepare_and_run( - "pack-create", + free::index::Subcommands::Info { no_details } => prepare_and_run( + "index-entries", verbose, progress, progress_keep_open, - core::pack::create::PROGRESS_RANGE, - move |progress, out, _err| { - let input = if has_tips { None } else { stdin_or_bail()?.into() }; - let repository = repository.unwrap_or_else(|| PathBuf::from(".")); - let context = core::pack::create::Context { - thread_limit, - thin, - nondeterministic_thread_count: nondeterministic_count.then(|| counting_threads), - pack_cache_size_in_bytes: pack_cache_size_mb.unwrap_or(0) * 1_000_000, - object_cache_size_in_bytes: object_cache_size_mb.unwrap_or(0) * 1_000_000, - statistics: if statistics { Some(format) } else { None }, + None, + move |_progress, out, err| { + core::index::information( + index_path, out, - expansion: expansion.unwrap_or(if has_tips { - core::pack::create::ObjectExpansion::TreeTraversal - } else { - core::pack::create::ObjectExpansion::None - }), - }; - core::pack::create(repository, tips, input, output_directory, progress, context) + err, + core::index::information::Options { + index: core::index::Options { object_hash, format }, + extension_details: !no_details, + }, + ) }, - ) - } - #[cfg(feature = "gitoxide-core-async-client")] - pack::Subcommands::Receive { - protocol, - url, - directory, - refs, - refs_directory, - } => { - let (_handle, progress) = - async_util::prepare(verbose, "pack-receive", core::pack::receive::PROGRESS_RANGE); - let fut = core::pack::receive( + ), + free::index::Subcommands::Entries => prepare_and_run( + "index-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::entries(index_path, out, core::index::Options { object_hash, format }) + }, + ), + free::index::Subcommands::Verify => prepare_and_run( + "index-verify", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::index::verify(index_path, out, core::index::Options { object_hash, format }) + }, + ), + }, + free::Subcommands::Mailmap { + cmd: free::mailmap::Platform { path, cmd }, + } => match cmd { + free::mailmap::Subcommands::Verify => prepare_and_run( + "mailmap-verify", + verbose, + progress, + progress_keep_open, + core::mailmap::PROGRESS_RANGE, + move |_progress, out, _err| core::mailmap::verify(path, format, out), + ), + }, + free::Subcommands::Pack(subcommands) => match subcommands { + free::pack::Subcommands::Create { + repository, + expansion, + thin, + statistics, + nondeterministic_count, + tips, + pack_cache_size_mb, + counting_threads, + object_cache_size_mb, + output_directory, + } => { + let has_tips = !tips.is_empty(); + prepare_and_run( + "pack-create", + verbose, + progress, + progress_keep_open, + core::pack::create::PROGRESS_RANGE, + move |progress, out, _err| { + let input = if has_tips { None } else { stdin_or_bail()?.into() }; + let repository = repository.unwrap_or_else(|| PathBuf::from(".")); + let context = core::pack::create::Context { + thread_limit, + thin, + nondeterministic_thread_count: nondeterministic_count.then(|| counting_threads), + pack_cache_size_in_bytes: pack_cache_size_mb.unwrap_or(0) * 1_000_000, + object_cache_size_in_bytes: object_cache_size_mb.unwrap_or(0) * 1_000_000, + statistics: if statistics { Some(format) } else { None }, + out, + expansion: expansion.unwrap_or(if has_tips { + core::pack::create::ObjectExpansion::TreeTraversal + } else { + core::pack::create::ObjectExpansion::None + }), + }; + core::pack::create(repository, tips, input, output_directory, progress, context) + }, + ) + } + #[cfg(feature = "gitoxide-core-async-client")] + free::pack::Subcommands::Receive { protocol, - &url, + url, directory, + refs, refs_directory, - refs.into_iter().map(|s| s.into()).collect(), - git_features::progress::DoOrDiscard::from(progress), - core::pack::receive::Context { - thread_limit, - format, - out: std::io::stdout(), - should_interrupt, - object_hash, - }, - ); - return futures_lite::future::block_on(fut); - } - #[cfg(feature = "gitoxide-core-blocking-client")] - pack::Subcommands::Receive { - protocol, - url, - directory, - refs, - refs_directory, - } => prepare_and_run( - "pack-receive", - verbose, - progress, - progress_keep_open, - core::pack::receive::PROGRESS_RANGE, - move |progress, out, _err| { - core::pack::receive( + } => { + let (_handle, progress) = + async_util::prepare(verbose, "pack-receive", core::pack::receive::PROGRESS_RANGE); + let fut = core::pack::receive( protocol, &url, directory, refs_directory, - refs.into_iter().map(|r| r.into()).collect(), - progress, + refs.into_iter().map(|s| s.into()).collect(), + git_features::progress::DoOrDiscard::from(progress), core::pack::receive::Context { thread_limit, format, + out: std::io::stdout(), should_interrupt, - out, object_hash, }, - ) - }, - ), - pack::Subcommands::Explode { - check, - sink_compress, - delete_pack, - pack_path, - object_path, - verify, - } => prepare_and_run( - "pack-explode", - verbose, - progress, - progress_keep_open, - None, - move |progress, _out, _err| { - core::pack::explode::pack_or_pack_index( - pack_path, - object_path, - check, - progress, - core::pack::explode::Context { - thread_limit, - delete_pack, - sink_compress, - verify, - should_interrupt, - object_hash, - }, - ) - }, - ), - pack::Subcommands::Verify { - args: - pack::VerifyOptions { - algorithm, - decode, - re_encode, - statistics, - }, - path, - } => prepare_and_run( - "pack-verify", - verbose, - progress, - progress_keep_open, - verify::PROGRESS_RANGE, - move |progress, out, err| { - let mode = verify_mode(decode, re_encode); - let output_statistics = if statistics { Some(format) } else { None }; - verify::pack_or_pack_index( - path, - progress, - verify::Context { - output_statistics, - out, - err, - thread_limit, - mode, - algorithm, - should_interrupt: &should_interrupt, - object_hash, - }, - ) - }, - ) - .map(|_| ()), - pack::Subcommands::MultiIndex(multi_index::Platform { multi_index_path, cmd }) => match cmd { - pack::multi_index::Subcommands::Entries => prepare_and_run( - "pack-multi-index-entries", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |_progress, out, _err| core::pack::multi_index::entries(multi_index_path, format, out), - ), - pack::multi_index::Subcommands::Info => prepare_and_run( - "pack-multi-index-info", - verbose, - progress, - progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |_progress, out, err| core::pack::multi_index::info(multi_index_path, format, out, err), - ), - pack::multi_index::Subcommands::Verify => prepare_and_run( - "pack-multi-index-verify", + ); + return futures_lite::future::block_on(fut); + } + #[cfg(feature = "gitoxide-core-blocking-client")] + free::pack::Subcommands::Receive { + protocol, + url, + directory, + refs, + refs_directory, + } => prepare_and_run( + "pack-receive", verbose, progress, progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, - move |progress, _out, _err| { - core::pack::multi_index::verify(multi_index_path, progress, &should_interrupt) + core::pack::receive::PROGRESS_RANGE, + move |progress, out, _err| { + core::pack::receive( + protocol, + &url, + directory, + refs_directory, + refs.into_iter().map(|r| r.into()).collect(), + progress, + core::pack::receive::Context { + thread_limit, + format, + should_interrupt, + out, + object_hash, + }, + ) }, ), - pack::multi_index::Subcommands::Create { index_paths } => prepare_and_run( - "pack-multi-index-create", + free::pack::Subcommands::Explode { + check, + sink_compress, + delete_pack, + pack_path, + object_path, + verify, + } => prepare_and_run( + "pack-explode", verbose, progress, progress_keep_open, - core::pack::multi_index::PROGRESS_RANGE, + None, move |progress, _out, _err| { - core::pack::multi_index::create( - index_paths, - multi_index_path, + core::pack::explode::pack_or_pack_index( + pack_path, + object_path, + check, progress, - &should_interrupt, - object_hash, + core::pack::explode::Context { + thread_limit, + delete_pack, + sink_compress, + verify, + should_interrupt, + object_hash, + }, ) }, ), - }, - pack::Subcommands::Index(subcommands) => match subcommands { - pack::index::Subcommands::Create { - iteration_mode, - pack_path, - directory, + free::pack::Subcommands::Verify { + args: + free::pack::VerifyOptions { + algorithm, + decode, + re_encode, + statistics, + }, + path, } => prepare_and_run( - "pack-index-create", + "pack-verify", verbose, progress, progress_keep_open, - core::pack::index::PROGRESS_RANGE, - move |progress, out, _err| { - use gitoxide_core::pack::index::PathOrRead; - let input = if let Some(path) = pack_path { - PathOrRead::Path(path) - } else { - if atty::is(atty::Stream::Stdin) { - anyhow::bail!( - "Refusing to read from standard input as no path is given, but it's a terminal." - ) - } - PathOrRead::Read(Box::new(std::io::stdin())) - }; - core::pack::index::from_pack( - input, - directory, + verify::PROGRESS_RANGE, + move |progress, out, err| { + let mode = verify_mode(decode, re_encode); + let output_statistics = if statistics { Some(format) } else { None }; + verify::pack_or_pack_index( + path, progress, - core::pack::index::Context { - thread_limit, - iteration_mode, - format, + verify::Context { + output_statistics, out, + err, + thread_limit, + mode, + algorithm, + should_interrupt: &should_interrupt, object_hash, - should_interrupt: &git_repository::interrupt::IS_INTERRUPTED, }, ) }, - ), + ) + .map(|_| ()), + free::pack::Subcommands::MultiIndex(free::pack::multi_index::Platform { multi_index_path, cmd }) => { + match cmd { + free::pack::multi_index::Subcommands::Entries => prepare_and_run( + "pack-multi-index-entries", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |_progress, out, _err| core::pack::multi_index::entries(multi_index_path, format, out), + ), + free::pack::multi_index::Subcommands::Info => prepare_and_run( + "pack-multi-index-info", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |_progress, out, err| { + core::pack::multi_index::info(multi_index_path, format, out, err) + }, + ), + free::pack::multi_index::Subcommands::Verify => prepare_and_run( + "pack-multi-index-verify", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |progress, _out, _err| { + core::pack::multi_index::verify(multi_index_path, progress, &should_interrupt) + }, + ), + free::pack::multi_index::Subcommands::Create { index_paths } => prepare_and_run( + "pack-multi-index-create", + verbose, + progress, + progress_keep_open, + core::pack::multi_index::PROGRESS_RANGE, + move |progress, _out, _err| { + core::pack::multi_index::create( + index_paths, + multi_index_path, + progress, + &should_interrupt, + object_hash, + ) + }, + ), + } + } + free::pack::Subcommands::Index(subcommands) => match subcommands { + free::pack::index::Subcommands::Create { + iteration_mode, + pack_path, + directory, + } => prepare_and_run( + "pack-index-create", + verbose, + progress, + progress_keep_open, + core::pack::index::PROGRESS_RANGE, + move |progress, out, _err| { + use gitoxide_core::pack::index::PathOrRead; + let input = if let Some(path) = pack_path { + PathOrRead::Path(path) + } else { + if atty::is(atty::Stream::Stdin) { + anyhow::bail!( + "Refusing to read from standard input as no path is given, but it's a terminal." + ) + } + PathOrRead::Read(Box::new(std::io::stdin())) + }; + core::pack::index::from_pack( + input, + directory, + progress, + core::pack::index::Context { + thread_limit, + iteration_mode, + format, + out, + object_hash, + should_interrupt: &git_repository::interrupt::IS_INTERRUPTED, + }, + ) + }, + ), + }, }, }, - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Subcommands::Remote(subcommands) => match subcommands { - #[cfg(feature = "gitoxide-core-async-client")] - remote::Subcommands::RefList { protocol, url } => { - let (_handle, progress) = - async_util::prepare(verbose, "remote-ref-list", Some(core::remote::refs::PROGRESS_RANGE)); - let fut = core::remote::refs::list( - protocol, - &url, - git_features::progress::DoOrDiscard::from(progress), - core::remote::refs::Context { + Subcommands::Verify { + args: + free::pack::VerifyOptions { + statistics, + algorithm, + decode, + re_encode, + }, + } => prepare_and_run( + "verify", + verbose, + progress, + progress_keep_open, + core::repository::verify::PROGRESS_RANGE, + move |progress, out, _err| { + core::repository::verify::integrity( + repository()?.into(), + out, + progress, + &should_interrupt, + core::repository::verify::Context { + output_statistics: statistics.then(|| format), + algorithm, + verify_mode: verify_mode(decode, re_encode), thread_limit, - format, - out: std::io::stdout(), }, - ); - return futures_lite::future::block_on(fut); - } - #[cfg(feature = "gitoxide-core-blocking-client")] - remote::Subcommands::RefList { protocol, url } => prepare_and_run( - "remote-ref-list", + ) + }, + ), + Subcommands::Revision(cmd) => match cmd { + revision::Subcommands::Explain { spec } => prepare_and_run( + "commit-describe", verbose, progress, progress_keep_open, - core::remote::refs::PROGRESS_RANGE, - move |progress, out, _err| { - core::remote::refs::list( - protocol, - &url, - progress, - core::remote::refs::Context { - thread_limit, - format, - out, + None, + move |_progress, out, _err| core::repository::revision::explain(repository()?.into(), spec, out), + ), + }, + Subcommands::Commit(cmd) => match cmd { + commit::Subcommands::Describe { + annotated_tags, + all_refs, + first_parent, + always, + long, + statistics, + max_candidates, + rev_spec, + } => prepare_and_run( + "commit-describe", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| { + core::repository::commit::describe( + repository()?.into(), + rev_spec.as_deref(), + out, + err, + core::repository::commit::describe::Options { + all_tags: !annotated_tags, + all_refs, + long_format: long, + first_parent, + statistics, + max_candidates, + always, }, ) }, ), }, - Subcommands::CommitGraph(subcommands) => match subcommands { - commitgraph::Subcommands::Verify { path, statistics } => prepare_and_run( - "commitgraph-verify", + Subcommands::Tree(cmd) => match cmd { + tree::Subcommands::Entries { + treeish, + recursive, + extended, + } => prepare_and_run( + "tree-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::repository::tree::entries( + repository()?.into(), + treeish.as_deref(), + recursive, + extended, + format, + out, + ) + }, + ), + tree::Subcommands::Info { treeish, extended } => prepare_and_run( + "tree-info", verbose, progress, progress_keep_open, None, move |_progress, out, err| { - let output_statistics = if statistics { Some(format) } else { None }; - core::commitgraph::verify::graph_or_file( - path, - core::commitgraph::verify::Context { - err, - out, - output_statistics, + core::repository::tree::info(repository()?.into(), treeish.as_deref(), extended, format, out, err) + }, + ), + }, + Subcommands::Odb(cmd) => match cmd { + odb::Subcommands::Entries => prepare_and_run( + "odb-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| core::repository::odb::entries(repository()?.into(), format, out), + ), + odb::Subcommands::Info => prepare_and_run( + "odb-info", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| core::repository::odb::info(repository()?.into(), format, out, err), + ), + }, + Subcommands::Mailmap(cmd) => match cmd { + mailmap::Subcommands::Entries => prepare_and_run( + "mailmap-entries", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, err| core::repository::mailmap::entries(repository()?.into(), format, out, err), + ), + }, + Subcommands::Exclude(cmd) => match cmd { + exclude::Subcommands::Query { + patterns, + pathspecs, + show_ignore_patterns, + } => prepare_and_run( + "exclude-query", + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + use git::bstr::ByteSlice; + core::repository::exclude::query( + repository()?.into(), + if pathspecs.is_empty() { + Box::new( + stdin_or_bail()? + .byte_lines() + .filter_map(Result::ok) + .filter_map(|line| git::path::Spec::from_bytes(line.as_bstr())), + ) as Box> + } else { + Box::new(pathspecs.into_iter()) + }, + out, + core::repository::exclude::query::Options { + format, + show_ignore_patterns, + overrides: patterns, }, ) }, - ) - .map(|_| ()), + ), }, }?; Ok(()) diff --git a/src/plumbing/options.rs b/src/plumbing/options.rs index 7a0f73b3e46..489133e7cd9 100644 --- a/src/plumbing/options.rs +++ b/src/plumbing/options.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use gitoxide_core as core; #[derive(Debug, clap::Parser)] @@ -5,6 +7,10 @@ use gitoxide_core as core; #[clap(subcommand_required = true)] #[clap(arg_required_else_help = true)] pub struct Args { + /// The repository to access. + #[clap(short = 'r', long, default_value = ".")] + pub repository: PathBuf, + #[clap(long, short = 't')] /// The amount of threads to use for some operations. /// @@ -46,221 +52,218 @@ pub struct Args { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Subcommands for interacting with packs and their indices. + /// Interact with the object database. #[clap(subcommand)] - Pack(pack::Subcommands), - /// Subcommands for interacting with git remotes, e.g. git repositories hosted on servers. + Odb(odb::Subcommands), + /// Interact with tree objects. #[clap(subcommand)] - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Remote(remote::Subcommands), - /// Subcommands for interacting with commit-graphs + Tree(tree::Subcommands), + /// Interact with commit objects. + #[clap(subcommand)] + Commit(commit::Subcommands), + /// Verify the integrity of the entire repository + Verify { + #[clap(flatten)] + args: free::pack::VerifyOptions, + }, + /// Query and obtain information about revisions. + #[clap(subcommand)] + Revision(revision::Subcommands), + /// Interact with the mailmap. #[clap(subcommand)] - CommitGraph(commitgraph::Subcommands), - /// Subcommands for interacting with a worktree index, typically at .git/index - Index(index::Platform), - /// Subcommands for interacting with entire git repositories - Repository(repo::Platform), - /// Subcommands for interacting with mailmaps - Mailmap(mailmap::Platform), + Mailmap(mailmap::Subcommands), + /// Interact with the exclude files like .gitignore. + #[clap(subcommand)] + Exclude(exclude::Subcommands), + Config(config::Platform), + /// Subcommands that need no git repository to run. + #[clap(subcommand)] + Free(free::Subcommands), } -/// -pub mod pack { - use std::{ffi::OsString, path::PathBuf}; +pub mod config { + /// Print all entries in a configuration file or access other sub-commands + #[derive(Debug, clap::Parser)] + #[clap(subcommand_required(false))] + pub struct Platform { + /// The filter terms to limit the output to matching sections and subsections only. + /// + /// Typical filters are `branch` or `remote.origin` or `remote.or*` - git-style globs are supported + /// and comparisons are case-insensitive. + pub filter: Vec, + } +} + +pub mod mailmap { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print all entries in configured mailmaps, inform about errors as well. + Entries, + } +} - use gitoxide_core as core; +pub mod odb { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Print all object names. + Entries, + /// Provide general information about the object database. + Info, + } +} +pub mod tree { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Subcommands for interacting with pack indices (.idx) - #[clap(subcommand)] - Index(index::Subcommands), - /// Subcommands for interacting with multi-pack indices (named "multi-pack-index") - MultiIndex(multi_index::Platform), - /// Create a new pack with a set of objects. - Create { + /// Print entries in a given tree + Entries { + /// Traverse the entire tree and its subtrees respectively, not only this tree. #[clap(long, short = 'r')] - /// the directory containing the '.git' repository from which objects should be read. - repository: Option, - - #[clap(long, short = 'e', possible_values(core::pack::create::ObjectExpansion::variants()))] - /// the way objects are expanded. They differ in costs. - /// - /// Possible values are "none" and "tree-traversal". Default is "none". - expansion: Option, + recursive: bool, - #[clap(long, default_value_t = 3, requires = "nondeterministic-count")] - /// The amount of threads to use when counting and the `--nondeterminisitc-count` flag is set, defaulting - /// to the globally configured threads. - /// - /// Use it to have different trade-offs between counting performance and cost in terms of CPU, as the scaling - /// here is everything but linear. The effectiveness of each core seems to be no more than 30%. - counting_threads: usize, + /// Provide files size as well. This is expensive as the object is decoded entirely. + #[clap(long, short = 'e')] + extended: bool, - #[clap(long)] - /// if set, the counting phase may be accelerated using multithreading. - /// - /// On the flip side, however, one will loose deterministic counting results which affects the - /// way the resulting pack is structured. - nondeterministic_count: bool, + /// The tree to traverse, or the tree at `HEAD` if unspecified. + treeish: Option, + }, + /// Provide information about a tree. + Info { + /// Provide files size as well. This is expensive as the object is decoded entirely. + #[clap(long, short = 'e')] + extended: bool, + /// The tree to traverse, or the tree at `HEAD` if unspecified. + treeish: Option, + }, + } +} - #[clap(long, short = 's')] - /// If set statistical information will be presented to inform about pack creation details. - /// It's a form of instrumentation for developers to help improve pack generation. - statistics: bool, +pub mod commit { + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Describe the current commit or the given one using the name of the closest annotated tag in its ancestry. + Describe { + /// Use annotated tag references only, not all tags. + #[clap(long, short = 't', conflicts_with("all-refs"))] + annotated_tags: bool, - #[clap(long)] - /// The size in megabytes for a cache to speed up pack access for packs with long delta chains. - /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. - /// - /// If unset, no cache will be used. - pack_cache_size_mb: Option, + /// Use all references under the `ref/` namespaces, which includes tag references, local and remote branches. + #[clap(long, short = 'a', conflicts_with("annotated-tags"))] + all_refs: bool, - #[clap(long)] - /// The size in megabytes for a cache to speed up accessing entire objects, bypassing object database access when hit. - /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. - /// - /// This cache type is currently only effective when using the 'diff-tree' object expansion. - /// - /// If unset, no cache will be used. - object_cache_size_mb: Option, + /// Only follow the first parent when traversing the commit graph. + #[clap(long, short = 'f')] + first_parent: bool, - #[clap(long)] - /// if set, delta-objects whose base object wouldn't be in the pack will not be recompressed as base object, but instead - /// refer to its base object using its object id. - /// - /// This allows for smaller packs but requires the receiver of the pack to resolve these ids before storing the pack. - /// Packs produced with this option enabled are only valid in transit, but not at rest. - thin: bool, + /// Always display the long format, even if that would not be necessary as the id is located directly on a reference. + #[clap(long, short = 'l')] + long: bool, - /// The directory into which to write the pack file. - #[clap(long, short = 'o')] - output_directory: Option, + /// Consider only the given `n` candidates. This can take longer, but potentially produces more accurate results. + #[clap(long, short = 'c', default_value = "10")] + max_candidates: usize, - /// The tips from which to start the commit graph iteration, either as fully qualified commit hashes - /// or as branch names. - /// - /// If empty, we expect to read objects on stdin and default to 'none' as expansion mode. - /// Otherwise the expansion mode is 'tree-traversal' by default. - tips: Vec, - }, - /// Use the git-protocol to receive a pack, emulating a clone. - #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] - Receive { - /// The protocol version to use. Valid values are 1 and 2 - #[clap(long, short = 'p')] - protocol: Option, + /// Print information on stderr to inform about performance statistics + #[clap(long, short = 's')] + statistics: bool, - /// the directory into which to write references. Existing files will be overwritten. - /// - /// Note that the directory will be created if needed. - #[clap(long, short = 'd')] - refs_directory: Option, + #[clap(long)] + /// If there was no way to describe the commit, fallback to using the abbreviated input revision. + always: bool, - /// The URLs or path from which to receive the pack. - /// - /// See here for a list of supported URLs: - url: String, + /// A specification of the revision to use, or the current `HEAD` if unset. + rev_spec: Option, + }, + } +} - /// If set once or more times, these references will be fetched instead of all advertised ones. - /// - /// Note that this requires a reasonably modern git server. - #[clap(long = "reference", short = 'r')] - refs: Vec, +pub mod revision { + #[derive(Debug, clap::Subcommand)] + #[clap(visible_alias = "rev")] + pub enum Subcommands { + /// Provide the revision specification like `@~1` to explain. + Explain { spec: std::ffi::OsString }, + } +} - /// The directory into which to write the received pack and index. - /// - /// If unset, they will be discarded. - directory: Option, +/// +pub mod free { + #[derive(Debug, clap::Subcommand)] + #[clap(visible_alias = "no-repo")] + pub enum Subcommands { + /// Subcommands for interacting with git remote server. + #[clap(subcommand)] + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + Remote(remote::Subcommands), + /// Subcommands for interacting with commit-graphs + #[clap(subcommand)] + CommitGraph(commitgraph::Subcommands), + /// Subcommands for interacting with mailmaps + Mailmap { + #[clap(flatten)] + cmd: mailmap::Platform, }, - /// Dissolve a pack into its loose objects. - /// - /// Note that this effectively removes delta compression for an average compression of 2x, creating one file per object in the process. - /// Thus this should only be done to dissolve small packs after a fetch. - Explode { - #[clap(long)] - /// Read written objects back and assert they match their source. Fail the operation otherwise. - /// - /// Only relevant if an object directory is set. - verify: bool, - - /// delete the pack and index file after the operation is successful - #[clap(long)] - delete_pack: bool, + /// Subcommands for interacting with pack files and indices + #[clap(subcommand)] + Pack(pack::Subcommands), + /// Subcommands for interacting with a worktree index, typically at .git/index + Index(index::Platform), + } - /// The amount of checks to run - #[clap( - long, - short = 'c', - default_value = "all", - possible_values(core::pack::explode::SafetyCheck::variants()) - )] - check: core::pack::explode::SafetyCheck, + /// + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + pub mod remote { + use gitoxide_core as core; - /// Compress bytes even when using the sink, i.e. no object directory is specified + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// List remote references from a remote identified by a url. /// - /// This helps to determine overhead related to compression. If unset, the sink will - /// only create hashes from bytes, which is usually limited by the speed at which input - /// can be obtained. - #[clap(long)] - sink_compress: bool, - - /// The '.pack' or '.idx' file to explode into loose objects - pack_path: PathBuf, - - /// The path into which all objects should be written. Commonly '.git/objects' - object_path: Option, - }, - /// Verify the integrity of a pack, index or multi-index file - Verify { - #[clap(flatten)] - args: VerifyOptions, + /// This is the plumbing equivalent of `git ls-remote`. + /// Supported URLs are documented here: + RefList { + /// The protocol version to use. Valid values are 1 and 2 + #[clap(long, short = 'p')] + protocol: Option, - /// The '.pack', '.idx' or 'multi-pack-index' file to validate. - path: PathBuf, - }, + /// the URLs or path from which to receive references + /// + /// See here for a list of supported URLs: + url: String, + }, + } } - #[derive(Debug, clap::Parser)] - pub struct VerifyOptions { - /// output statistical information - #[clap(long, short = 's')] - pub statistics: bool, - /// The algorithm used to verify packs. They differ in costs. - #[clap( - long, - short = 'a', - default_value = "less-time", - possible_values(core::pack::verify::Algorithm::variants()) - )] - pub algorithm: core::pack::verify::Algorithm, - - #[clap(long, conflicts_with("re-encode"))] - /// Decode and parse tags, commits and trees to validate their correctness beyond hashing correctly. - /// - /// Malformed objects should not usually occur, but could be injected on purpose or accident. - /// This will reduce overall performance. - pub decode: bool, + /// + pub mod commitgraph { + use std::path::PathBuf; - #[clap(long)] - /// Decode and parse tags, commits and trees to validate their correctness, and re-encode them. - /// - /// This flag is primarily to test the implementation of encoding, and requires to decode the object first. - /// Encoding an object after decoding it should yield exactly the same bytes. - /// This will reduce overall performance even more, as re-encoding requires to transform zero-copy objects into - /// owned objects, causing plenty of allocation to occour. - pub re_encode: bool, + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Verify the integrity of a commit graph + Verify { + /// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate. + path: PathBuf, + /// output statistical information about the pack + #[clap(long, short = 's')] + statistics: bool, + }, + } } - /// - pub mod multi_index { + pub mod index { use std::path::PathBuf; #[derive(Debug, clap::Parser)] pub struct Platform { + /// The object format to assume when reading files that don't inherently know about it, or when writing files. + #[clap(long, default_value_t = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] + pub object_hash: git_repository::hash::Kind, + /// The path to the index file. - #[clap(short = 'i', long, default_value = ".git/objects/pack/multi-pack-index")] - pub multi_index_path: PathBuf, + #[clap(short = 'i', long, default_value = ".git/index")] + pub index_path: PathBuf, /// Subcommands #[clap(subcommand)] @@ -269,348 +272,343 @@ pub mod pack { #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// Display all entries of a multi-index: - Entries, - /// Print general information about a multi-index file - Info, - /// Verify a multi-index quickly without inspecting objects themselves + /// Validate constraints and assumptions of an index along with its integrity. Verify, - /// Create a multi-pack index from one or more pack index files, overwriting possibloy existing files. - Create { - /// Paths to the pack index files to read (with .idx extension). - /// - /// Note for the multi-index to be useful, it should be side-by-side with the supplied `.idx` files. - #[clap(required = true)] - index_paths: Vec, + /// Print all entries to standard output + Entries, + /// Print information about the index structure + Info { + /// Do not extract specific extension information to gain only a superficial idea of the index's composition. + #[clap(long)] + no_details: bool, + }, + /// Checkout the index into a directory with exclusive write access, similar to what would happen during clone. + CheckoutExclusive { + /// The path to `.git` repository from which objects can be obtained to write the actual files referenced + /// in the index. Use this measure the impact on extracting objects on overall performance. + #[clap(long, short = 'r')] + repository: Option, + /// Ignore errors and keep checking out as many files as possible, and report all errors at the end of the operation. + #[clap(long, short = 'k')] + keep_going: bool, + /// Enable to query the object database yet write only empty files. This is useful to measure the overhead of ODB query + /// compared to writing the bytes to disk. + #[clap(long, short = 'e', requires = "repository")] + empty_files: bool, + /// The directory into which to write all index entries. + directory: PathBuf, }, } } /// - pub mod index { - use std::path::PathBuf; + pub mod pack { + use std::{ffi::OsString, path::PathBuf}; use gitoxide_core as core; #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// create a pack index from a pack data file. + /// Subcommands for interacting with pack indices (.idx) + #[clap(subcommand)] + Index(index::Subcommands), + /// Subcommands for interacting with multi-pack indices (named "multi-pack-index") + MultiIndex(multi_index::Platform), + /// Create a new pack with a set of objects. Create { - /// Specify how to iterate the pack, defaults to 'verify' - /// - /// Valid values are - /// - /// **as-is** do not do anything and expect the pack file to be valid as per the trailing hash, - /// **verify** the input ourselves and validate that it matches with the hash provided in the pack, - /// **restore** hash the input ourselves and ignore failing entries, instead finish the pack with the hash we computed - /// to keep as many objects as possible. - #[clap( - long, - short = 'i', - default_value = "verify", - possible_values(core::pack::index::IterationMode::variants()) - )] - iteration_mode: core::pack::index::IterationMode, + #[clap(long, short = 'r')] + /// the directory containing the '.git' repository from which objects should be read. + repository: Option, - /// Path to the pack file to read (with .pack extension). + #[clap(long, short = 'e', possible_values(core::pack::create::ObjectExpansion::variants()))] + /// the way objects are expanded. They differ in costs. /// - /// If unset, the pack file is expected on stdin. - #[clap(long, short = 'p')] - pack_path: Option, + /// Possible values are "none" and "tree-traversal". Default is "none". + expansion: Option, - /// The folder into which to place the pack and the generated index file + #[clap(long, default_value_t = 3, requires = "nondeterministic-count")] + /// The amount of threads to use when counting and the `--nondeterminisitc-count` flag is set, defaulting + /// to the globally configured threads. /// - /// If unset, only informational output will be provided to standard output. - directory: Option, - }, - } - } -} + /// Use it to have different trade-offs between counting performance and cost in terms of CPU, as the scaling + /// here is everything but linear. The effectiveness of each core seems to be no more than 30%. + counting_threads: usize, -/// -pub mod repo { - use std::path::PathBuf; - - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The repository to access. - #[clap(short = 'r', long, default_value = ".")] - pub repository: PathBuf, - - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } - - #[derive(Debug, clap::Subcommand)] - #[clap(visible_alias = "repo")] - pub enum Subcommands { - /// Verify the integrity of the entire repository - Verify { - #[clap(flatten)] - args: super::pack::VerifyOptions, - }, - /// Interact with commit objects. - Commit { - #[clap(subcommand)] - cmd: commit::Subcommands, - }, - /// Interact with tree objects. - Tree { - #[clap(subcommand)] - cmd: tree::Subcommands, - }, - /// Interact with the object database. - Odb { - #[clap(subcommand)] - cmd: odb::Subcommands, - }, - /// Interact with the mailmap. - Mailmap { - #[clap(subcommand)] - cmd: mailmap::Subcommands, - }, - /// Interact with the exclude files like .gitignore. - Exclude { - #[clap(subcommand)] - cmd: exclude::Subcommands, - }, - /// Query and obtain information about revisions. - Revision { - #[clap(subcommand)] - cmd: revision::Subcommands, - }, - } - - pub mod revision { - #[derive(Debug, clap::Subcommand)] - #[clap(visible_alias = "rev")] - pub enum Subcommands { - /// Provide the revision specification like `@~1` to explain. - Explain { spec: std::ffi::OsString }, - } - } + #[clap(long)] + /// if set, the counting phase may be accelerated using multithreading. + /// + /// On the flip side, however, one will loose deterministic counting results which affects the + /// way the resulting pack is structured. + nondeterministic_count: bool, - pub mod exclude { - use std::ffi::OsString; + #[clap(long, short = 's')] + /// If set statistical information will be presented to inform about pack creation details. + /// It's a form of instrumentation for developers to help improve pack generation. + statistics: bool, - use git_repository as git; + #[clap(long)] + /// The size in megabytes for a cache to speed up pack access for packs with long delta chains. + /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. + /// + /// If unset, no cache will be used. + pack_cache_size_mb: Option, - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Check if path-specs are excluded and print the result similar to `git check-ignore`. - Query { - /// Show actual ignore patterns instead of un-excluding an entry. + #[clap(long)] + /// The size in megabytes for a cache to speed up accessing entire objects, bypassing object database access when hit. + /// It is shared among all threads, so 4 threads would use their own cache 1/4th of the size. /// - /// That way one can understand why an entry might not be excluded. - #[clap(long, short = 'i')] - show_ignore_patterns: bool, - /// Additional patterns to use for exclusions. They have the highest priority. + /// This cache type is currently only effective when using the 'diff-tree' object expansion. /// - /// Useful for undoing previous patterns using the '!' prefix. - #[clap(long, short = 'p')] - patterns: Vec, - /// The git path specifications to check for exclusion, or unset to read from stdin one per line. - #[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))] - pathspecs: Vec, - }, - } - } - - pub mod mailmap { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print all entries in configured mailmaps, inform about errors as well. - Entries, - } - } + /// If unset, no cache will be used. + object_cache_size_mb: Option, - pub mod odb { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print all object names. - Entries, - /// Provide general information about the object database. - Info, - } - } + #[clap(long)] + /// if set, delta-objects whose base object wouldn't be in the pack will not be recompressed as base object, but instead + /// refer to its base object using its object id. + /// + /// This allows for smaller packs but requires the receiver of the pack to resolve these ids before storing the pack. + /// Packs produced with this option enabled are only valid in transit, but not at rest. + thin: bool, - pub mod commit { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Describe the current commit or the given one using the name of the closest annotated tag in its ancestry. - Describe { - /// Use annotated tag references only, not all tags. - #[clap(long, short = 't', conflicts_with("all-refs"))] - annotated_tags: bool, + /// The directory into which to write the pack file. + #[clap(long, short = 'o')] + output_directory: Option, - /// Use all references under the `ref/` namespaces, which includes tag references, local and remote branches. - #[clap(long, short = 'a', conflicts_with("annotated-tags"))] - all_refs: bool, + /// The tips from which to start the commit graph iteration, either as fully qualified commit hashes + /// or as branch names. + /// + /// If empty, we expect to read objects on stdin and default to 'none' as expansion mode. + /// Otherwise the expansion mode is 'tree-traversal' by default. + tips: Vec, + }, + /// Use the git-protocol to receive a pack, emulating a clone. + #[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] + Receive { + /// The protocol version to use. Valid values are 1 and 2 + #[clap(long, short = 'p')] + protocol: Option, - /// Only follow the first parent when traversing the commit graph. - #[clap(long, short = 'f')] - first_parent: bool, + /// the directory into which to write references. Existing files will be overwritten. + /// + /// Note that the directory will be created if needed. + #[clap(long, short = 'd')] + refs_directory: Option, - /// Always display the long format, even if that would not be necessary as the id is located directly on a reference. - #[clap(long, short = 'l')] - long: bool, + /// The URLs or path from which to receive the pack. + /// + /// See here for a list of supported URLs: + url: String, - /// Consider only the given `n` candidates. This can take longer, but potentially produces more accurate results. - #[clap(long, short = 'c', default_value = "10")] - max_candidates: usize, + /// If set once or more times, these references will be fetched instead of all advertised ones. + /// + /// Note that this requires a reasonably modern git server. + #[clap(long = "reference", short = 'r')] + refs: Vec, - /// Print information on stderr to inform about performance statistics - #[clap(long, short = 's')] - statistics: bool, + /// The directory into which to write the received pack and index. + /// + /// If unset, they will be discarded. + directory: Option, + }, + /// Dissolve a pack into its loose objects. + /// + /// Note that this effectively removes delta compression for an average compression of 2x, creating one file per object in the process. + /// Thus this should only be done to dissolve small packs after a fetch. + Explode { + #[clap(long)] + /// Read written objects back and assert they match their source. Fail the operation otherwise. + /// + /// Only relevant if an object directory is set. + verify: bool, + /// delete the pack and index file after the operation is successful #[clap(long)] - /// If there was no way to describe the commit, fallback to using the abbreviated input revision. - always: bool, + delete_pack: bool, - /// A specification of the revision to use, or the current `HEAD` if unset. - rev_spec: Option, - }, - } - } + /// The amount of checks to run + #[clap( + long, + short = 'c', + default_value = "all", + possible_values(core::pack::explode::SafetyCheck::variants()) + )] + check: core::pack::explode::SafetyCheck, - pub mod tree { - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Print entries in a given tree - Entries { - /// Traverse the entire tree and its subtrees respectively, not only this tree. - #[clap(long, short = 'r')] - recursive: bool, + /// Compress bytes even when using the sink, i.e. no object directory is specified + /// + /// This helps to determine overhead related to compression. If unset, the sink will + /// only create hashes from bytes, which is usually limited by the speed at which input + /// can be obtained. + #[clap(long)] + sink_compress: bool, - /// Provide files size as well. This is expensive as the object is decoded entirely. - #[clap(long, short = 'e')] - extended: bool, + /// The '.pack' or '.idx' file to explode into loose objects + pack_path: PathBuf, - /// The tree to traverse, or the tree at `HEAD` if unspecified. - treeish: Option, + /// The path into which all objects should be written. Commonly '.git/objects' + object_path: Option, }, - /// Provide information about a tree. - Info { - /// Provide files size as well. This is expensive as the object is decoded entirely. - #[clap(long, short = 'e')] - extended: bool, - /// The tree to traverse, or the tree at `HEAD` if unspecified. - treeish: Option, + /// Verify the integrity of a pack, index or multi-index file + Verify { + #[clap(flatten)] + args: VerifyOptions, + + /// The '.pack', '.idx' or 'multi-pack-index' file to validate. + path: PathBuf, }, } - } -} -/// -pub mod index { - use std::path::PathBuf; + #[derive(Debug, clap::Parser)] + pub struct VerifyOptions { + /// output statistical information + #[clap(long, short = 's')] + pub statistics: bool, + /// The algorithm used to verify packs. They differ in costs. + #[clap( + long, + short = 'a', + default_value = "less-time", + possible_values(core::pack::verify::Algorithm::variants()) + )] + pub algorithm: core::pack::verify::Algorithm, - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The object format to assume when reading files that don't inherently know about it, or when writing files. - #[clap(long, default_value_t = git_repository::hash::Kind::default(), possible_values(&["SHA1"]))] - pub object_hash: git_repository::hash::Kind, + #[clap(long, conflicts_with("re-encode"))] + /// Decode and parse tags, commits and trees to validate their correctness beyond hashing correctly. + /// + /// Malformed objects should not usually occur, but could be injected on purpose or accident. + /// This will reduce overall performance. + pub decode: bool, - /// The path to the index file. - #[clap(short = 'i', long, default_value = ".git/index")] - pub index_path: PathBuf, + #[clap(long)] + /// Decode and parse tags, commits and trees to validate their correctness, and re-encode them. + /// + /// This flag is primarily to test the implementation of encoding, and requires to decode the object first. + /// Encoding an object after decoding it should yield exactly the same bytes. + /// This will reduce overall performance even more, as re-encoding requires to transform zero-copy objects into + /// owned objects, causing plenty of allocation to occur. + pub re_encode: bool, + } - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } + /// + pub mod multi_index { + use std::path::PathBuf; + + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The path to the index file. + #[clap(short = 'i', long, default_value = ".git/objects/pack/multi-pack-index")] + pub multi_index_path: PathBuf, + + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Display all entries of a multi-index: + Entries, + /// Print general information about a multi-index file + Info, + /// Verify a multi-index quickly without inspecting objects themselves + Verify, + /// Create a multi-pack index from one or more pack index files, overwriting possibloy existing files. + Create { + /// Paths to the pack index files to read (with .idx extension). + /// + /// Note for the multi-index to be useful, it should be side-by-side with the supplied `.idx` files. + #[clap(required = true)] + index_paths: Vec, + }, + } + } - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Validate constraints and assumptions of an index along with its integrity. - Verify, - /// Print all entries to standard output - Entries, - /// Print information about the index structure - Info { - /// Do not extract specific extension information to gain only a superficial idea of the index's composition. - #[clap(long)] - no_details: bool, - }, - /// Checkout the index into a directory with exclusive write access, similar to what would happen during clone. - CheckoutExclusive { - /// The path to `.git` repository from which objects can be obtained to write the actual files referenced - /// in the index. Use this measure the impact on extracting objects on overall performance. - #[clap(long, short = 'r')] - repository: Option, - /// Ignore errors and keep checking out as many files as possible, and report all errors at the end of the operation. - #[clap(long, short = 'k')] - keep_going: bool, - /// Enable to query the object database yet write only empty files. This is useful to measure the overhead of ODB query - /// compared to writing the bytes to disk. - #[clap(long, short = 'e', requires = "repository")] - empty_files: bool, - /// The directory into which to write all index entries. - directory: PathBuf, - }, + /// + pub mod index { + use std::path::PathBuf; + + use gitoxide_core as core; + + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// create a pack index from a pack data file. + Create { + /// Specify how to iterate the pack, defaults to 'verify' + /// + /// Valid values are + /// + /// **as-is** do not do anything and expect the pack file to be valid as per the trailing hash, + /// **verify** the input ourselves and validate that it matches with the hash provided in the pack, + /// **restore** hash the input ourselves and ignore failing entries, instead finish the pack with the hash we computed + /// to keep as many objects as possible. + #[clap( + long, + short = 'i', + default_value = "verify", + possible_values(core::pack::index::IterationMode::variants()) + )] + iteration_mode: core::pack::index::IterationMode, + + /// Path to the pack file to read (with .pack extension). + /// + /// If unset, the pack file is expected on stdin. + #[clap(long, short = 'p')] + pack_path: Option, + + /// The folder into which to place the pack and the generated index file + /// + /// If unset, only informational output will be provided to standard output. + directory: Option, + }, + } + } } -} -/// -pub mod mailmap { - use std::path::PathBuf; + /// + pub mod mailmap { + use std::path::PathBuf; - #[derive(Debug, clap::Parser)] - pub struct Platform { - /// The path to the mailmap file. - #[clap(short = 'p', long, default_value = ".mailmap")] - pub path: PathBuf, + #[derive(Debug, clap::Parser)] + pub struct Platform { + /// The path to the mailmap file. + #[clap(short = 'p', long, default_value = ".mailmap")] + pub path: PathBuf, - /// Subcommands - #[clap(subcommand)] - pub cmd: Subcommands, - } + /// Subcommands + #[clap(subcommand)] + pub cmd: Subcommands, + } - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Parse all entries in the mailmap and report malformed lines or collisions. - Verify, + #[derive(Debug, clap::Subcommand)] + pub enum Subcommands { + /// Parse all entries in the mailmap and report malformed lines or collisions. + Verify, + } } } -/// -pub mod commitgraph { - use std::path::PathBuf; +pub mod exclude { + use std::ffi::OsString; - #[derive(Debug, clap::Subcommand)] - pub enum Subcommands { - /// Verify the integrity of a commit graph - Verify { - /// The path to '.git/objects/info/', '.git/objects/info/commit-graphs/', or '.git/objects/info/commit-graph' to validate. - path: PathBuf, - /// output statistical information about the pack - #[clap(long, short = 's')] - statistics: bool, - }, - } -} - -/// -#[cfg(any(feature = "gitoxide-core-async-client", feature = "gitoxide-core-blocking-client"))] -pub mod remote { - use gitoxide_core as core; + use git_repository as git; #[derive(Debug, clap::Subcommand)] pub enum Subcommands { - /// List remote references from a remote identified by a url. - /// - /// This is the plumbing equivalent of `git ls-remote`. - /// Supported URLs are documented here: - RefList { - /// The protocol version to use. Valid values are 1 and 2 - #[clap(long, short = 'p')] - protocol: Option, - - /// the URLs or path from which to receive references + /// Check if path-specs are excluded and print the result similar to `git check-ignore`. + Query { + /// Show actual ignore patterns instead of un-excluding an entry. /// - /// See here for a list of supported URLs: - url: String, + /// That way one can understand why an entry might not be excluded. + #[clap(long, short = 'i')] + show_ignore_patterns: bool, + /// Additional patterns to use for exclusions. They have the highest priority. + /// + /// Useful for undoing previous patterns using the '!' prefix. + #[clap(long, short = 'p')] + patterns: Vec, + /// The git path specifications to check for exclusion, or unset to read from stdin one per line. + #[clap(parse(try_from_os_str = std::convert::TryFrom::try_from))] + pathspecs: Vec, }, } } diff --git a/tests/journey/gix.sh b/tests/journey/gix.sh index ddc4475098a..4b1b902e149 100644 --- a/tests/journey/gix.sh +++ b/tests/journey/gix.sh @@ -46,8 +46,8 @@ title "git-tempfile crate" ) ) -title "gix repository" -(when "running 'repository'" +title "gix (with repository)" +(with "a git repository" snapshot="$snapshot/repository" (small-repo-in-sandbox (with "the 'verify' sub-command" @@ -55,14 +55,14 @@ title "gix repository" (with 'human output format' it "generates correct output" && { WITH_SNAPSHOT="$snapshot/success-format-human" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format human repo verify -s + expect_run $SUCCESSFULLY "$exe_plumbing" --format human verify -s } ) if test "$kind" = "max"; then (with "--format json" it "generates the correct output in JSON format" && { WITH_SNAPSHOT="$snapshot/success-format-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json repository verify --statistics + expect_run $SUCCESSFULLY "$exe_plumbing" --format json verify --statistics } ) fi @@ -70,570 +70,573 @@ title "gix repository" ) ) -title "gix pack" -(when "running 'pack'" - snapshot="$snapshot/pack" +(with "gix free" + snapshot="$snapshot/no-repo" + title "gix free pack" + (when "running 'pack'" + snapshot="$snapshot/pack" - title "gix pack receive" - (with "the 'receive' sub-command" - snapshot="$snapshot/receive" - (small-repo-in-sandbox - if [[ "$kind" != 'small' ]]; then + title "gix free pack receive" + (with "the 'receive' sub-command" + snapshot="$snapshot/receive" + (small-repo-in-sandbox + if [[ "$kind" != 'small' ]]; then - if [[ "$kind" != 'async' ]]; then - (with "file:// protocol" - (with "version 1" - (with "NO output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 .git - } - ) - (with "output directory" - mkdir out - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 .git out/ - } - it "creates an index and a pack in the output directory" && { - WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ - expect_run $SUCCESSFULLY ls out/ - } - (with "--write-refs set" + if [[ "$kind" != 'async' ]]; then + (with "file:// protocol" + (with "version 1" + (with "NO output directory" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 .git + } + ) + (with "output directory" + mkdir out it "generates the correct output" && { WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 --refs-directory out/all-refs .git out/ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 .git out/ } - it "writes references into the refs folder of the output directory" && { - expect_snapshot "$snapshot/repo-refs" out/all-refs + it "creates an index and a pack in the output directory" && { + WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ + expect_run $SUCCESSFULLY ls out/ } + (with "--write-refs set" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 --refs-directory out/all-refs .git out/ + } + it "writes references into the refs folder of the output directory" && { + expect_snapshot "$snapshot/repo-refs" out/all-refs + } + ) + rm -Rf out ) - rm -Rf out - ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack receive --protocol 1 .git - } - ) - fi - ) - (with "version 2" - (with "NO output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 .git - } - ) - (with "output directory" - mkdir out/ - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive .git out/ - } - it "creates an index and a pack in the output directory" && { - WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ - expect_run $SUCCESSFULLY ls out/ - } - rm -Rf out - ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack receive --protocol 2 .git - } + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack receive --protocol 1 .git + } + ) + fi ) - fi - ) - ) - fi - (with "git:// protocol" - launch-git-daemon - (with "version 1" - (with "NO output directory" - (with "no wanted refs" + (with "version 2" + (with "NO output directory" it "generates the correct output" && { WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 git://localhost/ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 .git } ) - (with "wanted refs" + (with "output directory" + mkdir out/ it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-wanted-ref-p1" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack receive -p 1 git://localhost/ -r =refs/heads/main + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive .git out/ + } + it "creates an index and a pack in the output directory" && { + WITH_SNAPSHOT="$snapshot/ls-in-output-dir" \ + expect_run $SUCCESSFULLY ls out/ } + rm -Rf out ) - ) - (with "output directory" - mkdir out - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 git://localhost/ out/ - } + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack receive --protocol 2 .git + } + ) + fi ) ) - (with "version 2" - (with "NO output directory" - (with "NO wanted refs" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 git://localhost/ - } + fi + (with "git:// protocol" + launch-git-daemon + (with "version 1" + (with "NO output directory" + (with "no wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 git://localhost/ + } + ) + (with "wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-wanted-ref-p1" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack receive -p 1 git://localhost/ -r =refs/heads/main + } + ) ) - (with "wanted refs" + (with "output directory" + mkdir out it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-single-ref" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 git://localhost/ -r refs/heads/main + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 git://localhost/ out/ } - (when "ref does not exist" - it "fails with a detailed error message including what the server said" && { - WITH_SNAPSHOT="$snapshot/file-v-any-no-output-non-existing-single-ref" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack receive -p 2 git://localhost/ -r refs/heads/does-not-exist + ) + ) + (with "version 2" + (with "NO output directory" + (with "NO wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 git://localhost/ + } + ) + (with "wanted refs" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-single-ref" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 git://localhost/ -r refs/heads/main } + (when "ref does not exist" + it "fails with a detailed error message including what the server said" && { + WITH_SNAPSHOT="$snapshot/file-v-any-no-output-non-existing-single-ref" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack receive -p 2 git://localhost/ -r refs/heads/does-not-exist + } + ) ) ) - ) - (with "output directory" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive git://localhost/ out/ - } + (with "output directory" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any-with-output" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive git://localhost/ out/ + } + ) ) ) - ) - (on_ci - if test "$kind" = "max"; then - (with "https:// protocol" - (with "version 1" - it "works" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 1 https://github.com/byron/gitoxide - } - ) - (with "version 2" - it "works" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack receive -p 2 https://github.com/byron/gitoxide - } + (on_ci + if test "$kind" = "max"; then + (with "https:// protocol" + (with "version 1" + it "works" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 1 https://github.com/byron/gitoxide + } + ) + (with "version 2" + it "works" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack receive -p 2 https://github.com/byron/gitoxide + } + ) ) + fi ) + elif [[ "$kind" = "small" ]]; then + it "fails as the CLI doesn't have networking in 'small' mode" && { + WITH_SNAPSHOT="$snapshot/pack receive-no-networking-in-small-failure" \ + expect_run 2 "$exe_plumbing" free pack receive -p 1 .git + } fi ) - elif [[ "$kind" = "small" ]]; then - it "fails as the CLI doesn't have networking in 'small' mode" && { - WITH_SNAPSHOT="$snapshot/pack receive-no-networking-in-small-failure" \ - expect_run 2 "$exe_plumbing" pack receive -p 1 .git - } - fi ) - ) - (with "the 'index' sub-command" - snapshot="$snapshot/index" - title "gix pack index create" - (with "the 'create' sub-command" - snapshot="$snapshot/create" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" - (with "a valid and complete pack file" - (with "NO output directory specified" - (with "pack file passed as file" - it "generates an index into a sink and outputs pack and index information" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -p "$PACK_FILE" - } - ) - (with "pack file passed from stdin" - it "generates an index into a sink and outputs pack and index information" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create < "$PACK_FILE" - } - if test "$kind" = "max"; then - (with "--format json" - it "generates the index into a sink and outputs information as JSON" && { - WITH_SNAPSHOT="$snapshot/no-output-dir-as-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack index create < "$PACK_FILE" + (with "the 'index' sub-command" + snapshot="$snapshot/index" + title "gix free pack index create" + (with "the 'create' sub-command" + snapshot="$snapshot/create" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + (with "a valid and complete pack file" + (with "NO output directory specified" + (with "pack file passed as file" + it "generates an index into a sink and outputs pack and index information" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -p "$PACK_FILE" } ) - fi + (with "pack file passed from stdin" + it "generates an index into a sink and outputs pack and index information" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create < "$PACK_FILE" + } + if test "$kind" = "max"; then + (with "--format json" + it "generates the index into a sink and outputs information as JSON" && { + WITH_SNAPSHOT="$snapshot/no-output-dir-as-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack index create < "$PACK_FILE" + } + ) + fi + ) ) - ) - (sandbox - (with "with an output directory specified" - it "generates an index and outputs information" && { - WITH_SNAPSHOT="$snapshot/output-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -p "$PACK_FILE" "$PWD" - } - it "writes the index and pack into the directory (they have the same names, different suffixes)" && { - WITH_SNAPSHOT="$snapshot/output-dir-content" \ - expect_run $SUCCESSFULLY ls - } + (sandbox + (with "with an output directory specified" + it "generates an index and outputs information" && { + WITH_SNAPSHOT="$snapshot/output-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -p "$PACK_FILE" "$PWD" + } + it "writes the index and pack into the directory (they have the same names, different suffixes)" && { + WITH_SNAPSHOT="$snapshot/output-dir-content" \ + expect_run $SUCCESSFULLY ls + } + ) ) ) - ) - (with "'restore' iteration mode" - (sandbox - cp "${PACK_FILE}" . - PACK_FILE="${PACK_FILE##*/}" - "$jtt" mess-in-the-middle "${PACK_FILE}" - - it "generates an index and outputs information (instead of failing)" && { - WITH_SNAPSHOT="$snapshot/output-dir-restore-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack index create -i restore -p "$PACK_FILE" "$PWD" - } + (with "'restore' iteration mode" + (sandbox + cp "${PACK_FILE}" . + PACK_FILE="${PACK_FILE##*/}" + "$jtt" mess-in-the-middle "${PACK_FILE}" - if test "$kind" = "max"; then - (with "--format json and the very same output directory" - it "generates the index, overwriting existing files, and outputs information as JSON" && { - WITH_SNAPSHOT="$snapshot/output-dir-restore-as-json-success" \ - SNAPSHOT_FILTER=remove-paths \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json pack index create -i restore $PWD < "$PACK_FILE" + it "generates an index and outputs information (instead of failing)" && { + WITH_SNAPSHOT="$snapshot/output-dir-restore-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack index create -i restore -p "$PACK_FILE" "$PWD" } - ) - fi - ) - ) - ) - ) - title "gix pack multi-index" - (with "the 'multi-index' sub-command" - snapshot="$snapshot/multi-index" - title "gix pack multi-index create" - (with "the 'create' sub-command" - snapshot="$snapshot/create" - (with 'multiple pack indices' - (sandbox - it "creates a multi-index successfully" && { - expect_run $SUCCESSFULLY "$exe_plumbing" pack multi-index -i multi-pack-index create $fixtures/packs/pack-*.idx + if test "$kind" = "max"; then + (with "--format json and the very same output directory" + it "generates the index, overwriting existing files, and outputs information as JSON" && { + WITH_SNAPSHOT="$snapshot/output-dir-restore-as-json-success" \ + SNAPSHOT_FILTER=remove-paths \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free pack index create -i restore $PWD < "$PACK_FILE" } ) + fi ) + ) ) - ) - - title "gix pack explode" - (with "the 'explode' sub-command" - snapshot="$snapshot/explode" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2" - (with "no objects directory specified" - it "explodes the pack successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/to-sink-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode "${PACK_FILE}.idx" - } - - (when "using the --delete-pack flag" - (sandbox - (with "a valid pack" - cp "${PACK_FILE}".idx "${PACK_FILE}".pack . - PACK_FILE="${PACK_FILE##*/}" - it "explodes the pack successfully and deletes the original pack and index" && { - WITH_SNAPSHOT="$snapshot/to-sink-delete-pack-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode --check skip-file-checksum --delete-pack "${PACK_FILE}.pack" - } - it "removes the original files" && { - expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack - expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx - } - ) - (with "a pack file that is invalid somewhere" - cp ${PACK_FILE}.idx ${PACK_FILE}.pack . - PACK_FILE="${PACK_FILE##*/}" - "$jtt" mess-in-the-middle "${PACK_FILE}".pack - - (with "and all safety checks" - it "does not explode the file at all" && { - WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-failure" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack explode --sink-compress --check all --delete-pack "${PACK_FILE}.pack" - } + ) - it "did not touch index or pack file" && { - expect_exists "${PACK_FILE}".pack - expect_exists "${PACK_FILE}".idx - } + title "gix free pack multi-index" + (with "the 'multi-index' sub-command" + snapshot="$snapshot/multi-index" + title "gix free pack multi-index create" + (with "the 'create' sub-command" + snapshot="$snapshot/create" + (with 'multiple pack indices' + (sandbox + it "creates a multi-index successfully" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free pack multi-index -i multi-pack-index create $fixtures/packs/pack-*.idx + } + ) ) + ) + ) - (with "and no safety checks at all (and an output directory)" - it "does explode the file" && { - WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-skip-checks-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode --verify --check skip-file-and-object-checksum-and-no-abort-on-decode \ - --delete-pack "${PACK_FILE}.pack" . - } + title "gix free pack explode" + (with "the 'explode' sub-command" + snapshot="$snapshot/explode" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2" + (with "no objects directory specified" + it "explodes the pack successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/to-sink-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" no-repo pack explode "${PACK_FILE}.idx" + } + (when "using the --delete-pack flag" + (sandbox + (with "a valid pack" + cp "${PACK_FILE}".idx "${PACK_FILE}".pack . + PACK_FILE="${PACK_FILE##*/}" + it "explodes the pack successfully and deletes the original pack and index" && { + WITH_SNAPSHOT="$snapshot/to-sink-delete-pack-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode --check skip-file-checksum --delete-pack "${PACK_FILE}.pack" + } it "removes the original files" && { expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx } + ) + (with "a pack file that is invalid somewhere" + cp ${PACK_FILE}.idx ${PACK_FILE}.pack . + PACK_FILE="${PACK_FILE##*/}" + "$jtt" mess-in-the-middle "${PACK_FILE}".pack + + (with "and all safety checks" + it "does not explode the file at all" && { + WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-failure" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack explode --sink-compress --check all --delete-pack "${PACK_FILE}.pack" + } - (with_program tree + it "did not touch index or pack file" && { + expect_exists "${PACK_FILE}".pack + expect_exists "${PACK_FILE}".idx + } + ) + + (with "and no safety checks at all (and an output directory)" + it "does explode the file" && { + WITH_SNAPSHOT="$snapshot/broken-delete-pack-to-sink-skip-checks-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode --verify --check skip-file-and-object-checksum-and-no-abort-on-decode \ + --delete-pack "${PACK_FILE}.pack" . + } - if test "$kind" = "small"; then - suffix=miniz-oxide - else - suffix=zlib-ng - fi - it "creates all pack objects, but the broken ones" && { - WITH_SNAPSHOT="$snapshot/broken-with-objects-dir-skip-checks-success-tree-$suffix" \ - expect_run $SUCCESSFULLY tree + it "removes the original files" && { + expect_run $WITH_FAILURE test -e "${PACK_FILE}".pack + expect_run $WITH_FAILURE test -e "${PACK_FILE}".idx } + + (with_program tree + + if test "$kind" = "small"; then + suffix=miniz-oxide + else + suffix=zlib-ng + fi + it "creates all pack objects, but the broken ones" && { + WITH_SNAPSHOT="$snapshot/broken-with-objects-dir-skip-checks-success-tree-$suffix" \ + expect_run $SUCCESSFULLY tree + } + ) ) ) ) ) ) - ) - (with "a non-existing directory specified" - it "fails with a helpful error message" && { - WITH_SNAPSHOT="$snapshot/missing-objects-dir-fail" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack explode -c skip-file-and-object-checksum "${PACK_FILE}.idx" does-not-exist - } - ) - (with "an existing directory specified" - (sandbox - it "succeeds" && { - WITH_SNAPSHOT="$snapshot/with-objects-dir-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack explode -c skip-file-and-object-checksum-and-no-abort-on-decode \ - "${PACK_FILE}.pack" . + (with "a non-existing directory specified" + it "fails with a helpful error message" && { + WITH_SNAPSHOT="$snapshot/missing-objects-dir-fail" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack explode -c skip-file-and-object-checksum "${PACK_FILE}.idx" does-not-exist } - - (with_program tree - it "creates all pack objects" && { - WITH_SNAPSHOT="$snapshot/with-objects-dir-success-tree" \ - expect_run $SUCCESSFULLY tree + ) + (with "an existing directory specified" + (sandbox + it "succeeds" && { + WITH_SNAPSHOT="$snapshot/with-objects-dir-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack explode -c skip-file-and-object-checksum-and-no-abort-on-decode \ + "${PACK_FILE}.pack" . } + + (with_program tree + it "creates all pack objects" && { + WITH_SNAPSHOT="$snapshot/with-objects-dir-success-tree" \ + expect_run $SUCCESSFULLY tree + } + ) ) ) ) - ) - - title "gix pack verify" - (with "the 'verify' sub-command" - snapshot="$snapshot/verify" - (with "a valid pack file" - PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" - it "verifies the pack successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$PACK_FILE" - } - ) - (with "a valid pack INDEX file" - MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" - (with "no statistics" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$MULTI_PACK_INDEX" - } - ) - (with "statistics" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --statistics "$MULTI_PACK_INDEX" - } - (with "and the less-memory algorithm" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" - } - ) - ) - (with "decode" - it "verifies the pack index successfully and with desired output, and decodes all objects" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" - } - ) - (with "re-encode" - it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { - WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" - } - ) - if test "$kind" = "max"; then - (with "statistics (JSON)" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 pack verify --statistics "$MULTI_PACK_INDEX" + title "gix free pack verify" + (with "the 'verify' sub-command" + snapshot="$snapshot/verify" + (with "a valid pack file" + PACK_FILE="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.pack" + it "verifies the pack successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$PACK_FILE" } ) - fi - ) - (with "a valid multi-pack index" - snapshot="$snapshot/multi-index" - (sandbox - MULTI_PACK_INDEX=multi-pack-index - cp $fixtures/packs/pack-* . - $exe_plumbing pack multi-index -i $MULTI_PACK_INDEX create *.idx - - (when "using fast validation via 'pack multi-index verify'" - it "verifies the pack index successfully and with desired output" && { - WITH_SNAPSHOT="$snapshot/fast-index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack multi-index -i "$MULTI_PACK_INDEX" verify - } - ) - + (with "a valid pack INDEX file" + MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" (with "no statistics" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$MULTI_PACK_INDEX" } ) (with "statistics" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --statistics "$MULTI_PACK_INDEX" } (with "and the less-memory algorithm" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" } ) ) (with "decode" it "verifies the pack index successfully and with desired output, and decodes all objects" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" } ) (with "re-encode" it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { WITH_SNAPSHOT="$snapshot/index-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" } ) if test "$kind" = "max"; then (with "statistics (JSON)" it "verifies the pack index successfully and with desired output" && { WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 pack verify --statistics "$MULTI_PACK_INDEX" + expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 free pack verify --statistics "$MULTI_PACK_INDEX" } ) fi ) - ) - (sandbox - (with "an INvalid pack INDEX file" - MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" - cp $MULTI_PACK_INDEX index.idx - echo $'\0' >> index.idx - it "fails to verify the pack index and with desired output" && { - WITH_SNAPSHOT="$snapshot/index-failure" \ - expect_run $WITH_FAILURE "$exe_plumbing" pack verify index.idx - } - ) - ) - ) -) + (with "a valid multi-pack index" + snapshot="$snapshot/multi-index" + (sandbox + MULTI_PACK_INDEX=multi-pack-index + cp $fixtures/packs/pack-* . + $exe_plumbing free pack multi-index -i $MULTI_PACK_INDEX create *.idx -title "gix remote" -(when "running 'remote'" - snapshot="$snapshot/remote" - title "gix remote ref-list" - (with "the 'ref-list' subcommand" - snapshot="$snapshot/ref-list" - (small-repo-in-sandbox - if [[ "$kind" != "small" ]]; then - - if [[ "$kind" != "async" ]]; then - (with "file:// protocol" - (with "version 1" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 .git - } - ) - (with "version 2" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list --protocol 2 "$PWD/.git" - } + (when "using fast validation via 'pack multi-index verify'" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/fast-index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack multi-index -i "$MULTI_PACK_INDEX" verify + } + ) + + (with "no statistics" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify "$MULTI_PACK_INDEX" + } + ) + (with "statistics" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --statistics "$MULTI_PACK_INDEX" + } + + (with "and the less-memory algorithm" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --statistics "$MULTI_PACK_INDEX" + } + ) + ) + (with "decode" + it "verifies the pack index successfully and with desired output, and decodes all objects" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-memory --decode "$MULTI_PACK_INDEX" + } + ) + (with "re-encode" + it "verifies the pack index successfully and with desired output, and re-encodes all objects" && { + WITH_SNAPSHOT="$snapshot/index-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free pack verify --algorithm less-time --re-encode "$MULTI_PACK_INDEX" + } + ) + if test "$kind" = "max"; then + (with "statistics (JSON)" + it "verifies the pack index successfully and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-with-statistics-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json --threads 1 free pack verify --statistics "$MULTI_PACK_INDEX" + } + ) + fi ) - if test "$kind" = "max"; then - (with "--format json" - it "generates the correct output in JSON format" && { - WITH_SNAPSHOT="$snapshot/file-v-any-json" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json remote ref-list .git + ) + (sandbox + (with "an INvalid pack INDEX file" + MULTI_PACK_INDEX="$fixtures/packs/pack-11fdfa9e156ab73caae3b6da867192221f2089c2.idx" + cp $MULTI_PACK_INDEX index.idx + echo $'\0' >> index.idx + it "fails to verify the pack index and with desired output" && { + WITH_SNAPSHOT="$snapshot/index-failure" \ + expect_run $WITH_FAILURE "$exe_plumbing" free pack verify index.idx } ) - fi ) - fi + ) + ) - (with "git:// protocol" - launch-git-daemon - (with "version 1" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 git://localhost/ - } - ) - (with "version 2" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/file-v-any" \ - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 2 git://localhost/ - } + title "gix free remote" + (when "running 'remote'" + snapshot="$snapshot/remote" + title "gix remote ref-list" + (with "the 'ref-list' subcommand" + snapshot="$snapshot/ref-list" + (small-repo-in-sandbox + if [[ "$kind" != "small" ]]; then + + if [[ "$kind" != "async" ]]; then + (with "file:// protocol" + (with "version 1" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 .git + } + ) + (with "version 2" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list --protocol 2 "$PWD/.git" + } + ) + if test "$kind" = "max"; then + (with "--format json" + it "generates the correct output in JSON format" && { + WITH_SNAPSHOT="$snapshot/file-v-any-json" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free remote ref-list .git + } + ) + fi ) - ) - if [[ "$kind" == "small" ]]; then - (with "https:// protocol (in small builds)" - it "fails as http is not compiled in" && { - WITH_SNAPSHOT="$snapshot/fail-http-in-small" \ - expect_run $WITH_FAILURE "$exe_plumbing" remote ref-list -p 1 https://github.com/byron/gitoxide - } - ) - fi - (on_ci - if [[ "$kind" = "max" ]]; then - (with "https:// protocol" + fi + + (with "git:// protocol" + launch-git-daemon (with "version 1" it "generates the correct output" && { - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 1 https://github.com/byron/gitoxide + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 git://localhost/ } ) (with "version 2" it "generates the correct output" && { - expect_run $SUCCESSFULLY "$exe_plumbing" remote ref-list -p 2 https://github.com/byron/gitoxide + WITH_SNAPSHOT="$snapshot/file-v-any" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 2 git://localhost/ } ) ) + if [[ "$kind" == "small" ]]; then + (with "https:// protocol (in small builds)" + it "fails as http is not compiled in" && { + WITH_SNAPSHOT="$snapshot/fail-http-in-small" \ + expect_run $WITH_FAILURE "$exe_plumbing" free remote ref-list -p 1 https://github.com/byron/gitoxide + } + ) + fi + (on_ci + if [[ "$kind" = "max" ]]; then + (with "https:// protocol" + (with "version 1" + it "generates the correct output" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 1 https://github.com/byron/gitoxide + } + ) + (with "version 2" + it "generates the correct output" && { + expect_run $SUCCESSFULLY "$exe_plumbing" free remote ref-list -p 2 https://github.com/byron/gitoxide + } + ) + ) + fi + ) + else + it "fails as the CLI doesn't include networking in 'small' mode" && { + WITH_SNAPSHOT="$snapshot/remote ref-list-no-networking-in-small-failure" \ + expect_run 2 "$exe_plumbing" free remote ref-list -p 1 .git + } fi ) - else - it "fails as the CLI doesn't include networking in 'small' mode" && { - WITH_SNAPSHOT="$snapshot/remote ref-list-no-networking-in-small-failure" \ - expect_run 2 "$exe_plumbing" remote ref-list -p 1 .git - } - fi ) ) -) -title "gix commit-graph" -(when "running 'commit-graph'" - snapshot="$snapshot/commit-graph" - title "gix commit-graph verify" - (with "the 'verify' sub-command" - snapshot="$snapshot/verify" + title "gix free commit-graph" + (when "running 'commit-graph'" + snapshot="$snapshot/commit-graph" + title "gix free commit-graph verify" + (with "the 'verify' sub-command" + snapshot="$snapshot/verify" - (small-repo-in-sandbox - (with "a valid and complete commit-graph file" - git commit-graph write --reachable - (with "statistics" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/statistics-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" commit-graph verify -s .git/objects/info - } - ) - if test "$kind" = "max"; then - (with "statistics --format json" - it "generates the correct output" && { - WITH_SNAPSHOT="$snapshot/statistics-json-success" \ - expect_run $SUCCESSFULLY "$exe_plumbing" --format json commit-graph verify -s .git/objects/info - } + (small-repo-in-sandbox + (with "a valid and complete commit-graph file" + git commit-graph write --reachable + (with "statistics" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/statistics-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" free commit-graph verify -s .git/objects/info + } + ) + if test "$kind" = "max"; then + (with "statistics --format json" + it "generates the correct output" && { + WITH_SNAPSHOT="$snapshot/statistics-json-success" \ + expect_run $SUCCESSFULLY "$exe_plumbing" --format json free commit-graph verify -s .git/objects/info + } + ) + fi ) - fi ) ) ) diff --git a/tests/snapshots/plumbing/commit-graph/verify/statistics-json-success b/tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/commit-graph/verify/statistics-json-success rename to tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-json-success diff --git a/tests/snapshots/plumbing/commit-graph/verify/statistics-success b/tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-success similarity index 100% rename from tests/snapshots/plumbing/commit-graph/verify/statistics-success rename to tests/snapshots/plumbing/no-repo/commit-graph/verify/statistics-success diff --git a/tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-failure b/tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-failure similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-failure rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-failure diff --git a/tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-skip-checks-success b/tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-skip-checks-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-delete-pack-to-sink-skip-checks-success rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-delete-pack-to-sink-skip-checks-success diff --git a/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide b/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide diff --git a/tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng b/tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng similarity index 100% rename from tests/snapshots/plumbing/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng rename to tests/snapshots/plumbing/no-repo/pack/explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng diff --git a/tests/snapshots/plumbing/pack/explode/missing-objects-dir-fail b/tests/snapshots/plumbing/no-repo/pack/explode/missing-objects-dir-fail similarity index 100% rename from tests/snapshots/plumbing/pack/explode/missing-objects-dir-fail rename to tests/snapshots/plumbing/no-repo/pack/explode/missing-objects-dir-fail diff --git a/tests/snapshots/plumbing/pack/explode/to-sink-delete-pack-success b/tests/snapshots/plumbing/no-repo/pack/explode/to-sink-delete-pack-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/to-sink-delete-pack-success rename to tests/snapshots/plumbing/no-repo/pack/explode/to-sink-delete-pack-success diff --git a/tests/snapshots/plumbing/pack/explode/to-sink-success b/tests/snapshots/plumbing/no-repo/pack/explode/to-sink-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/to-sink-success rename to tests/snapshots/plumbing/no-repo/pack/explode/to-sink-success diff --git a/tests/snapshots/plumbing/pack/explode/with-objects-dir-success b/tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/explode/with-objects-dir-success rename to tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success diff --git a/tests/snapshots/plumbing/pack/explode/with-objects-dir-success-tree b/tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success-tree similarity index 100% rename from tests/snapshots/plumbing/pack/explode/with-objects-dir-success-tree rename to tests/snapshots/plumbing/no-repo/pack/explode/with-objects-dir-success-tree diff --git a/tests/snapshots/plumbing/pack/index/create/no-output-dir-as-json-success b/tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-as-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/no-output-dir-as-json-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-as-json-success diff --git a/tests/snapshots/plumbing/pack/index/create/no-output-dir-success b/tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/no-output-dir-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/no-output-dir-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-content b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-content similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-content rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-content diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-restore-as-json-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-as-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-restore-as-json-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-as-json-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-restore-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-restore-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-restore-success diff --git a/tests/snapshots/plumbing/pack/index/create/output-dir-success b/tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-success similarity index 100% rename from tests/snapshots/plumbing/pack/index/create/output-dir-success rename to tests/snapshots/plumbing/no-repo/pack/index/create/output-dir-success diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-json b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-json similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-json rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-json diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-non-existing-single-ref b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-non-existing-single-ref similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-non-existing-single-ref rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-non-existing-single-ref diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-single-ref b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-single-ref similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-single-ref rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-single-ref diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-no-output-wanted-ref-p1 b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-wanted-ref-p1 similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-no-output-wanted-ref-p1 rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-no-output-wanted-ref-p1 diff --git a/tests/snapshots/plumbing/pack/receive/file-v-any-with-output b/tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output similarity index 100% rename from tests/snapshots/plumbing/pack/receive/file-v-any-with-output rename to tests/snapshots/plumbing/no-repo/pack/receive/file-v-any-with-output diff --git a/tests/snapshots/plumbing/pack/receive/ls-in-output-dir b/tests/snapshots/plumbing/no-repo/pack/receive/ls-in-output-dir similarity index 100% rename from tests/snapshots/plumbing/pack/receive/ls-in-output-dir rename to tests/snapshots/plumbing/no-repo/pack/receive/ls-in-output-dir diff --git a/tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure b/tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure similarity index 80% rename from tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure rename to tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure index 04aa399b4c1..3ca8355b32e 100644 --- a/tests/snapshots/plumbing/pack/receive/pack receive-no-networking-in-small-failure +++ b/tests/snapshots/plumbing/no-repo/pack/receive/pack receive-no-networking-in-small-failure @@ -1,6 +1,6 @@ error: Found argument 'receive' which wasn't expected, or isn't valid in this context USAGE: - gix pack + gix free pack For more information try --help \ No newline at end of file diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/HEAD b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/HEAD similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/HEAD rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/HEAD diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/dev b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/dev similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/dev rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/dev diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/main b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/main similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/heads/main rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/heads/main diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/annotated b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/annotated similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/annotated rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/annotated diff --git a/tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/unannotated b/tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/unannotated similarity index 100% rename from tests/snapshots/plumbing/pack/receive/repo-refs/refs/tags/unannotated rename to tests/snapshots/plumbing/no-repo/pack/receive/repo-refs/refs/tags/unannotated diff --git a/tests/snapshots/plumbing/pack/verify/index-failure b/tests/snapshots/plumbing/no-repo/pack/verify/index-failure similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-failure rename to tests/snapshots/plumbing/no-repo/pack/verify/index-failure diff --git a/tests/snapshots/plumbing/pack/verify/index-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-success diff --git a/tests/snapshots/plumbing/pack/verify/index-with-statistics-json-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-with-statistics-json-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-json-success diff --git a/tests/snapshots/plumbing/pack/verify/index-with-statistics-success b/tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/index-with-statistics-success rename to tests/snapshots/plumbing/no-repo/pack/verify/index-with-statistics-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/fast-index-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/fast-index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/fast-index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/fast-index-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-json-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-json-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-json-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-json-success diff --git a/tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-success b/tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/multi-index/index-with-statistics-success rename to tests/snapshots/plumbing/no-repo/pack/verify/multi-index/index-with-statistics-success diff --git a/tests/snapshots/plumbing/pack/verify/success b/tests/snapshots/plumbing/no-repo/pack/verify/success similarity index 100% rename from tests/snapshots/plumbing/pack/verify/success rename to tests/snapshots/plumbing/no-repo/pack/verify/success diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any b/tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any similarity index 100% rename from tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any rename to tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any-json b/tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any-json similarity index 100% rename from tests/snapshots/plumbing/plumbing/remote-ref-list/file-v-any-json rename to tests/snapshots/plumbing/no-repo/remote/ref-list/file-v-any-json diff --git a/tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure b/tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure similarity index 80% rename from tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure rename to tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure index ebb5294e607..885fe4f7406 100644 --- a/tests/snapshots/plumbing/remote/ref-list/remote ref-list-no-networking-in-small-failure +++ b/tests/snapshots/plumbing/no-repo/remote/ref-list/remote ref-list-no-networking-in-small-failure @@ -1,6 +1,6 @@ error: Found argument 'remote' which wasn't expected, or isn't valid in this context USAGE: - gix [OPTIONS] + gix free For more information try --help \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success b/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success deleted file mode 100644 index 301cf39a105..00000000000 --- a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-json-success +++ /dev/null @@ -1,8 +0,0 @@ -{ - "longest_path_length": 2, - "num_commits": 3, - "parent_counts": { - "0": 1, - "1": 2 - } -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success b/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success deleted file mode 100644 index b0bf9808c4a..00000000000 --- a/tests/snapshots/plumbing/plumbing/commit-graph-verify/statistics-success +++ /dev/null @@ -1,6 +0,0 @@ -number of commits with the given number of parents - 0: 1 - 1: 2 - ->: 3 - -longest path length between two commits: 2 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure b/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure deleted file mode 100644 index 188504fa7b4..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-failure +++ /dev/null @@ -1,5 +0,0 @@ -Error: Failed to explode the entire pack - some loose objects may have been created nonetheless - -Caused by: - 0: The pack of this index file failed to verify its checksums - 1: pack checksum mismatch: expected f1cd3cc7bc63a4a2b357a475a58ad49b40355470, got 337fe3b886fc5041a35313887d68feefeae52519 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-skip-checks-success b/tests/snapshots/plumbing/plumbing/pack-explode/broken-delete-pack-to-sink-skip-checks-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide b/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide deleted file mode 100644 index 44c9b820241..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-miniz-oxide +++ /dev/null @@ -1,54 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -25 directories, 26 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng b/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng deleted file mode 100644 index 3b1ec68144d..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/broken-with-objects-dir-skip-checks-success-tree-zlib-ng +++ /dev/null @@ -1,56 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── a2 -│   └── 9ebd0e0fcbcd2a0842dd44cc7c22a90a310a3a -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -26 directories, 27 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail b/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail deleted file mode 100644 index 1993202aeed..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/missing-objects-dir-fail +++ /dev/null @@ -1 +0,0 @@ -Error: The object directory at 'does-not-exist' is inaccessible \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-delete-pack-success b/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-delete-pack-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-success b/tests/snapshots/plumbing/plumbing/pack-explode/to-sink-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success b/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree b/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree deleted file mode 100644 index 3632ea6c75e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-explode/with-objects-dir-success-tree +++ /dev/null @@ -1,61 +0,0 @@ -. -├── 0e -│   └── ad45fc727edcf5cadca25ef922284f32bb6fc1 -├── 15 -│   └── 926d8d6d17d1cbdf7f03c457e8ff983270f363 -├── 18 -│   └── bd3fc20b0565f94bce0a3e94b6a83b26b88627 -├── 1a -│   └── 480b442042edd4a6bacae41bf4113727e7a130 -├── 1d -│   └── fd336d2290794b0b1f80d98af33f725da6f42d -├── 2b -│   └── 621c1a3aac23b8258885a9b4658d9ac993742f -├── 2c -│   └── 1e59ee54facb7d72c0061d06b9fe3889f357a9 -├── 2d -│   └── ad8b277db3a95919bd904133d7e7cc3e323cb9 -├── 3a -│   └── b660ad62dd7c8c8bd637aa9bc1c2843a8439fe -├── 3d -│   └── 650a1c41a4529863818fd613b95e83668bbfc1 -├── 41 -│   └── 97ce3c6d943759e1088a0298b64571b4bc725a -├── 4c -│   ├── 35f641dbedaed230b5588fdc106c4538b4d09b -│   └── 97a057e41159f9767cf8704ed5ae181adf4d8d -├── 50 -│   └── 1b297447a8255d3533c6858bb692575cdefaa0 -├── 5d -│   └── e2eda652f29103c0d160f8c05d7e83b653a157 -├── 66 -│   └── 74d310d179400358d581f9725cbd4a2c32e3bf -├── 68 -│   └── b95733c796b12571fb1f656062a15a78e7dcf4 -├── 83 -│   └── d9602eccfc733a550812ce492d4caa0af625c8 -├── 84 -│   ├── 26f672fc65239135b1f1580bb79ecb16fd05f0 -│   └── 81dbefa2fb9398a673fe1f48dc480c1f558890 -├── 85 -│   └── 48234cfc7b4f0c9475d24d4c386783533a8034 -├── 88 -│   └── 58983d81b0eef76eb55d21a0d96b7b16846eca -├── ac -│   └── f86bca46d2b53d19a5a382e10def38d3e224da -├── af -│   └── 4f6405296dec699321ca59d48583ffa0323b0e -├── b2 -│   └── 025146d0718d953036352f8435cfa392b1d799 -├── bb -│   └── a287531b3a845faa032a8fef3e6d70d185c89b -├── bd -│   └── 91890c62d85ec16aadd3fb991b3ad7a365adde -├── cb -│   └── 572206d9dac4ba52878e7e1a4a7028d85707ab -├── e2 -│   └── 34c232ce0b8acef3f43fa34c036e68522b5612 -└── e8 - └── 00b9c207e17f9b11e321cc1fba5dfe08af4222 - -28 directories, 30 files \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success deleted file mode 100644 index f4fa3f420b9..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-as-json-success +++ /dev/null @@ -1,57 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": { - "Sha1": [ - 86, - 14, - 186, - 102, - 230, - 179, - 145, - 235, - 131, - 239, - 195, - 236, - 159, - 200, - 163, - 8, - 119, - 136, - 145, - 28 - ] - }, - "data_hash": { - "Sha1": [ - 241, - 205, - 60, - 199, - 188, - 99, - 164, - 162, - 179, - 87, - 164, - 117, - 165, - 138, - 212, - 155, - 64, - 53, - 84, - 112 - ] - }, - "num_objects": 30 - }, - "pack_kind": "V2", - "index_path": null, - "data_path": null -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success deleted file mode 100644 index d781ca1ed7f..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/no-output-dir-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 560eba66e6b391eb83efc3ec9fc8a3087788911c -pack: f1cd3cc7bc63a4a2b357a475a58ad49b40355470 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content deleted file mode 100644 index a649eb341f0..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-content +++ /dev/null @@ -1,2 +0,0 @@ -f1cd3cc7bc63a4a2b357a475a58ad49b40355470.idx -f1cd3cc7bc63a4a2b357a475a58ad49b40355470.pack \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success deleted file mode 100644 index 7027faa9fe6..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-as-json-success +++ /dev/null @@ -1,57 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": { - "Sha1": [ - 44, - 185, - 97, - 229, - 91, - 122, - 124, - 171, - 95, - 21, - 242, - 34, - 7, - 36, - 229, - 221, - 122, - 222, - 249, - 244 - ] - }, - "data_hash": { - "Sha1": [ - 1, - 186, - 104, - 186, - 85, - 239, - 94, - 145, - 116, - 131, - 212, - 206, - 70, - 190, - 40, - 132, - 168, - 158, - 81, - 175 - ] - }, - "num_objects": 13 - }, - "pack_kind": "V2", - "index_path": "" - "data_path": "" -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success deleted file mode 100644 index 2c5237a4c0e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-restore-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 2cb961e55b7a7cab5f15f2220724e5dd7adef9f4 -pack: 01ba68ba55ef5e917483d4ce46be2884a89e51af \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success b/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success deleted file mode 100644 index d781ca1ed7f..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-index-from-data/output-dir-success +++ /dev/null @@ -1,2 +0,0 @@ -index: 560eba66e6b391eb83efc3ec9fc8a3087788911c -pack: f1cd3cc7bc63a4a2b357a475a58ad49b40355470 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output deleted file mode 100644 index 57c0617c306..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output +++ /dev/null @@ -1,8 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 - -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json deleted file mode 100644 index 590a10265e2..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "index": { - "index_kind": "V2", - "index_hash": "c787de2aafb897417ca8167baeb146eabd18bc5f", - "data_hash": "346574b7331dc3a1724da218d622c6e1b6c66a57", - "num_objects": 9 - }, - "pack_kind": "V2", - "index_path": null, - "data_path": null, - "refs": [ - { - "Symbolic": { - "path": "HEAD", - "target": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Direct": { - "path": "refs/heads/dev", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Peeled": { - "path": "refs/tags/annotated", - "tag": "feae03400632392a7f38e5b2775f98a439f5eaf5", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/tags/unannotated", - "object": "efa596d621559707b2d221f10490959b2decbc6c" - } - } - ] -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref deleted file mode 100644 index 6bed155babf..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-non-existing-single-ref +++ /dev/null @@ -1,5 +0,0 @@ -Error: The server response could not be parsed - -Caused by: - 0: Upload pack reported an error - 1: unknown ref refs/heads/does-not-exist \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref deleted file mode 100644 index 84260bc4021..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-single-ref +++ /dev/null @@ -1,2 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 deleted file mode 100644 index c5203d08e6e..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-no-output-wanted-ref-p1 +++ /dev/null @@ -1,4 +0,0 @@ -Error: Could not access repository or failed to read streaming pack file - -Caused by: - Want to get specific refs, but remote doesn't support this capability \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output b/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output deleted file mode 100644 index 43ac53bfe11..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/file-v-any-with-output +++ /dev/null @@ -1,8 +0,0 @@ -index: c787de2aafb897417ca8167baeb146eabd18bc5f (out/346574b7331dc3a1724da218d622c6e1b6c66a57.idx) -pack: 346574b7331dc3a1724da218d622c6e1b6c66a57 (out/346574b7331dc3a1724da218d622c6e1b6c66a57.pack) - -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir b/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir deleted file mode 100644 index 9c73e822fe2..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/ls-in-output-dir +++ /dev/null @@ -1,2 +0,0 @@ -346574b7331dc3a1724da218d622c6e1b6c66a57.idx -346574b7331dc3a1724da218d622c6e1b6c66a57.pack \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure b/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure deleted file mode 100644 index 74d6602a909..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/pack-receive-no-networking-in-small-failure +++ /dev/null @@ -1 +0,0 @@ -Unrecognized argument: pack-receive \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD deleted file mode 100644 index bddcc0b83e6..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/HEAD +++ /dev/null @@ -1 +0,0 @@ -ref: refs/heads/main \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev deleted file mode 100644 index 1a3950e2e0c..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/dev +++ /dev/null @@ -1 +0,0 @@ -ee3c97678e89db4eab7420b04aef51758359f152 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main deleted file mode 100644 index 6f4a76ab3b8..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/heads/main +++ /dev/null @@ -1 +0,0 @@ -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated deleted file mode 100644 index cd887a5aedb..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/annotated +++ /dev/null @@ -1 +0,0 @@ -feae03400632392a7f38e5b2775f98a439f5eaf5 \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated b/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated deleted file mode 100644 index cbc7b39d75c..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-receive/repo-refs/refs/tags/unannotated +++ /dev/null @@ -1 +0,0 @@ -efa596d621559707b2d221f10490959b2decbc6c \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-failure b/tests/snapshots/plumbing/plumbing/pack-verify/index-failure deleted file mode 100644 index b93e9895bb1..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-failure +++ /dev/null @@ -1,6 +0,0 @@ -Could not find matching pack file at 'index.pack' - only index file will be verified, error was: Could not open pack file at 'index.pack' -Error: Verification failure - -Caused by: - 0: Index file, pack file or object verification failed - 1: index checksum mismatch: expected 0eba66e6b391eb83efc3ec9fc8a3087788911c0a, got fa9a8a630eacc2d3df00aff604bec2451ccbc8ff \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success deleted file mode 100644 index 9a42741b0ba..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-json-success +++ /dev/null @@ -1,26 +0,0 @@ -{ - "average": { - "kind": "Tree", - "num_deltas": 1, - "decompressed_size": 3456, - "compressed_size": 1725, - "object_size": 9621 - }, - "objects_per_chain_length": { - "0": 18, - "1": 4, - "2": 3, - "3": 1, - "4": 2, - "5": 1, - "6": 1 - }, - "total_compressed_entries_size": 51753, - "total_decompressed_entries_size": 103701, - "total_object_size": 288658, - "pack_size": 51875, - "num_commits": 10, - "num_trees": 15, - "num_tags": 0, - "num_blobs": 5 -} \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success b/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success deleted file mode 100644 index 420f9ce588d..00000000000 --- a/tests/snapshots/plumbing/plumbing/pack-verify/index-with-statistics-success +++ /dev/null @@ -1,31 +0,0 @@ -objects per delta chain length - 0: 18 - 1: 4 - 2: 3 - 3: 1 - 4: 2 - 5: 1 - 6: 1 - ->: 30 - -averages - delta chain length: 1; - decompressed entry [B]: 3456; - compressed entry [B]: 1725; - decompressed object size [B]: 9621; - -compression - compressed entries size : 51.8 KB - decompressed entries size : 103.7 KB - total object size : 288.7 KB - pack size : 51.9 KB - - num trees : 15 - num blobs : 5 - num commits : 10 - num tags : 0 - - compression ratio : 2.00 - delta compression ratio : 5.58 - delta gain : 2.78 - pack overhead : 0.235% \ No newline at end of file diff --git a/tests/snapshots/plumbing/plumbing/pack-verify/success b/tests/snapshots/plumbing/plumbing/pack-verify/success deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure b/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure deleted file mode 100644 index aaf27b70548..00000000000 --- a/tests/snapshots/plumbing/plumbing/remote-ref-list/remote-ref-list-no-networking-in-small-failure +++ /dev/null @@ -1 +0,0 @@ -Unrecognized argument: remote-ref-list \ No newline at end of file diff --git a/tests/snapshots/plumbing/remote/ref-list/file-v-any b/tests/snapshots/plumbing/remote/ref-list/file-v-any deleted file mode 100644 index 1ff9b7937dc..00000000000 --- a/tests/snapshots/plumbing/remote/ref-list/file-v-any +++ /dev/null @@ -1,5 +0,0 @@ -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 HEAD symref-target:refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/heads/dev -3f72b39ad1600e6dac63430c15e0d875e9d3f9d6 refs/heads/main -ee3c97678e89db4eab7420b04aef51758359f152 refs/tags/annotated tag:feae03400632392a7f38e5b2775f98a439f5eaf5 -efa596d621559707b2d221f10490959b2decbc6c refs/tags/unannotated \ No newline at end of file diff --git a/tests/snapshots/plumbing/remote/ref-list/file-v-any-json b/tests/snapshots/plumbing/remote/ref-list/file-v-any-json deleted file mode 100644 index e4afb4c6b48..00000000000 --- a/tests/snapshots/plumbing/remote/ref-list/file-v-any-json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "Symbolic": { - "path": "HEAD", - "target": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Direct": { - "path": "refs/heads/dev", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/heads/main", - "object": "3f72b39ad1600e6dac63430c15e0d875e9d3f9d6" - } - }, - { - "Peeled": { - "path": "refs/tags/annotated", - "tag": "feae03400632392a7f38e5b2775f98a439f5eaf5", - "object": "ee3c97678e89db4eab7420b04aef51758359f152" - } - }, - { - "Direct": { - "path": "refs/tags/unannotated", - "object": "efa596d621559707b2d221f10490959b2decbc6c" - } - } -] \ No newline at end of file diff --git a/tests/tools/Cargo.toml b/tests/tools/Cargo.toml index 2e1600f441b..4c2fae52bd1 100644 --- a/tests/tools/Cargo.toml +++ b/tests/tools/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "git-testtools" description = "Shared code for gitoxide crates to facilitate testing" -version = "0.7.0" +version = "0.7.1" authors = ["Sebastian Thiel "] edition = "2018" license = "MIT OR Apache-2.0" @@ -13,7 +13,7 @@ path = "src/main.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -git-hash = { version = "^0.9.5", path = "../../git-hash" } +git-hash = { version = "^0.9.6", path = "../../git-hash" } git-lock = { version = "^2.0.0", path = "../../git-lock" } git-discover = { version = "^0.3.0", path = "../../git-discover" } git-attributes = { version = "^0.3.0", path = "../../git-attributes" } diff --git a/tests/tools/src/lib.rs b/tests/tools/src/lib.rs index bffdf6454ae..9736bc8eb9b 100644 --- a/tests/tools/src/lib.rs +++ b/tests/tools/src/lib.rs @@ -1,3 +1,6 @@ +//! Utilities for testing `gitoxide` crates, many of which might be useful for testing programs that use `git` in general. +#![deny(missing_docs)] +use std::ffi::OsString; use std::{ collections::BTreeMap, convert::Infallible, @@ -9,11 +12,26 @@ use std::{ pub use bstr; use bstr::{BStr, ByteSlice}; use io_close::Close; +pub use is_ci; use nom::error::VerboseError; use once_cell::sync::Lazy; use parking_lot::Mutex; pub use tempfile; +/// A result type to allow using the try operator `?` in unit tests. +/// +/// Use it like so: +/// +/// ```norun +/// use git_testtools::Result; +/// +/// #[test] +/// fn this() -> Result { +/// let x: usize = "42".parse()?; +/// Ok(()) +/// +/// } +/// ``` pub type Result = std::result::Result>; static SCRIPT_IDENTITY: Lazy>> = Lazy::new(|| Mutex::new(BTreeMap::new())); @@ -45,11 +63,16 @@ static EXCLUDE_LUT: Lazy>>> = Lazy Mutex::new(cache) }); +/// Define how [scripted_fixture_repo_writable_with_args()] uses produces the writable copy. pub enum Creation { + /// Run the script once and copy the data from its output to the writable location. + /// This is fast but won't work if absolute paths are produced by the script. CopyFromReadOnly, + /// Run the script in the writable location. That way, absolute paths match the location. ExecuteScript, } +/// Run `git` in `working_dir` with all provided `args`. pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result { std::process::Command::new("git") .current_dir(working_dir) @@ -57,37 +80,60 @@ pub fn run_git(working_dir: &Path, args: &[&str]) -> std::io::Result git_hash::ObjectId { git_hash::ObjectId::from_hex(hex.as_bytes()).expect("40 bytes hex") } +/// Return the path to the `/tests/fixtures/` directory. pub fn fixture_path(path: impl AsRef) -> PathBuf { PathBuf::from("tests").join("fixtures").join(path.as_ref()) } -pub fn crate_under_test() -> String { - std::env::current_dir() - .expect("CWD is valid") - .file_name() - .expect("typical cargo invocation") - .to_string_lossy() - .into_owned() -} - +/// Load the fixture from `/tests/fixtures/` and return its data, or _panic_. pub fn fixture_bytes(path: impl AsRef) -> Vec { match std::fs::read(fixture_path(path.as_ref())) { Ok(res) => res, Err(_) => panic!("File at '{}' not found", path.as_ref().display()), } } + +/// Run the executable at `script_name`, like `make_repo.sh` to produce a read-only directory to which +/// the path is returned. +/// +/// Note that it persists and the script at `script_name` will only be executed once if it ran without error. +/// +/// ### Automatic Archive Creation +/// +/// In order to speed up CI and even local runs should the cache get purged, the result of each script run +/// is automatically placed into a compressed _tar_ archive. +/// If a script result doesn't exist, these will be checked first and extracted if present, which they are by default. +/// This behaviour can be prohibited by setting the `GITOXIDE_TEST_IGNORE_ARCHIVES` to any value. +/// +/// To speed CI up, one can add these archives to the repository. It's absoutely recommended to use `git-lfs` for that to +/// not bloat the repository size. +/// +/// #### Disable Archive Creation +/// +/// If archives aren't useful, they can be disabled by using `.gitignore` specifications. +/// That way it's trivial to prevent creation of all archives with `generated-archives/*.tar.xz` in the root +/// or more specific `.gitignore` configurations in lower levels of the work tree. +/// +/// The latter is useful if the the script's output is platform specific. pub fn scripted_fixture_repo_read_only(script_name: impl AsRef) -> Result { scripted_fixture_repo_read_only_with_args(script_name, None) } +/// Run the executable at `script_name`, like `make_repo.sh` to produce a writable directory to which +/// the tempdir is returned. It will be removed automatically, courtesy of [`tempfile::TempDir`]. +/// +/// Note that `script_name` is only executed once, so the data can be copied from its read-only location. pub fn scripted_fixture_repo_writable(script_name: &str) -> Result { scripted_fixture_repo_writable_with_args(script_name, None, Creation::CopyFromReadOnly) } +/// Like [`scripted_fixture_repo_writable()`], but passes `args` to `script_name` while providing control over +/// the way files are created with `mode`. pub fn scripted_fixture_repo_writable_with_args( script_name: &str, args: impl IntoIterator, @@ -107,6 +153,7 @@ pub fn scripted_fixture_repo_writable_with_args( }) } +/// A utility to copy the entire contents of `src_dir` into `dst_dir`. pub fn copy_recursively_into_existing_dir(src_dir: impl AsRef, dst_dir: impl AsRef) -> std::io::Result<()> { fs_extra::copy_items( &std::fs::read_dir(src_dir)? @@ -124,7 +171,8 @@ pub fn copy_recursively_into_existing_dir(src_dir: impl AsRef, dst_dir: im .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; Ok(()) } -/// Returns the directory at which the data is present + +/// Like `scripted_fixture_repo_read_only()`], but passes `args` to `script_name`. pub fn scripted_fixture_repo_read_only_with_args( script_name: impl AsRef, args: impl IntoIterator, @@ -386,6 +434,7 @@ fn extract_archive( Ok((archive_identity, platform)) } +/// Transform a verbose bom errors from raw bytes into a `BStr` to make printing/debugging human-readable. pub fn to_bstr_err(err: nom::Err>) -> VerboseError<&BStr> { let err = match err { nom::Err::Error(err) | nom::Err::Failure(err) => err, @@ -404,8 +453,36 @@ fn family_name() -> &'static str { } } -pub fn sleep_forever() -> ! { - loop { - std::thread::sleep(std::time::Duration::from_secs(u64::MAX)) +/// A utility to set environment variables, while unsetting them (or resetting them to their previous value) on drop. +#[derive(Default)] +pub struct Env<'a> { + altered_vars: Vec<(&'a str, Option)>, +} + +impl<'a> Env<'a> { + /// Create a new instance. + pub fn new() -> Self { + Env { + altered_vars: Vec::new(), + } + } + + /// Set `var` to `value`. + pub fn set(mut self, var: &'a str, value: impl Into) -> Self { + let prev = std::env::var_os(var); + std::env::set_var(var, value.into()); + self.altered_vars.push((var, prev)); + self + } +} + +impl<'a> Drop for Env<'a> { + fn drop(&mut self) { + for (var, prev_value) in &self.altered_vars { + match prev_value { + Some(value) => std::env::set_var(var, value), + None => std::env::remove_var(var), + } + } } }