summaryrefslogtreecommitdiffstats
path: root/vendor/gix-index
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-index
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-index')
-rw-r--r--vendor/gix-index/.cargo-checksum.json1
-rw-r--r--vendor/gix-index/CHANGELOG.md894
-rw-r--r--vendor/gix-index/Cargo.toml105
-rw-r--r--vendor/gix-index/README.md11
-rw-r--r--vendor/gix-index/src/access/mod.rs223
-rw-r--r--vendor/gix-index/src/access/sparse.rs59
-rw-r--r--vendor/gix-index/src/decode/entries.rs181
-rw-r--r--vendor/gix-index/src/decode/header.rs46
-rw-r--r--vendor/gix-index/src/decode/mod.rs321
-rw-r--r--vendor/gix-index/src/entry/flags.rs130
-rw-r--r--vendor/gix-index/src/entry/mod.rs109
-rw-r--r--vendor/gix-index/src/entry/mode.rs24
-rw-r--r--vendor/gix-index/src/entry/write.rs39
-rw-r--r--vendor/gix-index/src/extension/decode.rs80
-rw-r--r--vendor/gix-index/src/extension/end_of_index_entry/decode.rs56
-rw-r--r--vendor/gix-index/src/extension/end_of_index_entry/mod.rs14
-rw-r--r--vendor/gix-index/src/extension/end_of_index_entry/write.rs29
-rw-r--r--vendor/gix-index/src/extension/fs_monitor.rs38
-rw-r--r--vendor/gix-index/src/extension/index_entry_offset_table.rs43
-rw-r--r--vendor/gix-index/src/extension/iter.rs58
-rw-r--r--vendor/gix-index/src/extension/link.rs177
-rw-r--r--vendor/gix-index/src/extension/mod.rs96
-rw-r--r--vendor/gix-index/src/extension/resolve_undo.rs64
-rw-r--r--vendor/gix-index/src/extension/sparse.rs11
-rw-r--r--vendor/gix-index/src/extension/tree/decode.rs63
-rw-r--r--vendor/gix-index/src/extension/tree/mod.rs21
-rw-r--r--vendor/gix-index/src/extension/tree/verify.rs134
-rw-r--r--vendor/gix-index/src/extension/tree/write.rs45
-rw-r--r--vendor/gix-index/src/extension/untracked_cache.rs156
-rw-r--r--vendor/gix-index/src/file/init.rs81
-rw-r--r--vendor/gix-index/src/file/mod.rs90
-rw-r--r--vendor/gix-index/src/file/verify.rs41
-rw-r--r--vendor/gix-index/src/file/write.rs51
-rw-r--r--vendor/gix-index/src/init.rs155
-rw-r--r--vendor/gix-index/src/lib.rs201
-rw-r--r--vendor/gix-index/src/verify.rs73
-rw-r--r--vendor/gix-index/src/write.rs215
37 files changed, 4135 insertions, 0 deletions
diff --git a/vendor/gix-index/.cargo-checksum.json b/vendor/gix-index/.cargo-checksum.json
new file mode 100644
index 000000000..33a11976e
--- /dev/null
+++ b/vendor/gix-index/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"5fe6c799f8373ce66e99b8b04303cf5b480b668d6a5c90ed48a52acb939ce417","Cargo.toml":"10ecfe6c6adaa8a0541786ad5754d2761760b37934bd74f126faab9acde3b96f","README.md":"5b2b4dc7ee56738b6049fb41ff03dd3d71fbdc1f0f72080f47c0d24f79c4b5cd","src/access/mod.rs":"c3931b8826965815da37b4dc64f185ccc014cdfd5c22edd5e27f9cc764982b2e","src/access/sparse.rs":"06b49a6a4578be70814a85e376fda23d0a2465ca83d7a201f9f365b29710a50b","src/decode/entries.rs":"f4b36701ae17bb98472e9c37c8528a2ab4b3bce1a8eee17264b0f33b88a81caf","src/decode/header.rs":"dc8b369949e811e7f6f7234f0b2f344645582079af4fd63f02d7f30c0495ba43","src/decode/mod.rs":"67bcc963b2e3dc5a7196dad40d4423c7440355e31adc87d49e084162b88a0d62","src/entry/flags.rs":"bd34af362b9632a9f036e031c7fbfb3a204225df47ee6785e9851d5824bf15fe","src/entry/mod.rs":"d1aafb15d6d961ddffbe65b37556912718432f2b46c500dd65a2caa597dd58f8","src/entry/mode.rs":"47bf79906d24b8a2b05d4e8483d3065be40efb4db05f619d43a7915ad4d7a4aa","src/entry/write.rs":"9b1aa309fff759bd5791c39fb348ceb5cc6ce935d7eddaf841b9bea7672aa1c8","src/extension/decode.rs":"b39f3dbde55511e348514a9960f1ec6c724b7f1c222a0d246b6155966ec29624","src/extension/end_of_index_entry/decode.rs":"2b92b42b6379256db179801f5b07e08921a78a30d92000b6b9b3583880ec0004","src/extension/end_of_index_entry/mod.rs":"4f05ce9eecdd91af4ecec5aeb46d3cd90cb96b718b87ce72a5cb7577c5e2a697","src/extension/end_of_index_entry/write.rs":"2a95dc2b10c40b18d2da5bf3cce787b47c09b83efb1cf75a4b70a6258978f897","src/extension/fs_monitor.rs":"ef45ee058ece94470c1c8ac47a84b542f7cfab47bbe49862d0c92310946fcab0","src/extension/index_entry_offset_table.rs":"4f51387b087abbdeb96c87cf935602749bfe36f1d44779c44b24d719d2689de0","src/extension/iter.rs":"d3f3efd6fc542fcc5ec2631083b8ecd764c00838f5614ae4a9f8f6c5436b0009","src/extension/link.rs":"63338550498a040dbf669478bec791033c6bfe4ebc44c3cbfaa3ef10c67421a4","src/extension/mod.rs":"358dcf44248ce3920af9ff3764e972e69ecddd9f12c34faeab1bec85b6c85996","src/extension/resolve_undo.rs":"801f1a5e2423dea9dc77f9f1361dd45cf3217365d30a34eec761554e5f0f5272","src/extension/sparse.rs":"d95d64d39b1007ad1039ab3584671c25b0069e28b2cddfcdc91b090ee4eea684","src/extension/tree/decode.rs":"fda161e6e83f14356fe737ff66bb6cf01f759e584ca271bceff028e4da43a0c7","src/extension/tree/mod.rs":"a7ae7ee2bbacacf3536051f479c4f8c0cab73fa2dd270ab50ec96bdb49fc88db","src/extension/tree/verify.rs":"548f327dd0454d9f2b1475b70f3f3e20563a31523a2529354830d4545481fe55","src/extension/tree/write.rs":"bb71478fff5773385109c2006ecd7e84808691d566e2be04d8b90c048c9895b6","src/extension/untracked_cache.rs":"836401401fb30d6e9e11da7fe78fb890c001aa7d8d67b261a3cc2d7ea31bc302","src/file/init.rs":"fecb84bd78e7f3197c429192f1d5972137145657849470154781a5bf14570f30","src/file/mod.rs":"6737b994a21d519550d2930dab1d1de2e1cd42f0fc41c2fa10f6c37d24f14045","src/file/verify.rs":"f1bbe16ff8d0360e5899d8fa60583f9a9f058154cc2409b94cf9863ddf9f4a17","src/file/write.rs":"a930bca8a3f82cbe61f0a98eb965f92977a692f504f5680a5b938413aefc49fb","src/init.rs":"97fdc30e5fcbc32ad52467599d5180f6e996696c7647d45ee794f2a56642c64d","src/lib.rs":"6789597ac9580f7c0cae3bb614ca1b83f4fb35807d3bfde48b258d7af1d9807f","src/verify.rs":"b5fe5901b46072429376f47675fe807acc7f2490b3720e0e228d23df48818536","src/write.rs":"692741fec3de15efad7b1ef6b4beaccc69d3e136d136de9e1b2a461b4a08f389"},"package":"c12caf7886c7ba06f2b28835cdc2be1dca86bd047d00299d2d49e707ce1c2616"} \ No newline at end of file
diff --git a/vendor/gix-index/CHANGELOG.md b/vendor/gix-index/CHANGELOG.md
new file mode 100644
index 000000000..855256c81
--- /dev/null
+++ b/vendor/gix-index/CHANGELOG.md
@@ -0,0 +1,894 @@
+# Changelog
+
+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.14.0 (2023-03-04)
+
+A maintenance release without user-facing changes.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 4 commits contributed to the release over the course of 1 calendar day.
+ - 3 days passed between releases.
+ - 0 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Prepare changelogs prior to release ([`895e482`](https://github.com/Byron/gitoxide/commit/895e482badf01e953bb9144001eebd5e1b1c4d84))
+ - Release gix-features v0.28.0, gix-actor v0.19.0, gix-object v0.28.0, gix-diff v0.28.0, gix-traverse v0.24.0, gix-pack v0.32.0, safety bump 20 crates ([`0f411e9`](https://github.com/Byron/gitoxide/commit/0f411e93ec812592bb9d3a52b751399dd86f76f7))
+ - Merge branch 'adjustments-for-cargo' ([`04ab852`](https://github.com/Byron/gitoxide/commit/04ab852f3be76bdf151affa25cf4b999b127bdfe))
+ - Adjust to changes in `git-features` ([`726b2cd`](https://github.com/Byron/gitoxide/commit/726b2cd7bee7e9c5854bcac9ef0cd56ed0542f0d))
+</details>
+
+## 0.13.0 (2023-03-01)
+
+<csr-id-41c8151451d55b47c0784e4799aec102e6ab9f29/>
+
+### Chore
+
+ - <csr-id-41c8151451d55b47c0784e4799aec102e6ab9f29/> use `btoi` instead of `atoi`.
+ There was no need to introduce an extra dependency here, and `btoi` is already used
+ in a couple of additional places.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 9 commits contributed to the release over the course of 3 calendar days.
+ - 8 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release gix-tempfile v4.1.0, gix-lock v4.0.0, gix-ref v0.25.0, gix-config v0.17.0, gix-url v0.14.0, gix-credentials v0.10.0, gix-diff v0.27.0, gix-discover v0.14.0, gix-hashtable v0.1.2, gix-bitmap v0.2.2, gix-traverse v0.23.0, gix-index v0.13.0, gix-mailmap v0.10.0, gix-pack v0.31.0, gix-odb v0.41.0, gix-transport v0.26.0, gix-protocol v0.27.0, gix-revision v0.11.0, gix-refspec v0.8.0, gix-worktree v0.13.0, gix v0.38.0, safety bump 6 crates ([`ea9fd1d`](https://github.com/Byron/gitoxide/commit/ea9fd1d9b60e1e9e17042e9e37c06525823c40a5))
+ - Release gix-features v0.27.0, gix-actor v0.18.0, gix-quote v0.4.3, gix-attributes v0.9.0, gix-object v0.27.0, gix-ref v0.25.0, gix-config v0.17.0, gix-url v0.14.0, gix-credentials v0.10.0, gix-diff v0.27.0, gix-discover v0.14.0, gix-hashtable v0.1.2, gix-bitmap v0.2.2, gix-traverse v0.23.0, gix-index v0.13.0, gix-mailmap v0.10.0, gix-pack v0.31.0, gix-odb v0.41.0, gix-transport v0.26.0, gix-protocol v0.27.0, gix-revision v0.11.0, gix-refspec v0.8.0, gix-worktree v0.13.0, gix v0.38.0 ([`e6cc618`](https://github.com/Byron/gitoxide/commit/e6cc6184a7a49dbc2503c1c1bdd3688ca5cec5fe))
+ - Remove versions from dev-dependencies to workspace crates. ([`3cfbf89`](https://github.com/Byron/gitoxide/commit/3cfbf89f8630dfc71c9085eee6ca286a5c96ad84))
+ - Adjust manifests prior to release ([`addd789`](https://github.com/Byron/gitoxide/commit/addd78958fdd1e54eb702854e96079539d01965a))
+ - Prepare changelogs prior to release ([`94c99c7`](https://github.com/Byron/gitoxide/commit/94c99c71520f33269cc8dbc26f82a74747cc7e16))
+ - Merge branch 'adjustments-for-cargo' ([`d686d94`](https://github.com/Byron/gitoxide/commit/d686d94e1030a8591ba074757d56927a346c8351))
+ - Use `btoi` instead of `atoi`. ([`41c8151`](https://github.com/Byron/gitoxide/commit/41c8151451d55b47c0784e4799aec102e6ab9f29))
+ - Prepare for git-tempfile release ([`56c005b`](https://github.com/Byron/gitoxide/commit/56c005b13c44376f71e61781e73c0bf93416d0e4))
+ - Make fmt ([`8ef1cb2`](https://github.com/Byron/gitoxide/commit/8ef1cb293434c7b9e1fda4a6963368e0435920a9))
+</details>
+
+## 0.12.4 (2023-02-20)
+
+### Bug Fixes
+
+ - <csr-id-e14dc7d475373d2c266e84ff8f1826c68a34ab92/> note that crates have been renamed from `git-*` to `gix-*`.
+ This also means that the `git-*` prefixed crates of the `gitoxide` project
+ are effectively unmaintained.
+ Use the crates with the `gix-*` prefix instead.
+
+ If you were using `git-repository`, then `gix` is its substitute.
+ - <csr-id-135d317065aae87af302beb6c26bb6ca8e30b6aa/> compatibility with `bstr` v1.3, use `*.as_bytes()` instead of `.as_ref()`.
+ `as_ref()` relies on a known target type which isn't always present. However, once
+ there is only one implementation, that's no problem, but when that changes compilation
+ fails due to ambiguity.
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 2 commits contributed to the release.
+ - 3 days passed between releases.
+ - 1 commit was understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' were seen in commit messages
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **Uncategorized**
+ - Release gix-date v0.4.3, gix-hash v0.10.3, gix-features v0.26.5, gix-actor v0.17.2, gix-glob v0.5.5, gix-path v0.7.2, gix-quote v0.4.2, gix-attributes v0.8.3, gix-validate v0.7.3, gix-object v0.26.2, gix-ref v0.24.1, gix-config v0.16.2, gix-command v0.2.4, gix-url v0.13.3, gix-credentials v0.9.2, gix-discover v0.13.1, gix-index v0.12.4, gix-mailmap v0.9.3, gix-pack v0.30.3, gix-packetline v0.14.3, gix-transport v0.25.6, gix-protocol v0.26.4, gix-revision v0.10.4, gix-refspec v0.7.3, gix-worktree v0.12.3, gix v0.36.1 ([`9604783`](https://github.com/Byron/gitoxide/commit/96047839a20a657a559376b0b14c65aeab96acbd))
+ - Compatibility with `bstr` v1.3, use `*.as_bytes()` instead of `.as_ref()`. ([`135d317`](https://github.com/Byron/gitoxide/commit/135d317065aae87af302beb6c26bb6ca8e30b6aa))
+</details>
+
+## 0.12.3 (2023-02-17)
+
+<csr-id-dc3fd48327a16bf8599eaf70b55ab6c1d754e4bb/>
+<csr-id-4a6d46f3ab3d15eb851c92f7e49eb6772bc4023b/>
+<csr-id-6c17f96fcee9e2935b464c8ffbd30b253d9f5a6c/>
+<csr-id-533e887e80c5f7ede8392884562e1c5ba56fb9a8/>
+
+### Documentation
+
+ - <csr-id-39ed9eda62b7718d5109135e5ad406fb1fe2978c/> fix typos
+
+### New Features
+
+ - <csr-id-5dc408f726d6f0f480438348eb5d713776329710/> read shared indices by dissolving them into the current one.
+ This allows the 'link' extension to be processed correctly, even though it
+ won't be maintained. When written back, the 'link' extension will be removed
+ automatically.
+ - <csr-id-6d8eb9f267ac7ef8db0f3a277c8c991df5ce8164/> add `impl Debug for State` to print all entries in a digestible form.
+ - <csr-id-7c8ba2c945d6313d27569f04b83ebf9a2387e6a2/> add `State::sort_entries_by(...)` to allow comparing by additional criteria.
+ This allows to orgranize entries based on flags, for example, which may help
+ in special occasions.
+ - <csr-id-1e3341a77c089e6d80c271fca46b774b01b01386/> `entry::Time` can convert from and to system time.
+ - <csr-id-654bd8f62d5546b0f57e82d1be8211431685c7ce/> add `State::sort_entries()` and `State::dangerously_push_entry()`.
+ Both methods work in tandem as one breaks invariants, but allows to quickly
+ add entries, while the other restores them.
+ - <csr-id-aa1b6ee1edeaebae1237184c635f69fdbb23ffee/> add `State::entry_mut_by_path_and_stage()`
+ - <csr-id-3ebe2d490b2dbdcb6fe0115b06f205f1c4008fff/> `State::write_to()` respects the `REMOVED` flag.
+ That way, one can mark entries for removal and these will be pruned
+ at write time. This is preferable over performing removals expensively
+ in memory.
+ - <csr-id-ec365866c746247b7d5d47b88d51d9cc255724fb/> expose `git_hash` as `hash` in the root of the crate.
+ This makes it easier to use the crate standalone as plumbing as instantiation
+ requires access to `git_hash`.
+ - <csr-id-5cc3a15a8085a67701fc1f2d3ba0e55f71d0a4c0/> add `File::at_or_default(...)` to easily open or create an empty in-memory index.
+ This is a common operation in new repositories.
+ - <csr-id-0b40951f0fb9ee354a83751264ed89d0608111f8/> add `State::new()` to create a new empty in-memory index.
+ - <csr-id-a7183e28513fa1e1a1a784b759677f0e4b4db5f4/> add `File::set_path()` to allow setting the location of an index file on disk.
+ This is useful to change the location of an index after reading it (from another
+ location, similar to copy-on-write).
+ - <csr-id-fd2dd3a74c8d64407c1c27f29a2914389ded3bd6/> name spawned threads
+ That way it's a bit more obvious what's happening when the CPU goes
+ up in flames.
+ - <csr-id-458e1bcbd7043f0759f7445bfa46189910baff54/> Clnoe for `File`
+ - <csr-id-9e03110578bd93da3f1a91c5bcd9fde942c81ac4/> `decode::Options::default_from_object_hash()`
+ An easier way to initialize decode options, providing only the mandatory
+ information.
+ - <csr-id-eedcffa728c7e895da51d5298db28f3fef05f7da/> `File::write()` for secure and complete writing of index files.
+ - <csr-id-b1c40b0364ef092cd52d03b34f491b254816b18d/> use docsrs feature in code to show what is feature-gated automatically on docs.rs
+ - <csr-id-517677147f1c17304c62cf97a1dd09f232ebf5db/> pass --cfg docsrs when compiling for https://docs.rs
+ - <csr-id-6d8d5e6198dfb4d648061807ed4f96868a36ee52/> `Stage::entry_index_by_path_and_stage()`, now with `::entry_by_path_and_stage()`
+ - <csr-id-55363ea650001b7717545b4d2968419707a3b8c6/> `State::entry_by_path_and_stage()` to find entries.
+ - <csr-id-40e6bde125778e3b50999331c4ed5a4b119937fa/> `Debug` and `Clone` for `File`
+ - <csr-id-8ab219ac47ca67f2478b8715d7820fd6171c0db2/> `State::path_backing()`.
+ That way it's possible to call certain methods that take a separate
+ path buffer.
+ - <csr-id-645ed50dc2ae5ded2df0c09daf4fe366b90cf47e/> support for separating lifetimes of entries and path-backing
+ This way it should be possible to access paths immutably even while
+ entries are available mutably, assuming we stagger accesses to put
+ mutation of entries last.
+
+### Refactor
+
+ - <csr-id-dc3fd48327a16bf8599eaf70b55ab6c1d754e4bb/> inline bounds checks
+ With `FnMut` one can pass any data to mutable state available via references
+ and thus bypass limitations due to the closure's return type.
+
+ The `gix-bitmap` crate was kept simple to encourage using it this way.
+
+### Reverted (BREAKING)
+
+ - <csr-id-bd312acf5ceba28edf2508aef6011c037eb0a377/> `decode::Options::default()` - remove `Default` impl.
+ The contained `git_hash::Kind` can't actually be defaulted as we
+ have to know the actual kind used in the repository.
+ - <csr-id-2da5a62432350ede6b816254c894863d14aa4ba1/> remove `write::Options::default()`.
+ In practice it's required to inform about the hash kind to use and it's
+ possibly incorrect to assume Sha1.
+
+### Bug Fixes (BREAKING)
+
+ - <csr-id-0b1543d481337ed51dcfdeb907af21f0bc98dcb9/> lower rust edition to 2018
+
+### New Features (BREAKING)
+
+ - <csr-id-3d8fa8fef9800b1576beab8a5bc39b821157a5ed/> upgrade edition to 2021 in most crates.
+ MSRV for this is 1.56, and we are now at 1.60 so should be compatible.
+ This isn't more than a patch release as it should break nobody
+ who is adhering to the MSRV, but let's be careful and mark it
+ breaking.
+
+ Note that `git-features` and `git-pack` are still on edition 2018
+ as they make use of a workaround to support (safe) mutable access
+ to non-overlapping entries in a slice which doesn't work anymore
+ in edition 2021.
+
+### Changed (BREAKING)
+
+ - <csr-id-3753ad58a8303fbf325b07757b2b97c34253bca4/> remove `File::into_state()` in favor of `From<File> for State`.
+ That way it's less discoverable, but more idiomatic, and we don't want to
+ get into the habit of providing multiple names of the exact same
+ functionality.
+ - <csr-id-59f679126aba6f8a432aeb53f0bbd5d136ec1deb/> `write::Options::object_hash` is now implied by the `State` itself.
+ The `State`, once initialized, knows the kind of object hash it uses and
+ there is no need to specify it again.
+
+ This affects some method signatures which now work without
+ `object_hash`.
+ - <csr-id-908163ab2f86a1b603e69f04cd857fbf52e5abfb/> `decode::Options::object_hash` is now a parameter to methods.
+ It's not actually an option that could be defaulted, but an integral
+ piece of knowledge that must always be defined by the caller.
+
+ This also makes `decode::Options::default()` available once again.
+ - <csr-id-92dda50e2d9c584b0e110026f59fb715ec41600a/> seal `File` members to preserve consistency better.
+ This also makes sure that it's obvious if the `checksum` is actually
+ already computed.
+ - <csr-id-99905bacace8aed42b16d43f0f04cae996cb971c/> upgrade `bstr` to `1.0.1`
+
+### Other
+
+ - <csr-id-4a6d46f3ab3d15eb851c92f7e49eb6772bc4023b/> sketch out how a write implementation could work
+ - <csr-id-6c17f96fcee9e2935b464c8ffbd30b253d9f5a6c/> :init module
+
+### Bug Fixes
+
+ - <csr-id-c2cc939d131a278c177c5f44d3b26127c65bd352/> lower MSRV to 1.52
+
+### Chore
+
+ - <csr-id-533e887e80c5f7ede8392884562e1c5ba56fb9a8/> remove default link to cargo doc everywhere
+
+### Commit Statistics
+
+<csr-read-only-do-not-edit/>
+
+ - 433 commits contributed to the release over the course of 903 calendar days.
+ - 37 commits were understood as [conventional](https://www.conventionalcommits.org).
+ - 11 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), [#384](https://github.com/Byron/gitoxide/issues/384), [#427](https://github.com/Byron/gitoxide/issues/427), [#450](https://github.com/Byron/gitoxide/issues/450), [#470](https://github.com/Byron/gitoxide/issues/470), [#691](https://github.com/Byron/gitoxide/issues/691), [#XXX](https://github.com/Byron/gitoxide/issues/XXX)
+
+### Commit Details
+
+<csr-read-only-do-not-edit/>
+
+<details><summary>view details</summary>
+
+ * **[#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))
+ - Prepare changelogs for git-index and dependencies ([`f54bf4b`](https://github.com/Byron/gitoxide/commit/f54bf4bde92b892b6d425987a6a37e10319c4635))
+ - Test for extra-long paths ([`3d61afe`](https://github.com/Byron/gitoxide/commit/3d61afe615227e2af96525eba5b0e8e2f94207f3))
+ - Test for extended flags ([`ae3b697`](https://github.com/Byron/gitoxide/commit/ae3b69710cf316cb8164120d4b98f051eef363bc))
+ - Use bitflags for Flags (in-memory and at-rest) ([`ea86eb0`](https://github.com/Byron/gitoxide/commit/ea86eb0bebb0f067fc8710779c2c296632451c54))
+ - Use bitflags for entry Mode ([`53df605`](https://github.com/Byron/gitoxide/commit/53df6057a8c50007716d89e08f1efe70435f8613))
+ - 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))
+ - Read remaining pieces of UNTR ([`9d9cc95`](https://github.com/Byron/gitoxide/commit/9d9cc95a24d86cf5f66f1746c09ece032640a892))
+ - 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))
+ - Flatten UNTR directory list for later access via bitmaps ([`2e39184`](https://github.com/Byron/gitoxide/commit/2e391841af52f88b7a0472179e5dda89cc6c9808))
+ - Read UNTR directory blocks and bitmaps ([`59f46fe`](https://github.com/Byron/gitoxide/commit/59f46fe134e8619f501c79da4290cadd5548751c))
+ - 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))
+ - Add UNTR extension fixture ([`3c7ba24`](https://github.com/Byron/gitoxide/commit/3c7ba247a3fdab114d9d549de50d6143c7fcce0a))
+ - REUC reading works ([`29c1af9`](https://github.com/Byron/gitoxide/commit/29c1af9b2d7b9879a806fc84cfc89ed6c0d7f083))
+ - Frame and test for REUC exstension ([`229cabe`](https://github.com/Byron/gitoxide/commit/229cabe8de9e1bd244d56d24327b05e3d80dfb6e))
+ - Add git index with REUC exstension ([`8359fdb`](https://github.com/Byron/gitoxide/commit/8359fdb6c49b263bc7ac2f3105254b83eac47638))
+ - 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))
+ - 'link' extension decoding to the point where bitmaps are needed ([`e18a2fd`](https://github.com/Byron/gitoxide/commit/e18a2fd68e1c7c06fe2ff9cd1634704313822b5c))
+ - Support for errors in extensions ([`8971991`](https://github.com/Byron/gitoxide/commit/8971991808b1497b9584b52270259d3c625fa970))
+ - Failing test for link decoding ([`e1daf18`](https://github.com/Byron/gitoxide/commit/e1daf18041862570fdfe53ff88d879d0c3cb7182))
+ - Don't forget to fail on unknown mandatory extension ([`f7e2bdd`](https://github.com/Byron/gitoxide/commit/f7e2bdd7dcb21600d2aca7d29d131eefe43f0f4f))
+ - Aggregation for index entries loaded in parallel ([`995994a`](https://github.com/Byron/gitoxide/commit/995994a895a6faa4537ae1a6564edc005be96a1a))
+ - Parallel loading of entries right before reducing them ([`de84a3a`](https://github.com/Byron/gitoxide/commit/de84a3a03bcc9dc3ff71810e35c869f9b73dd38f))
+ - Frame for using the new 'scoped threads' feature in git-features ([`6fea17d`](https://github.com/Byron/gitoxide/commit/6fea17d1306679d0454d01aa59adf12cd83c7973))
+ - Single and multi-threaded index tests ([`a22cb0f`](https://github.com/Byron/gitoxide/commit/a22cb0f1ead9a2f32e43eb2fb378281e592a4ed3))
+ - Prepare decode options for better control of threads ([`30de988`](https://github.com/Byron/gitoxide/commit/30de988f6a97177fcb32ffce37f4c80f46306a20))
+ - Cleanup ([`99d7224`](https://github.com/Byron/gitoxide/commit/99d7224baa04c199a7eb7aa2675b39657b0aef6a))
+ - Basic IEOT parsing ([`35bdee4`](https://github.com/Byron/gitoxide/commit/35bdee4bf77787bcbe6c3dd715a677e2e46a8ad1))
+ - Refactor ([`6f04f8b`](https://github.com/Byron/gitoxide/commit/6f04f8b8276de9c6b649642fb7c95eb5ffad77e4))
+ - Parse V4 delta-paths ([`06640e3`](https://github.com/Byron/gitoxide/commit/06640e3f98f25e9502db7ac68e1967d9fd25e8b2))
+ - More thorough tests for more complex repo with more entries ([`273853f`](https://github.com/Byron/gitoxide/commit/273853f1614a0106c60d3d73c3bf72fb57b405e8))
+ - The first test to validate an entry ([`f865ef6`](https://github.com/Byron/gitoxide/commit/f865ef6c626c9db39a09416333b6465fdd12c734))
+ - Now with counting of consumed bytes in extensions ([`77a062c`](https://github.com/Byron/gitoxide/commit/77a062cdaff1bdf80556301f1e1aa41002af9cef))
+ - Use correct post-header slice when parsing entries ([`da556b0`](https://github.com/Byron/gitoxide/commit/da556b0a64ac9ca8eaee62cab163789b55903b3d))
+ - All code needed to load extensions… ([`0a03f19`](https://github.com/Byron/gitoxide/commit/0a03f196b7ec4dc1e0e2377c729467781c9e6c2c))
+ - A step towards pasing V2 paths ([`01036ad`](https://github.com/Byron/gitoxide/commit/01036ad1bafb6a830734a9dd4f4e2949b8981a30))
+ - Most of the entry decoding, name is still missing ([`53e2d75`](https://github.com/Byron/gitoxide/commit/53e2d754262d9752d3b106f7991543986ad5426f))
+ - Extensions are optional, and so is their iteration ([`620d2e6`](https://github.com/Byron/gitoxide/commit/620d2e6bd4ef6d3281c096aaf344669bcf49e723))
+ - Prepare a more complex test for tree parsing, requires entry parsing ([`e7e0679`](https://github.com/Byron/gitoxide/commit/e7e067977ef440cf3edb8812c0d614b5d8213b58))
+ - Parse TREE chunk ([`a2ea498`](https://github.com/Byron/gitoxide/commit/a2ea49841a333c8af18fd258781a649214a0ae0b))
+ - Get closer to implementing a simple TREE extension decoding ([`49fcb6f`](https://github.com/Byron/gitoxide/commit/49fcb6f6ae9d6ed47e7c0c3ea2aa644d4e8cd264))
+ - Refactor ([`07e8fb2`](https://github.com/Byron/gitoxide/commit/07e8fb2cb6b7819eb34676ede57808b845298674))
+ - The first actual assetion ([`c17240d`](https://github.com/Byron/gitoxide/commit/c17240d0cbd6134a77a69359611789f4eebc727d))
+ - Refactor ([`d4b3a07`](https://github.com/Byron/gitoxide/commit/d4b3a07489703fb6d5e9b9fb9328741172826db9))
+ - Refactor ([`9fdd34b`](https://github.com/Byron/gitoxide/commit/9fdd34b634f4f15eb6cf5c2e7912bdc32dd61de6))
+ - Fix counting issue, checksum matches now ([`cc33752`](https://github.com/Byron/gitoxide/commit/cc337526365a04a23571123531f1ae565d386bcf))
+ - Another big step, even though EOIE checksum is still bugged ([`9ffd523`](https://github.com/Byron/gitoxide/commit/9ffd5231c582a3870c6d25ea870c005e77e32276))
+ - Right before implementing a traversal over extension chunks ([`79ca582`](https://github.com/Byron/gitoxide/commit/79ca582045dd03434737c779b84c991acf1b0823))
+ - Refactor ([`9b28b18`](https://github.com/Byron/gitoxide/commit/9b28b18262c763608d60fba65e91fcb9ca3ddb3e))
+ - First step towards reading the EOIE extension ([`068c716`](https://github.com/Byron/gitoxide/commit/068c716b46699234d6ad1db70be34b894e61d76a))
+ - Parse index header ([`5c731f8`](https://github.com/Byron/gitoxide/commit/5c731f831d007a4fe099cadc4ecaab113ab7e08a))
+ - First stab at basic index file parsing ([`826ca0c`](https://github.com/Byron/gitoxide/commit/826ca0c6a6801ec2a67ca73ac17092e5f85fe9ce))
+ - Refactor ([`494ed46`](https://github.com/Byron/gitoxide/commit/494ed46acc54bd342f891416918032a2c4848cf1))
+ - Git-index uses memmap2 ([`fbfea28`](https://github.com/Byron/gitoxide/commit/fbfea28d2c9ed92e270c6a5aa603d3c84769ae8f))
+ - 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)**
+ - Upgrade git-index->atoi to 1.0 ([`728dd65`](https://github.com/Byron/gitoxide/commit/728dd6501b86b12e1d0237256f94059a7ead14a9))
+ - 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)**
+ - Update changelogs prior to release ([`84cb256`](https://github.com/Byron/gitoxide/commit/84cb25614a5fcddff297c1713eba4efbb6ff1596))
+ - 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))
+ - 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))
+ - 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))
+ - Make fmt ([`636fa8a`](https://github.com/Byron/gitoxide/commit/636fa8a97ce56982c76dffc64ee084e31d39afad))
+ - Strucural refactor ([`cdca1df`](https://github.com/Byron/gitoxide/commit/cdca1dfec590d24dd42f34294e21f4bdf61d36ad))
+ - Allow mutation of entries during iteration, while obtaining their path ([`d0c4563`](https://github.com/Byron/gitoxide/commit/d0c4563f71ea18aaf8ae21dd8646ab256a550594))
+ * **[#329](https://github.com/Byron/gitoxide/issues/329)**
+ - Document all features related to serde1 ([`72b97f2`](https://github.com/Byron/gitoxide/commit/72b97f2ae4dc7642b160f183c6d5df4502dc186f))
+ * **[#333](https://github.com/Byron/gitoxide/issues/333)**
+ - Use git_features::path everywhere where there is a path conversion ([`2e1437c`](https://github.com/Byron/gitoxide/commit/2e1437cb0b5dc77f2317881767f71eaf9b009ebf))
+ * **[#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))
+ - Add archive files via git-lfs ([`7202a1c`](https://github.com/Byron/gitoxide/commit/7202a1c4734ad904c026ee3e4e2143c0461d51a2))
+ - 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))
+ * **[#427](https://github.com/Byron/gitoxide/issues/427)**
+ - Make fmt ([`4b320e7`](https://github.com/Byron/gitoxide/commit/4b320e773368ac5e8c38dd8a779ef3d6d2d024ec))
+ - Fix docs ([`5a0d6b7`](https://github.com/Byron/gitoxide/commit/5a0d6b76205d6e021348a930a5a17820e5dc4458))
+ - `Stage::entry_index_by_path_and_stage()`, now with `::entry_by_path_and_stage()` ([`6d8d5e6`](https://github.com/Byron/gitoxide/commit/6d8d5e6198dfb4d648061807ed4f96868a36ee52))
+ - `State::entry_by_path_and_stage()` to find entries. ([`55363ea`](https://github.com/Byron/gitoxide/commit/55363ea650001b7717545b4d2968419707a3b8c6))
+ - Refactor; prepare for entry-lookup by path ([`92de081`](https://github.com/Byron/gitoxide/commit/92de081dc9ab5660cb18fa750452345dd63550ea))
+ - `Debug` and `Clone` for `File` ([`40e6bde`](https://github.com/Byron/gitoxide/commit/40e6bde125778e3b50999331c4ed5a4b119937fa))
+ * **[#450](https://github.com/Byron/gitoxide/issues/450)**
+ - Clnoe for `File` ([`458e1bc`](https://github.com/Byron/gitoxide/commit/458e1bcbd7043f0759f7445bfa46189910baff54))
+ - Upgrade `bstr` to `1.0.1` ([`99905ba`](https://github.com/Byron/gitoxide/commit/99905bacace8aed42b16d43f0f04cae996cb971c))
+ * **[#470](https://github.com/Byron/gitoxide/issues/470)**
+ - Update changelogs prior to release ([`caa7a1b`](https://github.com/Byron/gitoxide/commit/caa7a1bdef74d7d3166a7e38127a59f5ab3cfbdd))
+ * **[#691](https://github.com/Byron/gitoxide/issues/691)**
+ - Set `rust-version` to 1.64 ([`55066ce`](https://github.com/Byron/gitoxide/commit/55066ce5fd71209abb5d84da2998b903504584bb))
+ * **[#XXX](https://github.com/Byron/gitoxide/issues/XXX)**
+ - Add tests to run into long-paths special case ([`d7a8a7d`](https://github.com/Byron/gitoxide/commit/d7a8a7dfe3089e35fca249af7a3482a893f91111))
+ * **Uncategorized**
+ - Release gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`48f5bd2`](https://github.com/Byron/gitoxide/commit/48f5bd2014fa3dda6fbd60d091065c5537f69453))
+ - Release gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`a5869e0`](https://github.com/Byron/gitoxide/commit/a5869e0b223406820bca836e3e3a7fae2bfd9b04))
+ - Release gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`41d57b9`](https://github.com/Byron/gitoxide/commit/41d57b98964094fc1528adb09f69ca824229bf25))
+ - Release gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`e313112`](https://github.com/Byron/gitoxide/commit/e31311257bd138b52042dea5fc40c3abab7f269b))
+ - Release gix-features v0.26.4, gix-actor v0.17.1, gix-glob v0.5.3, gix-path v0.7.1, gix-quote v0.4.1, gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`6efd0d3`](https://github.com/Byron/gitoxide/commit/6efd0d31fbeca31ab7319aa2ac97bb31dc4ce055))
+ - Release gix-date v0.4.2, gix-hash v0.10.2, gix-features v0.26.4, gix-actor v0.17.1, gix-glob v0.5.3, gix-path v0.7.1, gix-quote v0.4.1, gix-attributes v0.8.2, gix-config-value v0.10.1, gix-tempfile v3.0.2, gix-lock v3.0.2, gix-validate v0.7.2, gix-object v0.26.1, gix-ref v0.24.0, gix-sec v0.6.2, gix-config v0.16.1, gix-command v0.2.3, gix-prompt v0.3.2, gix-url v0.13.2, gix-credentials v0.9.1, gix-diff v0.26.1, gix-discover v0.13.0, gix-hashtable v0.1.1, gix-bitmap v0.2.1, gix-traverse v0.22.1, gix-index v0.12.3, gix-mailmap v0.9.2, gix-chunk v0.4.1, gix-pack v0.30.2, gix-odb v0.40.2, gix-packetline v0.14.2, gix-transport v0.25.4, gix-protocol v0.26.3, gix-revision v0.10.3, gix-refspec v0.7.2, gix-worktree v0.12.2, gix v0.36.0 ([`6ccc88a`](https://github.com/Byron/gitoxide/commit/6ccc88a8e4a56973b1a358cf72dc012ee3c75d56))
+ - Merge branch 'rename-crates' into inform-about-gix-rename ([`c9275b9`](https://github.com/Byron/gitoxide/commit/c9275b99ea43949306d93775d9d78c98fb86cfb1))
+ - Fix link in docs for `sort_entries_by` ([`eb48b16`](https://github.com/Byron/gitoxide/commit/eb48b162c3f9c449de0fabfc2e8a33001a2d14d0))
+ - Rename `git-testtools` to `gix-testtools` ([`b65c33d`](https://github.com/Byron/gitoxide/commit/b65c33d256cfed65d11adeff41132e3e58754089))
+ - Adjust to renaming of `git-pack` to `gix-pack` ([`1ee81ad`](https://github.com/Byron/gitoxide/commit/1ee81ad310285ee4aa118118a2be3810dbace574))
+ - Adjust to renaming of `git-odb` to `gix-odb` ([`476e2ad`](https://github.com/Byron/gitoxide/commit/476e2ad1a64e9e3f0d7c8651d5bcbee36cd78241))
+ - Adjust to renaming of `git-index` to `gix-index` ([`86db5e0`](https://github.com/Byron/gitoxide/commit/86db5e09fc58ce66b252dc13b8d7e2c48e4d5062))
+ - Rename `git-index` to `gix-index` ([`ecd3541`](https://github.com/Byron/gitoxide/commit/ecd354119c3a8b150a06df7205ddf022a825d6cd))
+ - Adjust to renaming of `git-diff` to `gix-diff` ([`49a163e`](https://github.com/Byron/gitoxide/commit/49a163ec8b18f0e5fcd05a315de16d5d8be7650e))
+ - Adjust to renaming of `git-commitgraph` to `gix-commitgraph` ([`f1dd0a3`](https://github.com/Byron/gitoxide/commit/f1dd0a3366e31259af029da73228e8af2f414244))
+ - Adjust to renaming of `git-mailmap` to `gix-mailmap` ([`2e28c56`](https://github.com/Byron/gitoxide/commit/2e28c56bb9f70de6f97439818118d3a25859698f))
+ - Adjust to renaming of `git-discover` to `gix-discover` ([`53adfe1`](https://github.com/Byron/gitoxide/commit/53adfe1c34e9ea3b27067a97b5e7ac80b351c441))
+ - Adjust to renaming of `git-lfs` to `gix-lfs` ([`b9225c8`](https://github.com/Byron/gitoxide/commit/b9225c830daf1388484ee7e05f727990fdeff43c))
+ - Adjust to renaming of `git-chunk` to `gix-chunk` ([`59194e3`](https://github.com/Byron/gitoxide/commit/59194e3a07853eae0624ebc4907478d1de4f7599))
+ - Adjust to renaming of `git-bitmap` to `gix-bitmap` ([`75f2a07`](https://github.com/Byron/gitoxide/commit/75f2a079b17489f62bc43e1f1d932307375c4f9d))
+ - Adjust to renaming for `git-protocol` to `gix-protocol` ([`823795a`](https://github.com/Byron/gitoxide/commit/823795addea3810243cab7936cd8ec0137cbc224))
+ - Adjust to renaming of `git-refspec` to `gix-refspec` ([`c958802`](https://github.com/Byron/gitoxide/commit/c9588020561577736faa065e7e5b5bb486ca8fe1))
+ - Adjust to renaming of `git-revision` to `gix-revision` ([`ee0ee84`](https://github.com/Byron/gitoxide/commit/ee0ee84607c2ffe11ee75f27a31903db68afed02))
+ - Adjust to renaming of `git-transport` to `gix-transport` ([`b2ccf71`](https://github.com/Byron/gitoxide/commit/b2ccf716dc4425bb96651d4d58806a3cc2da219e))
+ - Adjust to renaming of `git-credentials` to `gix-credentials` ([`6b18abc`](https://github.com/Byron/gitoxide/commit/6b18abcf2856f02ab938d535a65e51ac282bf94a))
+ - Adjust to renaming of `git-prompt` to `gix-prompt` ([`6a4654e`](https://github.com/Byron/gitoxide/commit/6a4654e0d10ab773dd219cb4b731c0fc1471c36d))
+ - Adjust to renaming of `git-command` to `gix-command` ([`d26b8e0`](https://github.com/Byron/gitoxide/commit/d26b8e046496894ae06b0bbfdba77196976cd975))
+ - Adjust to renaming of `git-packetline` to `gix-packetline` ([`5cbd22c`](https://github.com/Byron/gitoxide/commit/5cbd22cf42efb760058561c6c3bbcd4dab8c8be1))
+ - Adjust to renaming of `git-worktree` to `gix-worktree` ([`73a1282`](https://github.com/Byron/gitoxide/commit/73a12821b3d9b66ec1714d07dd27eb7a73e3a544))
+ - Adjust to renamining of `git-hashtable` to `gix-hashtable` ([`26a0c98`](https://github.com/Byron/gitoxide/commit/26a0c98d0a389b03e3dc7bfc758b37155e285244))
+ - Adjust to renamining of `git-worktree` to `gix-worktree` ([`108bb1a`](https://github.com/Byron/gitoxide/commit/108bb1a634f4828853fb590e9fc125f79441dd38))
+ - Adjust to renaming of `git-url` to `gix-url` ([`b50817a`](https://github.com/Byron/gitoxide/commit/b50817aadb143e19f61f64e19b19ec1107d980c6))
+ - Adjust to renaming of `git-date` to `gix-date` ([`9a79ff2`](https://github.com/Byron/gitoxide/commit/9a79ff2d5cc74c1efad9f41e21095ae498cce00b))
+ - Adjust to renamining of `git-attributes` to `gix-attributes` ([`4a8b3b8`](https://github.com/Byron/gitoxide/commit/4a8b3b812ac26f2a2aee8ce8ca81591273383c84))
+ - Adjust to renaminig of `git-quote` to `gix-quote` ([`648025b`](https://github.com/Byron/gitoxide/commit/648025b7ca94411fdd0d90c53e5faede5fde6c8d))
+ - Adjust to renaming of `git-config` to `gix-config` ([`3a861c8`](https://github.com/Byron/gitoxide/commit/3a861c8f049f6502d3bcbdac752659aa1aeda46a))
+ - Adjust to renaming of `git-ref` to `gix-ref` ([`1f5f695`](https://github.com/Byron/gitoxide/commit/1f5f695407b034377d94b172465ff573562b3fc3))
+ - Adjust to renaming of `git-lock` to `gix-lock` ([`2028e78`](https://github.com/Byron/gitoxide/commit/2028e7884ae1821edeec81612f501e88e4722b17))
+ - Adjust to renaming of `git-tempfile` to `gix-tempfile` ([`b6cc3eb`](https://github.com/Byron/gitoxide/commit/b6cc3ebb5137084a6327af16a7d9364d8f092cc9))
+ - Adjust to renaming of `git-object` to `gix-object` ([`fc86a1e`](https://github.com/Byron/gitoxide/commit/fc86a1e710ad7bf076c25cc6f028ddcf1a5a4311))
+ - Adjust to renaming of `git-actor` to `gix-actor` ([`4dc9b44`](https://github.com/Byron/gitoxide/commit/4dc9b44dc52f2486ffa2040585c6897c1bf55df4))
+ - Adjust to renaming of `git-validate` to `gix-validate` ([`5e40ad0`](https://github.com/Byron/gitoxide/commit/5e40ad078af3d08cbc2ca81ce755c0ed8a065b4f))
+ - Adjust to renaming of `git-hash` to `gix-hash` ([`4a9d025`](https://github.com/Byron/gitoxide/commit/4a9d0257110c3efa61d08c8457c4545b200226d1))
+ - Adjust to renaming of `git-features` to `gix-features` ([`e2dd68a`](https://github.com/Byron/gitoxide/commit/e2dd68a417aad229e194ff20dbbfd77668096ec6))
+ - Adjust to renaming of `git-glob` to `gix-glob` ([`35b2a3a`](https://github.com/Byron/gitoxide/commit/35b2a3acbc8f2a03f151bc0a3863163844e0ca86))
+ - Adjust to renaming of `git-sec` to `gix-sec` ([`eabbb92`](https://github.com/Byron/gitoxide/commit/eabbb923bd5a32fc80fa80f96cfdc2ab7bb2ed17))
+ - Adapt to renaming of `git-path` to `gix-path` ([`d3bbcfc`](https://github.com/Byron/gitoxide/commit/d3bbcfccad80fc44ea8e7bf819f23adaca06ba2d))
+ - Adjust to rename of `git-config-value` to `gix-config-value` ([`622b3e1`](https://github.com/Byron/gitoxide/commit/622b3e1d0bffa0f8db73697960f9712024fac430))
+ - Release git-features v0.26.4 ([`109f434`](https://github.com/Byron/gitoxide/commit/109f434e66559a791d541f86876ded8df10766f1))
+ - Release git-features v0.26.3 ([`1ecfb7f`](https://github.com/Byron/gitoxide/commit/1ecfb7f8bfb24432690d8f31367488f2e59a642a))
+ - Merge branch 'rename-crates' ([`6461c3d`](https://github.com/Byron/gitoxide/commit/6461c3da4d6daee857606d94294c3f87fc36965a))
+ - Rename `git-repository` to `gix` ([`7bed2a9`](https://github.com/Byron/gitoxide/commit/7bed2a96604397fa990f427b1a970ddeb6f09f1c))
+ - Release git-date v0.4.2, git-hash v0.10.2, git-features v0.26.2, git-actor v0.17.1, git-glob v0.5.3, git-path v0.7.1, git-quote v0.4.1, git-attributes v0.8.2, git-config-value v0.10.1, git-tempfile v3.0.2, git-lock v3.0.2, git-validate v0.7.2, git-object v0.26.1, git-ref v0.24.0, git-sec v0.6.2, git-config v0.16.0, git-command v0.2.3, git-prompt v0.3.2, git-url v0.13.2, git-credentials v0.9.1, git-diff v0.26.1, git-discover v0.13.0, git-hashtable v0.1.1, git-bitmap v0.2.1, git-traverse v0.22.1, git-index v0.12.3, git-mailmap v0.9.2, git-chunk v0.4.1, git-pack v0.30.2, git-odb v0.40.2, git-packetline v0.14.2, git-transport v0.25.4, git-protocol v0.26.3, git-revision v0.10.2, git-refspec v0.7.2, git-worktree v0.12.2, git-repository v0.34.0, safety bump 3 crates ([`c196d20`](https://github.com/Byron/gitoxide/commit/c196d206d57a310b1ce974a1cf0e7e6d6db5c4d6))
+ - Prepare changelogs prior to release ([`7c846d2`](https://github.com/Byron/gitoxide/commit/7c846d2102dc767366771925212712ef8cc9bf07))
+ - Merge branch 'Lioness100/main' ([`1e544e8`](https://github.com/Byron/gitoxide/commit/1e544e82455bf9ecb5e3c2146280eaf7ecd81f16))
+ - Fix typos ([`39ed9ed`](https://github.com/Byron/gitoxide/commit/39ed9eda62b7718d5109135e5ad406fb1fe2978c))
+ - Make fmt ([`e22080e`](https://github.com/Byron/gitoxide/commit/e22080e4a29d0bad15a99d565a5e3e304a8743ec))
+ - Break cyclical dev dependencies ([`1fea18f`](https://github.com/Byron/gitoxide/commit/1fea18f5f8b4189a23dc4fa3f041a672f6fbcfb3))
+ - Make error handling of split index to seemingly be en-par with 'git' ([`cec2547`](https://github.com/Byron/gitoxide/commit/cec2547758b91fa340130d55a3920445b75b3636))
+ - Read shared indices by dissolving them into the current one. ([`5dc408f`](https://github.com/Byron/gitoxide/commit/5dc408f726d6f0f480438348eb5d713776329710))
+ - Inline bounds checks ([`dc3fd48`](https://github.com/Byron/gitoxide/commit/dc3fd48327a16bf8599eaf70b55ab6c1d754e4bb))
+ - Remove `VerifiedBitmaps` structure… ([`49cf105`](https://github.com/Byron/gitoxide/commit/49cf105329c7f84c314c11e50fdb039984fa94b3))
+ - Release git-date v0.4.1, git-features v0.26.1, git-glob v0.5.2, git-attributes v0.8.1, git-tempfile v3.0.1, git-ref v0.23.1, git-sec v0.6.1, git-config v0.15.1, git-prompt v0.3.1, git-url v0.13.1, git-discover v0.12.1, git-index v0.12.2, git-mailmap v0.9.1, git-pack v0.30.1, git-odb v0.40.1, git-transport v0.25.3, git-protocol v0.26.2, git-revision v0.10.1, git-refspec v0.7.1, git-worktree v0.12.1, git-repository v0.33.0 ([`5b5b380`](https://github.com/Byron/gitoxide/commit/5b5b3809faa71c658db38b40dfc410224d08a367))
+ - Prepare changelogs prior to release ([`93bef97`](https://github.com/Byron/gitoxide/commit/93bef97b3c0c75d4bf7119fdd787516e1efc77bf))
+ - Merge branch 'patch-1' ([`b93f0c4`](https://github.com/Byron/gitoxide/commit/b93f0c49fc677b6c19aea332cbfc1445ce475375))
+ - Thanks clippy ([`9e04685`](https://github.com/Byron/gitoxide/commit/9e04685dd3f109bfb27663f9dc7c04102e660bf2))
+ - Move error handling into its own function ([`2b22d91`](https://github.com/Byron/gitoxide/commit/2b22d91f4b4f3aceab25cd54aa09aadff085d6ee))
+ - Release git-index v0.12.1 ([`8aa5c1d`](https://github.com/Byron/gitoxide/commit/8aa5c1db9e342cc49dfa588d5b4b9f893067dbf7))
+ - Merge branch 'various-improvements' ([`f191c1a`](https://github.com/Byron/gitoxide/commit/f191c1af14e512967ccb788fe997dafc5bb0e111))
+ - Add `impl Debug for State` to print all entries in a digestible form. ([`6d8eb9f`](https://github.com/Byron/gitoxide/commit/6d8eb9f267ac7ef8db0f3a277c8c991df5ce8164))
+ - Add `State::sort_entries_by(...)` to allow comparing by additional criteria. ([`7c8ba2c`](https://github.com/Byron/gitoxide/commit/7c8ba2c945d6313d27569f04b83ebf9a2387e6a2))
+ - Update documentation to make users of `State::dangerously_push_entry()` more aware. ([`b56de39`](https://github.com/Byron/gitoxide/commit/b56de392c632e9137091b7720208c38b162db7e7))
+ - Release git-date v0.4.0, git-actor v0.17.0, git-object v0.26.0, git-traverse v0.22.0, git-index v0.12.0, safety bump 15 crates ([`0e3d0a5`](https://github.com/Byron/gitoxide/commit/0e3d0a56d7e6a60c6578138f2690b4fa54a2072d))
+ - Prepare changelogs prior to release ([`d679f5b`](https://github.com/Byron/gitoxide/commit/d679f5b6f018633e858d3ebbdaf1cd5098bbc5e7))
+ - Merge branch 'various-improvements' ([`9eee8fe`](https://github.com/Byron/gitoxide/commit/9eee8fe1bed116b7a5a4f552982fa49da83ee92c))
+ - `entry::Time` can convert from and to system time. ([`1e3341a`](https://github.com/Byron/gitoxide/commit/1e3341a77c089e6d80c271fca46b774b01b01386))
+ - Add `State::sort_entries()` and `State::dangerously_push_entry()`. ([`654bd8f`](https://github.com/Byron/gitoxide/commit/654bd8f62d5546b0f57e82d1be8211431685c7ce))
+ - Improved error handling - panicing version ([`9509891`](https://github.com/Byron/gitoxide/commit/9509891a63a0e2291818943366be6a085b9dd198))
+ - Add `State::entry_mut_by_path_and_stage()` ([`aa1b6ee`](https://github.com/Byron/gitoxide/commit/aa1b6ee1edeaebae1237184c635f69fdbb23ffee))
+ - `State::write_to()` respects the `REMOVED` flag. ([`3ebe2d4`](https://github.com/Byron/gitoxide/commit/3ebe2d490b2dbdcb6fe0115b06f205f1c4008fff))
+ - Expose `git_hash` as `hash` in the root of the crate. ([`ec36586`](https://github.com/Byron/gitoxide/commit/ec365866c746247b7d5d47b88d51d9cc255724fb))
+ - Add `File::at_or_default(...)` to easily open or create an empty in-memory index. ([`5cc3a15`](https://github.com/Byron/gitoxide/commit/5cc3a15a8085a67701fc1f2d3ba0e55f71d0a4c0))
+ - Add `State::new()` to create a new empty in-memory index. ([`0b40951`](https://github.com/Byron/gitoxide/commit/0b40951f0fb9ee354a83751264ed89d0608111f8))
+ - Remove `File::into_state()` in favor of `From<File> for State`. ([`3753ad5`](https://github.com/Byron/gitoxide/commit/3753ad58a8303fbf325b07757b2b97c34253bca4))
+ - Add `File::set_path()` to allow setting the location of an index file on disk. ([`a7183e2`](https://github.com/Byron/gitoxide/commit/a7183e28513fa1e1a1a784b759677f0e4b4db5f4))
+ - Release git-features v0.26.0, git-actor v0.16.0, git-attributes v0.8.0, git-object v0.25.0, git-ref v0.22.0, git-config v0.14.0, git-command v0.2.1, git-url v0.13.0, git-credentials v0.9.0, git-diff v0.25.0, git-discover v0.11.0, git-traverse v0.21.0, git-index v0.11.0, git-mailmap v0.8.0, git-pack v0.29.0, git-odb v0.39.0, git-transport v0.25.0, git-protocol v0.26.0, git-revision v0.9.0, git-refspec v0.6.0, git-worktree v0.11.0, git-repository v0.31.0, safety bump 24 crates ([`5ac9fbe`](https://github.com/Byron/gitoxide/commit/5ac9fbe265a5b61c533a2a6b3abfed2bdf7f89ad))
+ - Prepare changelogs prior to release ([`30d8ca1`](https://github.com/Byron/gitoxide/commit/30d8ca19284049dcfbb0de2698cafae1d1a16b0c))
+ - Release git-date v0.3.1, git-features v0.25.0, git-actor v0.15.0, git-glob v0.5.1, git-path v0.7.0, git-attributes v0.7.0, git-config-value v0.10.0, git-lock v3.0.1, git-validate v0.7.1, git-object v0.24.0, git-ref v0.21.0, git-sec v0.6.0, git-config v0.13.0, git-prompt v0.3.0, git-url v0.12.0, git-credentials v0.8.0, git-diff v0.24.0, git-discover v0.10.0, git-traverse v0.20.0, git-index v0.10.0, git-mailmap v0.7.0, git-pack v0.28.0, git-odb v0.38.0, git-packetline v0.14.1, git-transport v0.24.0, git-protocol v0.25.0, git-revision v0.8.0, git-refspec v0.5.0, git-worktree v0.10.0, git-repository v0.30.0, safety bump 26 crates ([`e6b9906`](https://github.com/Byron/gitoxide/commit/e6b9906c486b11057936da16ed6e0ec450a0fb83))
+ - Prepare chnagelogs prior to git-repository release ([`7114bbb`](https://github.com/Byron/gitoxide/commit/7114bbb6732aa8571d4ab74f28ed3e26e9fbe4d0))
+ - Merge branch 'main' into read-split-index ([`c57bdde`](https://github.com/Byron/gitoxide/commit/c57bdde6de37eca9672ea715962bbd02aa3eb055))
+ - Assure recursive shared indices (possibly malicious) don't crash us ([`ede718c`](https://github.com/Byron/gitoxide/commit/ede718c24a5a5000bd41f49faabc7f2b19979e15))
+ - Refactor ([`d6ba366`](https://github.com/Byron/gitoxide/commit/d6ba3669f175e869060c97d359918e4901bde989))
+ - Refactor ([`c840c0d`](https://github.com/Byron/gitoxide/commit/c840c0d80aef8b4aadbd119108d2fb4c95a2503b))
+ - Merge branch 'adjustments-for-cargo' ([`083909b`](https://github.com/Byron/gitoxide/commit/083909bc7eb902eeee2002034fdb6ed88280dc5c))
+ - Upload missing fixture archive ([`5cab553`](https://github.com/Byron/gitoxide/commit/5cab5530350c9d2792f3aea6cb8591a0602efee2))
+ - Fix failing test… ([`1057ee4`](https://github.com/Byron/gitoxide/commit/1057ee4309dfd44d9a160b799c0d704eaa8cce64))
+ - Refactor ([`dfd00e5`](https://github.com/Byron/gitoxide/commit/dfd00e52e288e4253527b3bd3559484acc45d3c6))
+ - Test is green ([`800fc2b`](https://github.com/Byron/gitoxide/commit/800fc2b7b6e802dacebf905ed72d5028d807758e))
+ - Sketch `resolve_link_extension` function ([`c62a25b`](https://github.com/Byron/gitoxide/commit/c62a25b9001cf6363314b6f90eab22b17ab7aff1))
+ - Add test that compares split index with regular index ([`fc89517`](https://github.com/Byron/gitoxide/commit/fc89517dffeb51ac56a33fdfe7ff1402136d488a))
+ - Adjust to changes in `git-testtools` ([`4eb842c`](https://github.com/Byron/gitoxide/commit/4eb842c7150b980e1c2637217e1f9657a671cea7))
+ - Merge branch 'adjustments-for-cargo' ([`70ccbb2`](https://github.com/Byron/gitoxide/commit/70ccbb21b1113bdeb20b52d274141a9fdb75f579))
+ - Upgrade atoi from 1 to 2 ([`be6c65c`](https://github.com/Byron/gitoxide/commit/be6c65cb0fb056ae918b28050440946d0c2c9ada))
+ - Release git-hash v0.10.1, git-hashtable v0.1.0 ([`7717170`](https://github.com/Byron/gitoxide/commit/771717095d9a67b0625021eb0928828ab686e772))
+ - Merge branch 'main' into http-config ([`6b9632e`](https://github.com/Byron/gitoxide/commit/6b9632e16c416841ffff1b767ee7a6c89b421220))
+ - Release git-features v0.24.1, git-actor v0.14.1, git-index v0.9.1 ([`7893502`](https://github.com/Byron/gitoxide/commit/789350208efc9d5fc6f9bc4f113f77f9cb445156))
+ - Merge branch 'named-threads' ([`726dd87`](https://github.com/Byron/gitoxide/commit/726dd87b5db45c333ccad898338a1cacea9e3269))
+ - Name spawned threads ([`fd2dd3a`](https://github.com/Byron/gitoxide/commit/fd2dd3a74c8d64407c1c27f29a2914389ded3bd6))
+ - Merge branch 'main' into http-config ([`bcd9654`](https://github.com/Byron/gitoxide/commit/bcd9654e56169799eb706646da6ee1f4ef2021a9))
+ - Make fmt ([`0abab7d`](https://github.com/Byron/gitoxide/commit/0abab7da2ec1b8560e6c1eb009f534c9fc7814fe))
+ - Release git-hash v0.10.0, git-features v0.24.0, git-date v0.3.0, git-actor v0.14.0, git-glob v0.5.0, git-path v0.6.0, git-quote v0.4.0, git-attributes v0.6.0, git-config-value v0.9.0, git-tempfile v3.0.0, git-lock v3.0.0, git-validate v0.7.0, git-object v0.23.0, git-ref v0.20.0, git-sec v0.5.0, git-config v0.12.0, git-command v0.2.0, git-prompt v0.2.0, git-url v0.11.0, git-credentials v0.7.0, git-diff v0.23.0, git-discover v0.9.0, git-bitmap v0.2.0, git-traverse v0.19.0, git-index v0.9.0, git-mailmap v0.6.0, git-chunk v0.4.0, git-pack v0.27.0, git-odb v0.37.0, git-packetline v0.14.0, git-transport v0.23.0, git-protocol v0.24.0, git-revision v0.7.0, git-refspec v0.4.0, git-worktree v0.9.0, git-repository v0.29.0, git-commitgraph v0.11.0, gitoxide-core v0.21.0, gitoxide v0.19.0, safety bump 28 crates ([`b2c301e`](https://github.com/Byron/gitoxide/commit/b2c301ef131ffe1871314e19f387cf10a8d2ac16))
+ - Prepare changelogs prior to release ([`e4648f8`](https://github.com/Byron/gitoxide/commit/e4648f827c97e9d13636d1bbdc83dd63436e6e5c))
+ - Merge branch 'version2021' ([`0e4462d`](https://github.com/Byron/gitoxide/commit/0e4462df7a5166fe85c23a779462cdca8ee013e8))
+ - Upgrade edition to 2021 in most crates. ([`3d8fa8f`](https://github.com/Byron/gitoxide/commit/3d8fa8fef9800b1576beab8a5bc39b821157a5ed))
+ - Release git-glob v0.4.2, git-config-value v0.8.2, git-lock v2.2.0, git-ref v0.19.0, git-config v0.11.0, git-discover v0.8.0, git-index v0.8.0, git-transport v0.22.0, git-protocol v0.23.0, git-worktree v0.8.0, git-repository v0.28.0, gitoxide-core v0.20.0, gitoxide v0.18.0, safety bump 9 crates ([`0c253b1`](https://github.com/Byron/gitoxide/commit/0c253b15143dcedfe4c66d64ab1ea6e097030651))
+ - Prepare changelogs prior to release ([`fe5721f`](https://github.com/Byron/gitoxide/commit/fe5721f888c64c79fe9a734a9e33b94a282f8d97))
+ - Merge branch 'main' into http-config ([`7c5b37d`](https://github.com/Byron/gitoxide/commit/7c5b37d28e98f59a6847368a0d0166d2dbb4acc1))
+ - Release git-diff v0.22.0, git-index v0.7.1, git-pack v0.26.0, git-odb v0.36.0, git-transport v0.21.2, git-repository v0.27.0, safety bump 6 crates ([`f0cab31`](https://github.com/Byron/gitoxide/commit/f0cab317bb0c2799fa80d16f3ae1b89d6aee4284))
+ - Prepare changelogs prior to release ([`f5f3a9e`](https://github.com/Byron/gitoxide/commit/f5f3a9edd038a89c8c6c4da02054e5439bcc0071))
+ - Merge branch 'fixes-for-crates-index-diff' ([`255be4d`](https://github.com/Byron/gitoxide/commit/255be4ddcd6cbca0a89f286eeecdd19ff70e000f))
+ - Remove unused import; fix docs ([`efe0a51`](https://github.com/Byron/gitoxide/commit/efe0a51931fc7e42c82563575e3068dd6e401409))
+ - Release git-features v0.23.1, git-glob v0.4.1, git-config-value v0.8.1, git-tempfile v2.0.6, git-object v0.22.1, git-ref v0.18.0, git-sec v0.4.2, git-config v0.10.0, git-prompt v0.1.1, git-url v0.10.1, git-credentials v0.6.1, git-diff v0.21.0, git-discover v0.7.0, git-index v0.7.0, git-pack v0.25.0, git-odb v0.35.0, git-transport v0.21.1, git-protocol v0.22.0, git-refspec v0.3.1, git-worktree v0.7.0, git-repository v0.26.0, git-commitgraph v0.10.0, gitoxide-core v0.19.0, gitoxide v0.17.0, safety bump 9 crates ([`d071583`](https://github.com/Byron/gitoxide/commit/d071583c5576fdf5f7717765ffed5681792aa81f))
+ - Prepare changelogs prior to release ([`423af90`](https://github.com/Byron/gitoxide/commit/423af90c8202d62dc1ea4a76a0df6421d1f0aa06))
+ - Merge branch 'write-sparse-index' ([`ba17db0`](https://github.com/Byron/gitoxide/commit/ba17db03e4e832db724ab3e08e3df05eb61dd401))
+ - Thanks clippy ([`49b539b`](https://github.com/Byron/gitoxide/commit/49b539baf1be88961a9e2934ee714090f94ac57f))
+ - Remove tests and scaffolding code that probably won't be implemented soon. ([`177d1c8`](https://github.com/Byron/gitoxide/commit/177d1c8be2b73ab0e7534d8ba9a47c451e02cfbb))
+ - Refactor ([`0a74625`](https://github.com/Byron/gitoxide/commit/0a7462568c65057fb92b3824d0a73218c5184b2a))
+ - Act like git and write a sparse index even if it contains no dir entries anymore. ([`53af48c`](https://github.com/Byron/gitoxide/commit/53af48cff26542b4acf1510862f7ac0e94b24b2b))
+ - Bake knowledge about sparse related config parameters into types. ([`e61957e`](https://github.com/Byron/gitoxide/commit/e61957e16eefe61d222997c69c1ae4c8ea0a8b5f))
+ - Merge branch 'main' into write-sparse-index (upgrade to Rust 1.65) ([`5406630`](https://github.com/Byron/gitoxide/commit/5406630466145990b5adbdadb59151036993060d))
+ - Thanks clippy ([`04cfa63`](https://github.com/Byron/gitoxide/commit/04cfa635a65ae34ad6d22391f2febd2ca7eabca9))
+ - Merge branch 'main' into write-sparse-index ([`c4e6849`](https://github.com/Byron/gitoxide/commit/c4e68496c368611ebe17c6693d06c8147c28c717))
+ - Make fmt ([`ea2136b`](https://github.com/Byron/gitoxide/commit/ea2136b065979cecb3a1fdbf7b20ed7514128d9a))
+ - Merge branch 'gix-clone' ([`def53b3`](https://github.com/Byron/gitoxide/commit/def53b36c3dec26fa78939ab0584fe4ff930909c))
+ - Add and use `checked_is_sparse()` instead of cached `is_sparse` flag ([`e41ad0f`](https://github.com/Byron/gitoxide/commit/e41ad0fe585699b3d6cf3b3106567073e0a5ed5d))
+ - Refactor ([`3683963`](https://github.com/Byron/gitoxide/commit/36839630f1471bd73a13276652f3a6ddd1286faa))
+ - Thanks clippy ([`646b868`](https://github.com/Byron/gitoxide/commit/646b86802a669469b8cdfc228594a373a41e0f37))
+ - Added fixture, adjusted tests, refactor ([`3173c0b`](https://github.com/Byron/gitoxide/commit/3173c0b2f79fbba7d73e391cc5667ca35a56a3a1))
+ - Make clear in code that mandatory extensions will always be written… ([`3e37443`](https://github.com/Byron/gitoxide/commit/3e3744301c3a80f98751551f779c3105262b3fec))
+ - Respect the current 'is_sparse()` state when writing. ([`2012b27`](https://github.com/Byron/gitoxide/commit/2012b27246e8835b19725862409d2df23a2638c6))
+ - Refactor ([`a929bcf`](https://github.com/Byron/gitoxide/commit/a929bcf4ff5a2e383218cce6b12776e40c553b83))
+ - Thanks clippy ([`5bfd947`](https://github.com/Byron/gitoxide/commit/5bfd94711568174afe3a344514745dcd6a4992a4))
+ - Sketch out how a write implementation could work ([`4a6d46f`](https://github.com/Byron/gitoxide/commit/4a6d46f3ab3d15eb851c92f7e49eb6772bc4023b))
+ - Regenerated archive ([`cd1c752`](https://github.com/Byron/gitoxide/commit/cd1c752fde943804689039684b60ae4ddffee3f1))
+ - Updated docs ([`77a9d42`](https://github.com/Byron/gitoxide/commit/77a9d42ec4f5b5f0b4fcbb31fdf2e5eb57bb578b))
+ - Added first tests and implementation for writing the `sdir` extension ([`66a675f`](https://github.com/Byron/gitoxide/commit/66a675f68e46e6eaf7464912d2fb8af976c18565))
+ - Capability to write `sdir`extension ([`762e4cb`](https://github.com/Byron/gitoxide/commit/762e4cb2a55728f6b82a97164c4ac4b59035d2e8))
+ - Added tests for reading sparse indexes ([`ddaa003`](https://github.com/Byron/gitoxide/commit/ddaa003246fce16578b455077d98519ac05c6dae))
+ - Add temporary sparse index playground testfile ([`5589a7f`](https://github.com/Byron/gitoxide/commit/5589a7fb4df6650214b7210bd89257ecaf9cabd0))
+ - Add sparse index text fixtures ([`8a8a53e`](https://github.com/Byron/gitoxide/commit/8a8a53e8432af7a96fd7eff9af0bd241c7b3facd))
+ - Add `is_sparse` access method for `State` ([`7f012cf`](https://github.com/Byron/gitoxide/commit/7f012cf5f85634bd520065ecb39bb1bd19a987fa))
+ - Merge branch 'main' into gix-clone ([`de4fe06`](https://github.com/Byron/gitoxide/commit/de4fe06202906ea5c62e667826b42cf7b57b1ff0))
+ - Merge branch 'fix-gix-index-from-tree' ([`da5f63c`](https://github.com/Byron/gitoxide/commit/da5f63cbc7506990f46d310f8064678decb86928))
+ - `write::Options::object_hash` is now implied by the `State` itself. ([`59f6791`](https://github.com/Byron/gitoxide/commit/59f679126aba6f8a432aeb53f0bbd5d136ec1deb))
+ - `decode::Options::object_hash` is now a parameter to methods. ([`908163a`](https://github.com/Byron/gitoxide/commit/908163ab2f86a1b603e69f04cd857fbf52e5abfb))
+ - `decode::Options::default_from_object_hash()` ([`9e03110`](https://github.com/Byron/gitoxide/commit/9e03110578bd93da3f1a91c5bcd9fde942c81ac4))
+ - Refactor ([`6fb3255`](https://github.com/Byron/gitoxide/commit/6fb3255fb94758c025ed9edd971bdde54f409e77))
+ - Seal `File` members to preserve consistency better. ([`92dda50`](https://github.com/Byron/gitoxide/commit/92dda50e2d9c584b0e110026f59fb715ec41600a))
+ - `decode::Options::default()` - remove `Default` impl. ([`bd312ac`](https://github.com/Byron/gitoxide/commit/bd312acf5ceba28edf2508aef6011c037eb0a377))
+ - Fix tests ([`fc5cee1`](https://github.com/Byron/gitoxide/commit/fc5cee1d4f757b634856b9d91df1b4455a63f860))
+ - Assure we also write V3 files, validate auto-version discovery ([`abc3cf8`](https://github.com/Byron/gitoxide/commit/abc3cf894f18f1a3c04de7967748add15b2e040e))
+ - Loose fixtures are usable more easily now ([`b86012b`](https://github.com/Byron/gitoxide/commit/b86012bd5407e67159c2cc86ce97525d92189284))
+ - Remove `write::Options::default()`. ([`2da5a62`](https://github.com/Byron/gitoxide/commit/2da5a62432350ede6b816254c894863d14aa4ba1))
+ - `File::write()` for secure and complete writing of index files. ([`eedcffa`](https://github.com/Byron/gitoxide/commit/eedcffa728c7e895da51d5298db28f3fef05f7da))
+ - Prepare test for writing a complete index file from arbitrary state ([`281f5b8`](https://github.com/Byron/gitoxide/commit/281f5b828a2dbabd751e214b420e37d1d1e3a028))
+ - Merge branch 'gix-index-from-tree' ([`8c24386`](https://github.com/Byron/gitoxide/commit/8c24386f1874cd94f78fefbe434963f772878b1f))
+ - Refactor ([`08d5c0b`](https://github.com/Byron/gitoxide/commit/08d5c0b051572b7e7b51eb7bd7dd804b1fa6a1ab))
+ - Release git-hash v0.9.11, git-features v0.23.0, git-actor v0.13.0, git-attributes v0.5.0, git-object v0.22.0, git-ref v0.17.0, git-sec v0.4.1, git-config v0.9.0, git-url v0.10.0, git-credentials v0.6.0, git-diff v0.20.0, git-discover v0.6.0, git-traverse v0.18.0, git-index v0.6.0, git-mailmap v0.5.0, git-pack v0.24.0, git-odb v0.34.0, git-packetline v0.13.1, git-transport v0.21.0, git-protocol v0.21.0, git-revision v0.6.0, git-refspec v0.3.0, git-worktree v0.6.0, git-repository v0.25.0, safety bump 24 crates ([`104d922`](https://github.com/Byron/gitoxide/commit/104d922add61ab21c534c24ce8ed37cddf3e275a))
+ - Prepare changelogs for release ([`d232567`](https://github.com/Byron/gitoxide/commit/d23256701a95284857dc8d1cb37c7c94cada973c))
+ - Remove the .insert() call… ([`4bb3e8b`](https://github.com/Byron/gitoxide/commit/4bb3e8bd50958ddbfdee72247025a80a2ca850a8))
+ - Merge branch 'main' into fetch-pack ([`d686020`](https://github.com/Byron/gitoxide/commit/d6860205db847b8a474756e92578195e1022481c))
+ - Thanks clippy ([`b9937ad`](https://github.com/Byron/gitoxide/commit/b9937adc2c31095dde63397be7d56f1ea559b0f7))
+ - Merge branch 'fix-git-features' ([`82fd251`](https://github.com/Byron/gitoxide/commit/82fd251ac80d07bc9da8a4d36e517aa35580d188))
+ - Merge branch 'diff' ([`25a7726`](https://github.com/Byron/gitoxide/commit/25a7726377fbe400ea3c4927d04e9dec99802b7b))
+ - Release git-hash v0.9.10, git-features v0.22.5, git-date v0.2.0, git-actor v0.12.0, git-glob v0.4.0, git-path v0.5.0, git-quote v0.3.0, git-attributes v0.4.0, git-config-value v0.8.0, git-tempfile v2.0.5, git-validate v0.6.0, git-object v0.21.0, git-ref v0.16.0, git-sec v0.4.0, git-config v0.8.0, git-discover v0.5.0, git-traverse v0.17.0, git-index v0.5.0, git-worktree v0.5.0, git-testtools v0.9.0, git-command v0.1.0, git-prompt v0.1.0, git-url v0.9.0, git-credentials v0.5.0, git-diff v0.19.0, git-mailmap v0.4.0, git-chunk v0.3.2, git-pack v0.23.0, git-odb v0.33.0, git-packetline v0.13.0, git-transport v0.20.0, git-protocol v0.20.0, git-revision v0.5.0, git-refspec v0.2.0, git-repository v0.24.0, git-commitgraph v0.9.0, gitoxide-core v0.18.0, gitoxide v0.16.0, safety bump 28 crates ([`29a043b`](https://github.com/Byron/gitoxide/commit/29a043be6808a3e9199a9b26bd076fe843afe4f4))
+ - Make fmt ([`429cccc`](https://github.com/Byron/gitoxide/commit/429cccc5831c25a7205a12dc7a0443ac48616e2c))
+ - Merge branch 'filter-refs' ([`fd14489`](https://github.com/Byron/gitoxide/commit/fd14489f729172d615d0fa1e8dbd605e9eacf69d))
+ - Release git-features v0.22.6 ([`c9eda72`](https://github.com/Byron/gitoxide/commit/c9eda729d8f8bc266c7516c613d38acfb83a4743))
+ - Fix docs ([`87f6db7`](https://github.com/Byron/gitoxide/commit/87f6db7a7dc1561d06747135be206f700b75257c))
+ - Merge branch 'index-from-tree' ([`172f73c`](https://github.com/Byron/gitoxide/commit/172f73cf26878d153d51790fa01853fa4ba6beb7))
+ - Refactor ([`c40528e`](https://github.com/Byron/gitoxide/commit/c40528e86353fefe317d2b1ad33ff1236e589523))
+ - Refactor ([`b2835cc`](https://github.com/Byron/gitoxide/commit/b2835cc28e10907eb375b2beb400cf408fa5a3e0))
+ - Remove depthfirst traversal todo ([`5ca7945`](https://github.com/Byron/gitoxide/commit/5ca79458b11e0ead0027c64eecbc259b95f35ed5))
+ - Add test fixture and adjust ([`e153340`](https://github.com/Byron/gitoxide/commit/e153340f52bc13a980f347b215bc1337417bbbb4))
+ - Overwrite duplicate entries (like 'git')… ([`16d8944`](https://github.com/Byron/gitoxide/commit/16d8944b492f9f70a2a29403f2ac1e1a3e50f450))
+ - Refactor ([`49dc4a6`](https://github.com/Byron/gitoxide/commit/49dc4a6967912df68bc4394f331b3a5242cf52e9))
+ - Refactor ([`c2524a6`](https://github.com/Byron/gitoxide/commit/c2524a635627449f5bcdd51b37a1dcd55ee0c193))
+ - Refactor ([`6683081`](https://github.com/Byron/gitoxide/commit/668308139e5981094ed2b059a97f1c1245b04dc1))
+ - Compare individual entries more thoroughly ([`1c9b703`](https://github.com/Byron/gitoxide/commit/1c9b703db10aa01e127f2a0eafe24f2aa1fefee7))
+ - Thanks clippy ([`878593e`](https://github.com/Byron/gitoxide/commit/878593e4d0a5e74df267ac7d0bdaf827b7588043))
+ - Refactor... ([`dce45e6`](https://github.com/Byron/gitoxide/commit/dce45e6ffdfe0031349bfff006e5e7140c2f515c))
+ - :init module ([`6c17f96`](https://github.com/Byron/gitoxide/commit/6c17f96fcee9e2935b464c8ffbd30b253d9f5a6c))
+ - Refactor `Entry::cmp` ([`3a58c3e`](https://github.com/Byron/gitoxide/commit/3a58c3eb0b2802cd9acf05d8104a1b3a1dbc09bd))
+ - Make fmt ([`535e967`](https://github.com/Byron/gitoxide/commit/535e967666c6da657ff1b7eff7c64ab27cafb182))
+ - Merge branch 'main' into filter-refs-by-spec ([`9aa1d3d`](https://github.com/Byron/gitoxide/commit/9aa1d3dc46d4b1c76af257f573aff3aeef2d3fa8))
+ - Release git-features v0.22.4, git-url v0.8.0, safety bump 4 crates ([`1d4600a`](https://github.com/Byron/gitoxide/commit/1d4600ae51475c2e225f96c16c41e2c4a2b3f2aa))
+ - Merge branch 'main' into filter-refs-by-spec ([`1f6e5ab`](https://github.com/Byron/gitoxide/commit/1f6e5ab15f5fd8d23719b13e6aea59cd231ac0fe))
+ - Merge branch 'fix-522' ([`5869e9f`](https://github.com/Byron/gitoxide/commit/5869e9ff2508d5a93c07635277af8764fcb57713))
+ - Release git-hash v0.9.9 ([`da0716f`](https://github.com/Byron/gitoxide/commit/da0716f8c27b4f29cfff0e5ce7fcb3d7240f4aeb))
+ - Refactor ([`bba180d`](https://github.com/Byron/gitoxide/commit/bba180d78c182c873cc968bfd40186876dfde671))
+ - Merge branch 'main' into index-from-tree ([`bc64b96`](https://github.com/Byron/gitoxide/commit/bc64b96a2ec781c72d1d4daad38aa7fb8b74f99b))
+ - Added more fixtures to test ([`adf5e54`](https://github.com/Byron/gitoxide/commit/adf5e5422a871ea435bb0ec320744b63f53d3159))
+ - Initial test and implementation for State::from_tree ([`14694a4`](https://github.com/Byron/gitoxide/commit/14694a4aeff7f05818aa7851e0e2fa56e911322c))
+ - Merge branch 'main' into filter-refs-by-spec ([`cef0b51`](https://github.com/Byron/gitoxide/commit/cef0b51ade2a3301fa09ede7a425aa1fe3527e78))
+ - Release git-attributes v0.3.3, git-ref v0.15.3, git-index v0.4.3, git-worktree v0.4.3, git-testtools v0.8.0 ([`baad4ce`](https://github.com/Byron/gitoxide/commit/baad4ce51fe0e8c0c1de1b08148d8303878ca37b))
+ - Prepare changelogs prior to release of git-testtools ([`7668e38`](https://github.com/Byron/gitoxide/commit/7668e38fab8891ed7e73fae3a6f5a8772e0f0d0b))
+ - Release git-features v0.22.3, git-revision v0.4.4 ([`c2660e2`](https://github.com/Byron/gitoxide/commit/c2660e2503323531ba02519eaa51124ee22fec51))
+ - Merge branch 'main' into filter-refs-by-spec ([`cfa1440`](https://github.com/Byron/gitoxide/commit/cfa144031dbcac2707ab0cec012bc35e78f9c475))
+ - Release git-date v0.0.5, git-hash v0.9.8, git-features v0.22.2, git-actor v0.11.3, git-glob v0.3.2, git-quote v0.2.1, git-attributes v0.3.2, git-tempfile v2.0.4, git-lock v2.1.1, git-validate v0.5.5, git-object v0.20.2, git-ref v0.15.2, git-sec v0.3.1, git-config v0.7.0, git-credentials v0.4.0, git-diff v0.17.2, git-discover v0.4.1, git-bitmap v0.1.2, git-index v0.4.2, git-mailmap v0.3.2, git-chunk v0.3.1, git-traverse v0.16.2, git-pack v0.21.2, git-odb v0.31.2, git-packetline v0.12.7, git-url v0.7.2, git-transport v0.19.2, git-protocol v0.19.0, git-revision v0.4.2, git-refspec v0.1.0, git-worktree v0.4.2, git-repository v0.22.0, safety bump 4 crates ([`4974eca`](https://github.com/Byron/gitoxide/commit/4974eca96d525d1ee4f8cad79bb713af7a18bf9d))
+ - Merge branch 'main' into remote-ls-refs ([`e2ee3de`](https://github.com/Byron/gitoxide/commit/e2ee3ded97e5c449933712883535b30d151c7c78))
+ - Merge branch 'docsrs-show-features' ([`31c2351`](https://github.com/Byron/gitoxide/commit/31c235140cad212d16a56195763fbddd971d87ce))
+ - Use docsrs feature in code to show what is feature-gated automatically on docs.rs ([`b1c40b0`](https://github.com/Byron/gitoxide/commit/b1c40b0364ef092cd52d03b34f491b254816b18d))
+ - Pass --cfg docsrs when compiling for https://docs.rs ([`5176771`](https://github.com/Byron/gitoxide/commit/517677147f1c17304c62cf97a1dd09f232ebf5db))
+ - Remove default link to cargo doc everywhere ([`533e887`](https://github.com/Byron/gitoxide/commit/533e887e80c5f7ede8392884562e1c5ba56fb9a8))
+ - Merge branch 'main' into remote-ls-refs ([`bd5f3e8`](https://github.com/Byron/gitoxide/commit/bd5f3e8db7e0bb4abfb7b0f79f585ab82c3a14ab))
+ - Release git-date v0.0.3, git-actor v0.11.1, git-attributes v0.3.1, git-tempfile v2.0.3, git-object v0.20.1, git-ref v0.15.1, git-config v0.6.1, git-diff v0.17.1, git-discover v0.4.0, git-bitmap v0.1.1, git-index v0.4.1, git-mailmap v0.3.1, git-traverse v0.16.1, git-pack v0.21.1, git-odb v0.31.1, git-packetline v0.12.6, git-url v0.7.1, git-transport v0.19.1, git-protocol v0.18.1, git-revision v0.4.0, git-worktree v0.4.1, git-repository v0.21.0, safety bump 5 crates ([`c96473d`](https://github.com/Byron/gitoxide/commit/c96473dce21c3464aacbc0a62d520c1a33172611))
+ - Prepare changelogs prior to reelase ([`c06ae1c`](https://github.com/Byron/gitoxide/commit/c06ae1c606b6af9c2a12021103d99c2810750d60))
+ - Release git-hash v0.9.7, git-features v0.22.1 ([`232784a`](https://github.com/Byron/gitoxide/commit/232784a59ded3e8016e4257c7e146ad385cdd64a))
+ - Merge branch 'main' into remote-ls-refs ([`c4bf958`](https://github.com/Byron/gitoxide/commit/c4bf9585d815bc342e5fb383336cc654280dd34f))
+ - Fix CI for good ([`e0c0b8c`](https://github.com/Byron/gitoxide/commit/e0c0b8c7c1898b2bc11a915e8e4fb8426295ccbb))
+ - Fix CI ([`2433be1`](https://github.com/Byron/gitoxide/commit/2433be173c2145198f7891dc7a1f7c4acf215b11))
+ - Merge branch 'index-write-refactor' ([`805f432`](https://github.com/Byron/gitoxide/commit/805f432bf8e9d2dd9ede56caf959de386d5d80c7))
+ - Refactor ([`3af5121`](https://github.com/Byron/gitoxide/commit/3af5121330ae96aec32d0360c8b2e24a8860a2e8))
+ - Refactor ([`b41d93a`](https://github.com/Byron/gitoxide/commit/b41d93ac604b9807c24d93c6849f852489f512c0))
+ - Thanks clippy ([`4390c32`](https://github.com/Byron/gitoxide/commit/4390c32f9ea0683561a78349456c87329fef3b41))
+ - Run tests against all input files we have ([`de8abe6`](https://github.com/Byron/gitoxide/commit/de8abe6923b01563db812ba007ea65b7f193082d))
+ - Combine more tests into one to reduce duplication ([`933ad9e`](https://github.com/Byron/gitoxide/commit/933ad9e8ff0d58ad2590907cf84b43bc424e3219))
+ - Assure that extended flags receive version 3; make `version` an implementation detail ([`6d810a1`](https://github.com/Byron/gitoxide/commit/6d810a135eeb71b8b04f7d9cb6c5f115587c2a63))
+ - Support for extended flags, and V3 as it's a requirements. ([`417d90e`](https://github.com/Byron/gitoxide/commit/417d90eb267dd74a5372f1c7d8feb7cb4e98d2a1))
+ - Refcator ([`27993c0`](https://github.com/Byron/gitoxide/commit/27993c01a1533d561629635336c5cedf53dd0efc))
+ - Fix tree ext reading and writing; round-trip with long path works now ([`f93febe`](https://github.com/Byron/gitoxide/commit/f93febe2d2c55938ac8f698b57144583caab54ef))
+ - First PoC for writing long paths, even though it doens't produce the entire file yet ([`581cbd7`](https://github.com/Byron/gitoxide/commit/581cbd7afeac0f7654611c83deacae802ef5da6f))
+ - Make it more explicit to write all available extensions by default ([`fbe9815`](https://github.com/Byron/gitoxide/commit/fbe981519446e55c4020e95841e7bff7e54e358e))
+ - Fix docs ([`9861a6c`](https://github.com/Byron/gitoxide/commit/9861a6ce8abc438a1e0739aa6d55ced450a4465b))
+ - Thanks clippy ([`834be93`](https://github.com/Byron/gitoxide/commit/834be93e6db84bb9160dd4677b7e9d63213c23cd))
+ - Thanks clippy ([`9b3a940`](https://github.com/Byron/gitoxide/commit/9b3a940d9f4694912f32cb86752f3f7507882010))
+ - Generalize extension writing so that writing more will be easier ([`8ef5378`](https://github.com/Byron/gitoxide/commit/8ef5378dfaefe2d562d16b861fb4bb0fa4fdfe93))
+ - Generalize EOIE exstension writing ([`18b722e`](https://github.com/Byron/gitoxide/commit/18b722e06bfb8bbbf0ada7438266e31a4317f2d4))
+ - Provide a stand-alone way of writing end-of-index extensions ([`7ca297a`](https://github.com/Byron/gitoxide/commit/7ca297af400e50d42cffcaab54b1684f6810eb4f))
+ - Refactor ([`a5b2ef9`](https://github.com/Byron/gitoxide/commit/a5b2ef9a33720312a6b30b7cdae564bf759b0218))
+ - Additional validation ([`ee7b5bb`](https://github.com/Byron/gitoxide/commit/ee7b5bba09bc20e1531cb733b5b2aac8232e7674))
+ - Refactor ([`e35aac6`](https://github.com/Byron/gitoxide/commit/e35aac66079464a9494744c201355ab2faa0a2b3))
+ - Refactor ([`52386f4`](https://github.com/Byron/gitoxide/commit/52386f4a8ee11b0d2858412b4b5ec4b73544ba30))
+ - Refactor ([`75a2338`](https://github.com/Byron/gitoxide/commit/75a2338fe9cfac478164b7b575c0f3c2b910111d))
+ - Refactor ([`f6f2861`](https://github.com/Byron/gitoxide/commit/f6f2861f57be8ad4c795c90ae6fc7e568aeb12da))
+ - Refactor ([`6cf9277`](https://github.com/Byron/gitoxide/commit/6cf92776b0349bf735c28b6275fdf551ce236d4d))
+ - Refactor ([`a6354c0`](https://github.com/Byron/gitoxide/commit/a6354c076b2967ff31feb30e7b73f0a6eb92a459))
+ - Fill in all remaining documentation, raise `git-index` to 'usable' state ([`3568ae3`](https://github.com/Byron/gitoxide/commit/3568ae3a1ed7c2d0c9b7e1dc690b055b4f43bdd2))
+ - First step towards everything being documented ([`919923c`](https://github.com/Byron/gitoxide/commit/919923c08b641ca148c2f25d193d65bb068cc787))
+ - Remove quickerror in favor of thiserror ([`dd7ce3f`](https://github.com/Byron/gitoxide/commit/dd7ce3f77c868f81196103b021957ace54ca2b9c))
+ - Refactor ([`618736b`](https://github.com/Byron/gitoxide/commit/618736b614330d9576e58c5bb9b3696de3f76d84))
+ - Refactor ([`4dda27e`](https://github.com/Byron/gitoxide/commit/4dda27e716927a506e16da9d6cd50547de1fc84e))
+ - Added test for eoie extension ([`a433c0d`](https://github.com/Byron/gitoxide/commit/a433c0d7feebebdf17e9d362894a6fa38221b402))
+ - Estimate vector size for tree entries ([`74455e6`](https://github.com/Byron/gitoxide/commit/74455e65e3ef48ce55fa40b7d9ef050a8e0e9b84))
+ - Implemented File::write_to for hashed write ([`6b6db34`](https://github.com/Byron/gitoxide/commit/6b6db340b351836f055d8ab74ed3d3f370cca7be))
+ - Refactor and add more tests ([`6b32bcf`](https://github.com/Byron/gitoxide/commit/6b32bcfde1af3ada67a6fa0448611d0ecca2f605))
+ - Merge branch 'write-index-files' into write-index-v2 ([`cddc2ca`](https://github.com/Byron/gitoxide/commit/cddc2ca06f63f66e887ff821452d1f56fb08fe6a))
+ - Thanks clippy ([`a66403c`](https://github.com/Byron/gitoxide/commit/a66403c8e14716023455d606e1c63787ac40f4f4))
+ - Write wrapper to count written bytes ([`b147090`](https://github.com/Byron/gitoxide/commit/b147090bafd22da07f475a167bb921f3e0fa0017))
+ - Refactor test ([`33a3009`](https://github.com/Byron/gitoxide/commit/33a3009c4d851fae9156cb3bdb664c05118ef442))
+ - Merge branch 'rev-parse-delegate' ([`2f506c7`](https://github.com/Byron/gitoxide/commit/2f506c7c2988477b0f97d272a9ac9ed47b236457))
+ - Refactor... ([`81eef35`](https://github.com/Byron/gitoxide/commit/81eef353f8f2add26720e3dd3981ce1b790f996c))
+ - Refactor tests... ([`3a9b51b`](https://github.com/Byron/gitoxide/commit/3a9b51b0fe812d454378c8ac887bba11740a81ee))
+ - Convert 'in-memory' flags to 'storage' flags ([`017377d`](https://github.com/Byron/gitoxide/commit/017377d8c49c236bb3ab240794dbd7a714efb7e1))
+ - Merge branch 'write-index-files' into rev-parse-delegate ([`370110d`](https://github.com/Byron/gitoxide/commit/370110d3356528af38150c2280ed505354ceca5b))
+ - Small improvements ([`e5cb6d9`](https://github.com/Byron/gitoxide/commit/e5cb6d94ef31e007847a6072ead8962b16eba105))
+ - Fix pathname in test ([`1f18e19`](https://github.com/Byron/gitoxide/commit/1f18e19fd3c07b540d56c86afa4cb708ad1126ac))
+ - Thanks clippy ([`3f72180`](https://github.com/Byron/gitoxide/commit/3f7218044fdc9d24693c04e0c1c97069c9a3f698))
+ - Sucesfully writing the first basic index files ([`a9c6f22`](https://github.com/Byron/gitoxide/commit/a9c6f2260e96928d678b29a765c26e88f0ff5908))
+ - Merge pull request #2 from SidneyDouw/main ([`ce885ad`](https://github.com/Byron/gitoxide/commit/ce885ad4c3324c09c83751c32e014f246c748766))
+ - Merge branch 'Byron:main' into main ([`9b9ea02`](https://github.com/Byron/gitoxide/commit/9b9ea0275f8ff5862f24cf5a4ca53bb1cd610709))
+ - Merge branch 'main' into rev-parse-delegate ([`6da8250`](https://github.com/Byron/gitoxide/commit/6da82507588d3bc849217c11d9a1d398b67f2ed6))
+ - Merge branch 'main' into pathspec ([`7b61506`](https://github.com/Byron/gitoxide/commit/7b615060712565f515515e35a3e8346278ad770c))
+ - 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))
+ - Setup and refactor tests ([`7eed237`](https://github.com/Byron/gitoxide/commit/7eed2375a3f076e6fdf9dde4673733e85d5612aa))
+ - Generate index header ([`f1d7c1c`](https://github.com/Byron/gitoxide/commit/f1d7c1c137c712ecca76b8e69b11481bf0f1a860))
+ - Merge pull request #1 from Byron/main ([`085e76b`](https://github.com/Byron/gitoxide/commit/085e76b121291ed9bd324139105d2bd4117bedf8))
+ - Merge branch 'main' into SidneyDouw-pathspec ([`a22b1d8`](https://github.com/Byron/gitoxide/commit/a22b1d88a21311d44509018729c3ef1936cf052a))
+ - Merge branch 'main' into git_includeif ([`598c853`](https://github.com/Byron/gitoxide/commit/598c853087fcf8f77299aa5b9803bcec705c0cd0))
+ - 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 'main' into git_includeif ([`b1bfc8f`](https://github.com/Byron/gitoxide/commit/b1bfc8fe8efb6d8941f54dddd0fcad99aa13ed6c))
+ - Merge branch 'basic-worktree-support' ([`e058bda`](https://github.com/Byron/gitoxide/commit/e058bdabf8449b6a6fdff851e3929137d9b71568))
+ - Merge branch 'main' into git_includeif ([`05eb340`](https://github.com/Byron/gitoxide/commit/05eb34023933918c51c03cf2afd774db89cc5a33))
+ - Merge branch 'main' into msrv-for-windows ([`7cb1972`](https://github.com/Byron/gitoxide/commit/7cb19729133325bdfacedf44cdc0500cbcf36684))
+ - Merge branch 'worktree-stack' ([`98da8ba`](https://github.com/Byron/gitoxide/commit/98da8ba52cef8ec27f705fcbc84773e5bacc4e10))
+ - Merge branch 'worktree-stack' ([`39046e9`](https://github.com/Byron/gitoxide/commit/39046e98098da7d490757477986479126a45b3e5))
+ - Merge branch 'main' into repo-status ([`0eb2372`](https://github.com/Byron/gitoxide/commit/0eb23721dca78f6e6bf864c5c3a3e44df8b419f0))
+ - Merge branch 'test-archive-support' ([`350df01`](https://github.com/Byron/gitoxide/commit/350df01042d6ca8b93f8737fa101e69b50535a0f))
+ - 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))
+ - 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 'unify-path-encoding' ([`566ff8a`](https://github.com/Byron/gitoxide/commit/566ff8a3597b889899d41ca15e5b9af7e05f1a4b))
+ - 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))
+ - Merge branch 'index-verification' ([`ad3c803`](https://github.com/Byron/gitoxide/commit/ad3c8032cee02052ef3940d1d7c950270a0a299a))
+ - 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))
+ - 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 ([`09df2bc`](https://github.com/Byron/gitoxide/commit/09df2bcb4b45f72742d139530907be8aa4dc36f8))
+ - Thanks clippy ([`93c3d23`](https://github.com/Byron/gitoxide/commit/93c3d23d255a02d65b5404c2f62f96d94e36f33d))
+ - 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))
+ - Thanks clippy ([`591511a`](https://github.com/Byron/gitoxide/commit/591511a739f91c5e8ff4243059ac98052a44c914))
+ - Remove dash in all repository links ([`98c1360`](https://github.com/Byron/gitoxide/commit/98c1360ba4d2fb3443602b7da8775906224feb1d))
+ - Merge from main. ([`b59bd5e`](https://github.com/Byron/gitoxide/commit/b59bd5e0b0895c7d1d585816cec8be4dea78c278))
+ - Refactor ([`e4bcfe6`](https://github.com/Byron/gitoxide/commit/e4bcfe6406b14feffa63598c7cdcc8ecc73222bd))
+ - Add placeholder for git-index crate ([`52ff13c`](https://github.com/Byron/gitoxide/commit/52ff13cf260b53423faf59e5c666ff1565bde947))
+</details>
+
+## 0.12.2 (2023-01-10)
+
+A maintenance release without user-facing changes.
+
+## 0.12.1 (2023-01-08)
+
+### New Features
+
+ - <csr-id-6d8eb9f267ac7ef8db0f3a277c8c991df5ce8164/> add `impl Debug for State` to print all entries in a digestible form.
+ - <csr-id-7c8ba2c945d6313d27569f04b83ebf9a2387e6a2/> add `State::sort_entries_by(...)` to allow comparing by additional criteria.
+ This allows to organize entries based on flags, for example, which may help
+ in special occasions.
+
+## 0.12.0 (2023-01-06)
+
+### New Features
+
+ - <csr-id-1e3341a77c089e6d80c271fca46b774b01b01386/> `entry::Time` can convert from and to system time.
+ - <csr-id-654bd8f62d5546b0f57e82d1be8211431685c7ce/> add `State::sort_entries()` and `State::dangerously_push_entry()`.
+ Both methods work in tandem as one breaks invariants, but allows to quickly
+ add entries, while the other restores them.
+ - <csr-id-aa1b6ee1edeaebae1237184c635f69fdbb23ffee/> add `State::entry_mut_by_path_and_stage()`
+ - <csr-id-3ebe2d490b2dbdcb6fe0115b06f205f1c4008fff/> `State::write_to()` respects the `REMOVED` flag.
+ That way, one can mark entries for removal and these will be pruned
+ at write time. This is preferable over performing removals expensively
+ in memory.
+ - <csr-id-ec365866c746247b7d5d47b88d51d9cc255724fb/> expose `gix_hash` as `hash` in the root of the crate.
+ This makes it easier to use the crate standalone as plumbing as instantiation
+ requires access to `gix_hash`.
+ - <csr-id-5cc3a15a8085a67701fc1f2d3ba0e55f71d0a4c0/> add `File::at_or_default(...)` to easily open or create an empty in-memory index.
+ This is a common operation in new repositories.
+ - <csr-id-0b40951f0fb9ee354a83751264ed89d0608111f8/> add `State::new()` to create a new empty in-memory index.
+ - <csr-id-a7183e28513fa1e1a1a784b759677f0e4b4db5f4/> add `File::set_path()` to allow setting the location of an index file on disk.
+ This is useful to change the location of an index after reading it (from another
+ location, similar to copy-on-write).
+
+### Changed (BREAKING)
+
+ - <csr-id-3753ad58a8303fbf325b07757b2b97c34253bca4/> remove `File::into_state()` in favor of `From<File> for State`.
+ That way it's less discoverable, but more idiomatic, and we don't want to
+ get into the habit of providing multiple names of the exact same
+ functionality.
+
+## 0.11.0 (2022-12-30)
+
+A maintenance release without user-facing changes.
+
+## 0.10.0 (2022-12-19)
+
+A maintenance release without user-facing changes.
+
+## 0.9.1 (2022-11-27)
+
+### New Features
+
+ - <csr-id-fd2dd3a74c8d64407c1c27f29a2914389ded3bd6/> name spawned threads
+ That way it's a bit more obvious what's happening when the CPU goes
+ up in flames.
+
+## 0.9.0 (2022-11-21)
+
+### New Features (BREAKING)
+
+ - <csr-id-3d8fa8fef9800b1576beab8a5bc39b821157a5ed/> upgrade edition to 2021 in most crates.
+ MSRV for this is 1.56, and we are now at 1.60 so should be compatible.
+ This isn't more than a patch release as it should break nobody
+ who is adhering to the MSRV, but let's be careful and mark it
+ breaking.
+
+ Note that `gix-features` and `gix-pack` are still on edition 2018
+ as they make use of a workaround to support (safe) mutable access
+ to non-overlapping entries in a slice which doesn't work anymore
+ in edition 2021.
+
+## 0.8.0 (2022-11-17)
+
+A maintenance release without user-facing changes.
+
+## 0.7.1 (2022-11-08)
+
+A maintenance release without user-facing changes.
+
+## 0.7.0 (2022-11-06)
+
+<csr-id-4a6d46f3ab3d15eb851c92f7e49eb6772bc4023b/>
+
+### New Features
+
+ - <csr-id-458e1bcbd7043f0759f7445bfa46189910baff54/> Clone for `File`
+ - <csr-id-9e03110578bd93da3f1a91c5bcd9fde942c81ac4/> `decode::Options::default_from_object_hash()`
+ An easier way to initialize decode options, providing only the mandatory
+ information.
+ - <csr-id-eedcffa728c7e895da51d5298db28f3fef05f7da/> `File::write()` for secure and complete writing of index files.
+
+### Other
+
+ - <csr-id-4a6d46f3ab3d15eb851c92f7e49eb6772bc4023b/> sketch out how a write implementation could work
+
+### Changed (BREAKING)
+
+ - <csr-id-59f679126aba6f8a432aeb53f0bbd5d136ec1deb/> `write::Options::object_hash` is now implied by the `State` itself.
+ The `State`, once initialized, knows the kind of object hash it uses and
+ there is no need to specify it again.
+
+ This affects some method signatures which now work without
+ `object_hash`.
+ - <csr-id-908163ab2f86a1b603e69f04cd857fbf52e5abfb/> `decode::Options::object_hash` is now a parameter to methods.
+ It's not actually an option that could be defaulted, but an integral
+ piece of knowledge that must always be defined by the caller.
+
+ This also makes `decode::Options::default()` available once again.
+ - <csr-id-92dda50e2d9c584b0e110026f59fb715ec41600a/> seal `File` members to preserve consistency better.
+ This also makes sure that it's obvious if the `checksum` is actually
+ already computed.
+
+### Reverted (BREAKING)
+
+ - <csr-id-bd312acf5ceba28edf2508aef6011c037eb0a377/> `decode::Options::default()` - remove `Default` impl.
+ The contained `gix_hash::Kind` can't actually be defaulted as we
+ have to know the actual kind used in the repository.
+ - <csr-id-2da5a62432350ede6b816254c894863d14aa4ba1/> remove `write::Options::default()`.
+ In practice it's required to inform about the hash kind to use and it's
+ possibly incorrect to assume Sha1.
+
+## 0.6.0 (2022-10-10)
+
+Maintenance release without user-facing changes.
+
+## 0.5.0 (2022-09-20)
+
+<csr-id-6c17f96fcee9e2935b464c8ffbd30b253d9f5a6c/>
+
+### Other
+
+ - <csr-id-6c17f96fcee9e2935b464c8ffbd30b253d9f5a6c/> :init module
+
+### Changed (BREAKING)
+
+ - <csr-id-99905bacace8aed42b16d43f0f04cae996cb971c/> upgrade `bstr` to `1.0.1`
+
+## 0.4.3 (2022-08-27)
+
+Maintenance release without user-facing changes.
+
+## 0.4.2 (2022-08-24)
+
+<csr-id-533e887e80c5f7ede8392884562e1c5ba56fb9a8/>
+
+### Chore
+
+ - <csr-id-533e887e80c5f7ede8392884562e1c5ba56fb9a8/> remove default link to cargo doc everywhere
+
+### New Features
+
+ - <csr-id-b1c40b0364ef092cd52d03b34f491b254816b18d/> use docsrs feature in code to show what is feature-gated automatically on docs.rs
+ - <csr-id-517677147f1c17304c62cf97a1dd09f232ebf5db/> pass --cfg docsrs when compiling for https://docs.rs
+
+## 0.4.1 (2022-08-17)
+
+### New Features
+
+ - <csr-id-6d8d5e6198dfb4d648061807ed4f96868a36ee52/> `Stage::entry_index_by_path_and_stage()`, now with `::entry_by_path_and_stage()`
+ - <csr-id-55363ea650001b7717545b4d2968419707a3b8c6/> `State::entry_by_path_and_stage()` to find entries.
+ - <csr-id-40e6bde125778e3b50999331c4ed5a4b119937fa/> `Debug` and `Clone` for `File`
+
+## 0.4.0 (2022-07-22)
+
+This is a maintenance release with no functional changes.
+
+## 0.3.0 (2022-05-18)
+
+### New Features
+
+ - <csr-id-8ab219ac47ca67f2478b8715d7820fd6171c0db2/> `State::path_backing()`.
+ That way it's possible to call certain methods that take a separate
+ path buffer.
+ - <csr-id-645ed50dc2ae5ded2df0c09daf4fe366b90cf47e/> support for separating lifetimes of entries and path-backing
+ This way it should be possible to access paths immutably even while
+ entries are available mutably, assuming we stagger accesses to put
+ mutation of entries last.
+
+## 0.2.0 (2022-04-03)
+
+### Bug Fixes
+
+ - <csr-id-c2cc939d131a278c177c5f44d3b26127c65bd352/> lower MSRV to 1.52
+
+### Bug Fixes (BREAKING)
+
+ - <csr-id-0b1543d481337ed51dcfdeb907af21f0bc98dcb9/> lower rust edition to 2018
+
+## 0.1.0 (2022-01-19)
+
+The initial release which can read a complete index, version 2 to 4, with all extensions.
+The reading can be performed with multiple threads as well, partially depending on whether
+certain extensions are present.
+
+## v0.0.0 (2020-08-28)
+
diff --git a/vendor/gix-index/Cargo.toml b/vendor/gix-index/Cargo.toml
new file mode 100644
index 000000000..87191a09f
--- /dev/null
+++ b/vendor/gix-index/Cargo.toml
@@ -0,0 +1,105 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.64"
+name = "gix-index"
+version = "0.14.0"
+authors = ["Sebastian Thiel <sebastian.thiel@icloud.com>"]
+include = [
+ "src/**/*",
+ "README.md",
+ "CHANGELOG.md",
+]
+autotests = false
+description = "A work-in-progress crate of the gitoxide project dedicated implementing the git index file"
+readme = "README.md"
+license = "MIT/Apache-2.0"
+repository = "https://github.com/Byron/gitoxide"
+
+[package.metadata.docs.rs]
+features = [
+ "document-features",
+ "serde1",
+]
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
+
+[lib]
+test = true
+doctest = false
+
+[dependencies.bitflags]
+version = "1.3.2"
+
+[dependencies.bstr]
+version = "1.3.0"
+default-features = false
+
+[dependencies.btoi]
+version = "0.4.3"
+
+[dependencies.document-features]
+version = "0.2.0"
+optional = true
+
+[dependencies.filetime]
+version = "0.2.15"
+
+[dependencies.gix-bitmap]
+version = "^0.2.2"
+
+[dependencies.gix-features]
+version = "^0.28.0"
+features = [
+ "rustsha1",
+ "progress",
+]
+
+[dependencies.gix-hash]
+version = "^0.10.3"
+
+[dependencies.gix-lock]
+version = "^4.0.0"
+
+[dependencies.gix-object]
+version = "^0.28.0"
+
+[dependencies.gix-traverse]
+version = "^0.24.0"
+
+[dependencies.itoa]
+version = "1.0.3"
+
+[dependencies.memmap2]
+version = "0.5.0"
+
+[dependencies.serde]
+version = "1.0.114"
+features = ["derive"]
+optional = true
+default-features = false
+
+[dependencies.smallvec]
+version = "1.7.0"
+
+[dependencies.thiserror]
+version = "1.0.32"
+
+[features]
+serde1 = [
+ "serde",
+ "smallvec/serde",
+ "gix-hash/serde1",
+]
diff --git a/vendor/gix-index/README.md b/vendor/gix-index/README.md
new file mode 100644
index 000000000..f239be693
--- /dev/null
+++ b/vendor/gix-index/README.md
@@ -0,0 +1,11 @@
+
+#### Test fixtures
+
+Most of the test indices are snatched directly from the unit test suite of `git` itself, usually by running something like the following
+
+```shell
+ ./t1700-split-index.sh -r 2 --debug
+```
+
+Then one finds all test state and the index in particular in `trash directory/t1700-split-index/.git/index` and can possibly copy it over and use as fixture.
+The preferred way is to find a test of interest, and use its setup code within one of our own fixture scripts that are executed once to generate the file of interest.
diff --git a/vendor/gix-index/src/access/mod.rs b/vendor/gix-index/src/access/mod.rs
new file mode 100644
index 000000000..e8f2dc9f8
--- /dev/null
+++ b/vendor/gix-index/src/access/mod.rs
@@ -0,0 +1,223 @@
+use std::cmp::Ordering;
+
+use bstr::{BStr, ByteSlice, ByteVec};
+
+use crate::{entry, extension, Entry, PathStorage, State, Version};
+
+// TODO: integrate this somehow, somewhere, depending on later usage.
+#[allow(dead_code)]
+mod sparse;
+
+/// General information and entries
+impl State {
+ /// Return the version used to store this state's information on disk.
+ pub fn version(&self) -> Version {
+ self.version
+ }
+
+ /// Return the kind of hashes used in this instance.
+ pub fn object_hash(&self) -> gix_hash::Kind {
+ self.object_hash
+ }
+
+ /// Return our entries
+ pub fn entries(&self) -> &[Entry] {
+ &self.entries
+ }
+ /// Return our path backing, the place which keeps all paths one after another, with entries storing only the range to access them.
+ pub fn path_backing(&self) -> &PathStorage {
+ &self.path_backing
+ }
+
+ /// Runs `filter_map` on all entries, returning an iterator over all paths along with the result of `filter_map`.
+ pub fn entries_with_paths_by_filter_map<'a, T>(
+ &'a self,
+ mut filter_map: impl FnMut(&'a BStr, &Entry) -> Option<T> + 'a,
+ ) -> impl Iterator<Item = (&'a BStr, T)> + 'a {
+ self.entries.iter().filter_map(move |e| {
+ let p = e.path(self);
+ filter_map(p, e).map(|t| (p, t))
+ })
+ }
+ /// Return mutable entries along with their path, as obtained from `backing`.
+ pub fn entries_mut_with_paths_in<'state, 'backing>(
+ &'state mut self,
+ backing: &'backing PathStorage,
+ ) -> impl Iterator<Item = (&'state mut Entry, &'backing BStr)> {
+ self.entries.iter_mut().map(move |e| {
+ let path = backing[e.path.clone()].as_bstr();
+ (e, path)
+ })
+ }
+
+ /// Find the entry index in [`entries()`][State::entries()] matching the given repository-relative
+ /// `path` and `stage`, or `None`.
+ ///
+ /// Use the index for accessing multiple stages if they exists, but at least the single matching entry.
+ pub fn entry_index_by_path_and_stage(&self, path: &BStr, stage: entry::Stage) -> Option<usize> {
+ self.entries
+ .binary_search_by(|e| e.path(self).cmp(path).then_with(|| e.stage().cmp(&stage)))
+ .ok()
+ }
+
+ /// Find the entry index in [`entries()[..upper_bound]`][State::entries()] matching the given repository-relative
+ /// `path` and `stage`, or `None`.
+ ///
+ /// Use the index for accessing multiple stages if they exists, but at least the single matching entry.
+ ///
+ /// # Panics
+ ///
+ /// If `upper_bound` is out of bounds of our entries array.
+ pub fn entry_index_by_path_and_stage_bounded(
+ &self,
+ path: &BStr,
+ stage: entry::Stage,
+ upper_bound: usize,
+ ) -> Option<usize> {
+ self.entries[..upper_bound]
+ .binary_search_by(|e| e.path(self).cmp(path).then_with(|| e.stage().cmp(&stage)))
+ .ok()
+ }
+
+ /// Like [`entry_index_by_path_and_stage()`][State::entry_index_by_path_and_stage()],
+ /// but returns the entry instead of the index.
+ pub fn entry_by_path_and_stage(&self, path: &BStr, stage: entry::Stage) -> Option<&Entry> {
+ self.entry_index_by_path_and_stage(path, stage)
+ .map(|idx| &self.entries[idx])
+ }
+
+ /// Return the entry at `idx` or _panic_ if the index is out of bounds.
+ ///
+ /// The `idx` is typically returned by [entry_by_path_and_stage()][State::entry_by_path_and_stage()].
+ pub fn entry(&self, idx: usize) -> &Entry {
+ &self.entries[idx]
+ }
+
+ /// Returns a boolean value indicating whether the index is sparse or not.
+ ///
+ /// An index is sparse if it contains at least one [Mode::DIR][entry::Mode::DIR] entry.
+ pub fn is_sparse(&self) -> bool {
+ self.is_sparse
+ }
+}
+
+/// Mutation
+impl State {
+ /// After usage of the storage obtained by [`take_path_backing()`][Self::take_path_backing()], return it here.
+ /// Note that it must not be empty.
+ pub fn return_path_backing(&mut self, backing: PathStorage) {
+ debug_assert!(
+ self.path_backing.is_empty(),
+ "BUG: return path backing only after taking it, once"
+ );
+ self.path_backing = backing;
+ }
+
+ /// Return mutable entries in a slice.
+ pub fn entries_mut(&mut self) -> &mut [Entry] {
+ &mut self.entries
+ }
+ /// Return mutable entries along with their paths in an iterator.
+ pub fn entries_mut_with_paths(&mut self) -> impl Iterator<Item = (&mut Entry, &BStr)> {
+ let paths = &self.path_backing;
+ self.entries.iter_mut().map(move |e| {
+ let path = paths[e.path.clone()].as_bstr();
+ (e, path)
+ })
+ }
+
+ /// Sometimes it's needed to remove the path backing to allow certain mutation to happen in the state while supporting reading the entry's
+ /// path.
+ pub fn take_path_backing(&mut self) -> PathStorage {
+ assert_eq!(
+ self.entries.is_empty(),
+ self.path_backing.is_empty(),
+ "BUG: cannot take out backing multiple times"
+ );
+ std::mem::take(&mut self.path_backing)
+ }
+
+ /// Like [`entry_index_by_path_and_stage()`][State::entry_index_by_path_and_stage()],
+ /// but returns the mutable entry instead of the index.
+ pub fn entry_mut_by_path_and_stage(&mut self, path: &BStr, stage: entry::Stage) -> Option<&mut Entry> {
+ self.entry_index_by_path_and_stage(path, stage)
+ .map(|idx| &mut self.entries[idx])
+ }
+
+ /// Push a new entry containing `stat`, `id`, `flags` and `mode` and `path` to the end of our storage, without performing
+ /// any sanity checks. This means it's possible to push a new entry to the same path on the same stage and even after sorting
+ /// the entries lookups may still return the wrong one of them unless the correct binary search criteria is chosen.
+ ///
+ /// Note that this *is likely* to break invariants that will prevent further lookups by path unless
+ /// [`entry_index_by_path_and_stage_bounded()`][State::entry_index_by_path_and_stage_bounded()] is used with
+ /// the `upper_bound` being the amount of entries before the first call to this method.
+ ///
+ /// Alternatively, make sure to call [sort_entries()][State::sort_entries()] before entry lookup by path to restore
+ /// the invariant.
+ pub fn dangerously_push_entry(
+ &mut self,
+ stat: entry::Stat,
+ id: gix_hash::ObjectId,
+ flags: entry::Flags,
+ mode: entry::Mode,
+ path: &BStr,
+ ) {
+ let path = {
+ let path_start = self.path_backing.len();
+ self.path_backing.push_str(path);
+ path_start..self.path_backing.len()
+ };
+
+ self.entries.push(Entry {
+ stat,
+ id,
+ flags,
+ mode,
+ path,
+ });
+ }
+
+ /// Unconditionally sort entries as needed to perform lookups quickly.
+ pub fn sort_entries(&mut self) {
+ let path_backing = &self.path_backing;
+ self.entries.sort_by(|a, b| {
+ Entry::cmp_filepaths(a.path_in(path_backing), b.path_in(path_backing))
+ .then_with(|| a.stage().cmp(&b.stage()))
+ });
+ }
+
+ /// Similar to [`sort_entries()`][State::sort_entries()], but applies `compare` after comparing
+ /// by path and stage as a third criteria.
+ pub fn sort_entries_by(&mut self, mut compare: impl FnMut(&Entry, &Entry) -> Ordering) {
+ let path_backing = &self.path_backing;
+ self.entries.sort_by(|a, b| {
+ Entry::cmp_filepaths(a.path_in(path_backing), b.path_in(path_backing))
+ .then_with(|| a.stage().cmp(&b.stage()))
+ .then_with(|| compare(a, b))
+ });
+ }
+}
+
+/// Extensions
+impl State {
+ /// Access the `tree` extension.
+ pub fn tree(&self) -> Option<&extension::Tree> {
+ self.tree.as_ref()
+ }
+ /// Access the `link` extension.
+ pub fn link(&self) -> Option<&extension::Link> {
+ self.link.as_ref()
+ }
+ /// Obtain the resolve-undo extension.
+ pub fn resolve_undo(&self) -> Option<&extension::resolve_undo::Paths> {
+ self.resolve_undo.as_ref()
+ }
+ /// Obtain the untracked extension.
+ pub fn untracked(&self) -> Option<&extension::UntrackedCache> {
+ self.untracked.as_ref()
+ }
+ /// Obtain the fsmonitor extension.
+ pub fn fs_monitor(&self) -> Option<&extension::FsMonitor> {
+ self.fs_monitor.as_ref()
+ }
+}
diff --git a/vendor/gix-index/src/access/sparse.rs b/vendor/gix-index/src/access/sparse.rs
new file mode 100644
index 000000000..27804f846
--- /dev/null
+++ b/vendor/gix-index/src/access/sparse.rs
@@ -0,0 +1,59 @@
+/// Configuration related to sparse indexes.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Options {
+ /// If true, certain entries in the index will be excluded / skipped for certain operations,
+ /// based on the ignore patterns in the `.git/info/sparse-checkout` file. These entries will
+ /// carry the [`SKIP_WORKTREE`][crate::entry::Flags::SKIP_WORKTREE] flag.
+ ///
+ /// This typically is the value of `core.sparseCheckout` in the git configuration.
+ pub sparse_checkout: bool,
+
+ /// Interpret the `.git/info/sparse-checkout` file using _cone mode_.
+ ///
+ /// If true, _cone mode_ is active and entire directories will be included in the checkout, as well as files in the root
+ /// of the repository.
+ /// If false, non-cone mode is active and entries to _include_ will be matched with patterns like those found in `.gitignore` files.
+ ///
+ /// This typically is the value of `core.sparseCheckoutCone` in the git configuration.
+ pub directory_patterns_only: bool,
+
+ /// If true, will attempt to write a sparse index file which only works in cone mode.
+ ///
+ /// A sparse index has [`DIR` entries][crate::entry::Mode::DIR] that represent entire directories to be skipped
+ /// during checkout and other operations due to the added presence of
+ /// the [`SKIP_WORKTREE`][crate::entry::Flags::SKIP_WORKTREE] flag.
+ ///
+ /// This is typically the value of `index.sparse` in the git configuration.
+ pub write_sparse_index: bool,
+}
+
+impl Options {
+ /// Derive a valid mode from all parameters that affect the 'sparseness' of the index.
+ ///
+ /// Some combinations of them degenerate to one particular mode.
+ pub fn sparse_mode(&self) -> Mode {
+ match (
+ self.sparse_checkout,
+ self.directory_patterns_only,
+ self.write_sparse_index,
+ ) {
+ (true, true, true) => Mode::IncludeDirectoriesStoreIncludedEntriesAndExcludedDirs,
+ (true, true, false) => Mode::IncludeDirectoriesStoreAllEntriesSkipUnmatched,
+ (true, false, _) => Mode::IncludeByIgnorePatternStoreAllEntriesSkipUnmatched,
+ (false, _, _) => Mode::Disabled,
+ }
+ }
+}
+
+/// Describes the configuration how a sparse index should be written, or if one should be written at all.
+#[derive(Debug)]
+pub enum Mode {
+ /// index with DIR entries for exclusion and included entries, directory-only include patterns in `.git/info/sparse-checkout` file.
+ IncludeDirectoriesStoreIncludedEntriesAndExcludedDirs,
+ /// index with all file entries and skip worktree flags for exclusion, directory-only include patterns in `.git/info/sparse-checkout` file.
+ IncludeDirectoriesStoreAllEntriesSkipUnmatched,
+ /// index with all file entries and skip-worktree flags for exclusion, `ignore` patterns to include entries in `.git/info/sparse-checkout` file.
+ IncludeByIgnorePatternStoreAllEntriesSkipUnmatched,
+ /// index with all entries, non is excluded, `.git/info/sparse-checkout` file is not considered, a regular index.
+ Disabled,
+}
diff --git a/vendor/gix-index/src/decode/entries.rs b/vendor/gix-index/src/decode/entries.rs
new file mode 100644
index 000000000..0a968ae0c
--- /dev/null
+++ b/vendor/gix-index/src/decode/entries.rs
@@ -0,0 +1,181 @@
+use std::{convert::TryInto, ops::Range};
+
+use crate::{
+ decode::{self, header},
+ entry,
+ util::{read_u32, split_at_byte_exclusive, split_at_pos, var_int},
+ Entry, Version,
+};
+
+/// a guess directly from git sources
+pub const AVERAGE_V4_DELTA_PATH_LEN_IN_BYTES: usize = 80;
+
+pub struct Outcome {
+ pub is_sparse: bool,
+}
+
+pub fn estimate_path_storage_requirements_in_bytes(
+ num_entries: u32,
+ on_disk_size: usize,
+ offset_to_extensions: Option<usize>,
+ object_hash: gix_hash::Kind,
+ version: Version,
+) -> usize {
+ const fn on_disk_entry_sans_path(object_hash: gix_hash::Kind) -> usize {
+ 8 + // ctime
+ 8 + // mtime
+ (4 * 6) + // various stat fields
+ 2 + // flag, ignore extended flag as we'd rather overallocate a bit
+ object_hash.len_in_bytes()
+ }
+ match version {
+ Version::V3 | Version::V2 => {
+ let size_of_entries_block = offset_to_extensions.unwrap_or(on_disk_size);
+ size_of_entries_block
+ .saturating_sub(num_entries as usize * on_disk_entry_sans_path(object_hash))
+ .saturating_sub(header::SIZE)
+ }
+ Version::V4 => num_entries as usize * AVERAGE_V4_DELTA_PATH_LEN_IN_BYTES,
+ }
+}
+
+/// Note that `data` must point to the beginning of the entries, right past the header.
+pub fn chunk<'a>(
+ mut data: &'a [u8],
+ entries: &mut Vec<Entry>,
+ path_backing: &mut Vec<u8>,
+ num_entries: u32,
+ object_hash: gix_hash::Kind,
+ version: Version,
+) -> Result<(Outcome, &'a [u8]), decode::Error> {
+ let mut is_sparse = false;
+ let has_delta_paths = version == Version::V4;
+ let mut prev_path = None;
+ let mut delta_buf = Vec::<u8>::with_capacity(AVERAGE_V4_DELTA_PATH_LEN_IN_BYTES);
+
+ for idx in 0..num_entries {
+ let (entry, remaining) = load_one(
+ data,
+ path_backing,
+ object_hash.len_in_bytes(),
+ has_delta_paths,
+ prev_path,
+ )
+ .ok_or(decode::Error::Entry { index: idx })?;
+
+ data = remaining;
+ if entry.mode.is_sparse() {
+ is_sparse = true;
+ }
+ // TODO: entries are actually in an intrusive collection, with path as key. Could be set for us. This affects 'ignore_case' which we
+ // also don't yet handle but probably could, maybe even smartly with the collection.
+ // For now it's unclear to me how they access the index, they could iterate quickly, and have fast access by path.
+ entries.push(entry);
+ prev_path = entries.last().map(|e| (e.path.clone(), &mut delta_buf));
+ }
+
+ Ok((Outcome { is_sparse }, data))
+}
+
+/// Note that `prev_path` is only useful if the version is V4
+fn load_one<'a>(
+ data: &'a [u8],
+ path_backing: &mut Vec<u8>,
+ hash_len: usize,
+ has_delta_paths: bool,
+ prev_path_and_buf: Option<(Range<usize>, &mut Vec<u8>)>,
+) -> Option<(Entry, &'a [u8])> {
+ let first_byte_of_entry = data.as_ptr() as usize;
+ let (ctime_secs, data) = read_u32(data)?;
+ let (ctime_nsecs, data) = read_u32(data)?;
+ let (mtime_secs, data) = read_u32(data)?;
+ let (mtime_nsecs, data) = read_u32(data)?;
+ let (dev, data) = read_u32(data)?;
+ let (ino, data) = read_u32(data)?;
+ let (mode, data) = read_u32(data)?;
+ let (uid, data) = read_u32(data)?;
+ let (gid, data) = read_u32(data)?;
+ let (size, data) = read_u32(data)?;
+ let (hash, data) = split_at_pos(data, hash_len)?;
+ let (flags, data) = read_u16(data)?;
+ let flags = entry::at_rest::Flags::from_bits(flags)?;
+ let (flags, data) = if flags.contains(entry::at_rest::Flags::EXTENDED) {
+ let (extended_flags, data) = read_u16(data)?;
+ let extended_flags = entry::at_rest::FlagsExtended::from_bits(extended_flags)?;
+ let extended_flags = extended_flags.to_flags()?;
+ (flags.to_memory() | extended_flags, data)
+ } else {
+ (flags.to_memory(), data)
+ };
+
+ let start = path_backing.len();
+ let data = if has_delta_paths {
+ let (strip_len, data) = var_int(data)?;
+ if let Some((prev_path, buf)) = prev_path_and_buf {
+ let end = prev_path.end.checked_sub(strip_len.try_into().ok()?)?;
+ let copy_len = end.checked_sub(prev_path.start)?;
+ if copy_len > 0 {
+ buf.resize(copy_len, 0);
+ buf.copy_from_slice(&path_backing[prev_path.start..end]);
+ path_backing.extend_from_slice(buf);
+ }
+ }
+
+ let (path, data) = split_at_byte_exclusive(data, 0)?;
+ path_backing.extend_from_slice(path);
+
+ data
+ } else {
+ let (path, data) = if flags.contains(entry::Flags::PATH_LEN) {
+ split_at_byte_exclusive(data, 0)?
+ } else {
+ let path_len = (flags.bits() & entry::Flags::PATH_LEN.bits()) as usize;
+ let (path, data) = split_at_pos(data, path_len)?;
+ (path, skip_padding(data, first_byte_of_entry))
+ };
+
+ path_backing.extend_from_slice(path);
+ data
+ };
+ let path_range = start..path_backing.len();
+
+ Some((
+ Entry {
+ stat: entry::Stat {
+ ctime: entry::Time {
+ secs: ctime_secs,
+ nsecs: ctime_nsecs,
+ },
+ mtime: entry::Time {
+ secs: mtime_secs,
+ nsecs: mtime_nsecs,
+ },
+ dev,
+ ino,
+ uid,
+ gid,
+ size,
+ },
+ id: gix_hash::ObjectId::from(hash),
+ flags: flags & !entry::Flags::PATH_LEN,
+ // This forces us to add the bits we need before being able to use them.
+ mode: entry::Mode::from_bits_truncate(mode),
+ path: path_range,
+ },
+ data,
+ ))
+}
+
+#[inline]
+fn skip_padding(data: &[u8], first_byte_of_entry: usize) -> &[u8] {
+ let current_offset = data.as_ptr() as usize;
+ let c_padding = (current_offset - first_byte_of_entry + 8) & !7;
+ let skip = (first_byte_of_entry + c_padding) - current_offset;
+
+ &data[skip..]
+}
+
+#[inline]
+fn read_u16(data: &[u8]) -> Option<(u16, &[u8])> {
+ split_at_pos(data, 2).map(|(num, data)| (u16::from_be_bytes(num.try_into().unwrap()), data))
+}
diff --git a/vendor/gix-index/src/decode/header.rs b/vendor/gix-index/src/decode/header.rs
new file mode 100644
index 000000000..04c3bfd98
--- /dev/null
+++ b/vendor/gix-index/src/decode/header.rs
@@ -0,0 +1,46 @@
+pub(crate) const SIZE: usize = 4 /*signature*/ + 4 /*version*/ + 4 /* num entries */;
+
+use crate::{util::from_be_u32, Version};
+
+pub(crate) const SIGNATURE: &[u8] = b"DIRC";
+
+mod error {
+
+ /// The error produced when failing to decode an index header.
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("{0}")]
+ Corrupt(&'static str),
+ #[error("Index version {0} is not supported")]
+ UnsupportedVersion(u32),
+ }
+}
+pub use error::Error;
+
+pub(crate) fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Result<(Version, u32, &[u8]), Error> {
+ if data.len() < (3 * 4) + object_hash.len_in_bytes() {
+ return Err(Error::Corrupt(
+ "File is too small even for header with zero entries and smallest hash",
+ ));
+ }
+
+ let (signature, data) = data.split_at(4);
+ if signature != SIGNATURE {
+ return Err(Error::Corrupt(
+ "Signature mismatch - this doesn't claim to be a header file",
+ ));
+ }
+
+ let (version, data) = data.split_at(4);
+ let version = match from_be_u32(version) {
+ 2 => Version::V2,
+ 3 => Version::V3,
+ 4 => Version::V4,
+ unknown => return Err(Error::UnsupportedVersion(unknown)),
+ };
+ let (entries, data) = data.split_at(4);
+ let entries = from_be_u32(entries);
+
+ Ok((version, entries, data))
+}
diff --git a/vendor/gix-index/src/decode/mod.rs b/vendor/gix-index/src/decode/mod.rs
new file mode 100644
index 000000000..e84d8f717
--- /dev/null
+++ b/vendor/gix-index/src/decode/mod.rs
@@ -0,0 +1,321 @@
+use filetime::FileTime;
+
+use crate::{entry, extension, Entry, State, Version};
+
+mod entries;
+///
+pub mod header;
+
+mod error {
+
+ use crate::{decode, extension};
+
+ /// The error returned by [State::from_bytes()][crate::State::from_bytes()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error(transparent)]
+ Header(#[from] decode::header::Error),
+ #[error("Could not parse entry at index {index}")]
+ Entry { index: u32 },
+ #[error("Mandatory extension wasn't implemented or malformed.")]
+ Extension(#[from] extension::decode::Error),
+ #[error("Index trailer should have been {expected} bytes long, but was {actual}")]
+ UnexpectedTrailerLength { expected: usize, actual: usize },
+ #[error("Shared index checksum was {actual_checksum} but should have been {expected_checksum}")]
+ ChecksumMismatch {
+ actual_checksum: gix_hash::ObjectId,
+ expected_checksum: gix_hash::ObjectId,
+ },
+ }
+}
+pub use error::Error;
+use gix_features::parallel::InOrderIter;
+
+use crate::util::read_u32;
+
+/// Options to define how to decode an index state [from bytes][State::from_bytes()].
+#[derive(Default, Clone, Copy)]
+pub struct Options {
+ /// If Some(_), we are allowed to use more than one thread. If Some(N), use no more than N threads. If Some(0)|None, use as many threads
+ /// as there are logical cores.
+ ///
+ /// This applies to loading extensions in parallel to entries if the common EOIE extension is available.
+ /// It also allows to use multiple threads for loading entries if the IEOT extension is present.
+ pub thread_limit: Option<usize>,
+ /// The minimum size in bytes to load extensions in their own thread, assuming there is enough `num_threads` available.
+ /// If set to 0, for example, extensions will always be read in their own thread if enough threads are available.
+ pub min_extension_block_in_bytes_for_threading: usize,
+ /// Set the expected hash of this index if we are read as part of a `link` extension.
+ ///
+ /// We will abort reading this file if it doesn't match.
+ pub expected_checksum: Option<gix_hash::ObjectId>,
+}
+
+impl State {
+ /// Decode an index state from `data` and store `timestamp` in the resulting instance for pass-through, assuming `object_hash`
+ /// to be used through the file.
+ pub fn from_bytes(
+ data: &[u8],
+ timestamp: FileTime,
+ object_hash: gix_hash::Kind,
+ Options {
+ thread_limit,
+ min_extension_block_in_bytes_for_threading,
+ expected_checksum,
+ }: Options,
+ ) -> Result<(Self, gix_hash::ObjectId), Error> {
+ let (version, num_entries, post_header_data) = header::decode(data, object_hash)?;
+ let start_of_extensions = extension::end_of_index_entry::decode(data, object_hash);
+
+ let mut num_threads = gix_features::parallel::num_threads(thread_limit);
+ let path_backing_buffer_size = entries::estimate_path_storage_requirements_in_bytes(
+ num_entries,
+ data.len(),
+ start_of_extensions,
+ object_hash,
+ version,
+ );
+
+ let (entries, ext, data) = match start_of_extensions {
+ Some(offset) if num_threads > 1 => {
+ let extensions_data = &data[offset..];
+ let index_offsets_table = extension::index_entry_offset_table::find(extensions_data, object_hash);
+ let (entries_res, ext_res) = gix_features::parallel::threads(|scope| {
+ let extension_loading =
+ (extensions_data.len() > min_extension_block_in_bytes_for_threading).then({
+ num_threads -= 1;
+ || {
+ gix_features::parallel::build_thread()
+ .name("gix-index.from_bytes.load-extensions".into())
+ .spawn_scoped(scope, || extension::decode::all(extensions_data, object_hash))
+ .expect("valid name")
+ }
+ });
+ let entries_res = match index_offsets_table {
+ Some(entry_offsets) => {
+ let chunk_size = (entry_offsets.len() as f32 / num_threads as f32).ceil() as usize;
+ let num_chunks = entry_offsets.chunks(chunk_size).count();
+ let mut threads = Vec::with_capacity(num_chunks);
+ for (id, chunks) in entry_offsets.chunks(chunk_size).enumerate() {
+ let chunks = chunks.to_vec();
+ threads.push(
+ gix_features::parallel::build_thread()
+ .name(format!("gix-index.from_bytes.read-entries.{id}"))
+ .spawn_scoped(scope, move || {
+ let num_entries_for_chunks =
+ chunks.iter().map(|c| c.num_entries).sum::<u32>() as usize;
+ let mut entries = Vec::with_capacity(num_entries_for_chunks);
+ let path_backing_buffer_size_for_chunks =
+ entries::estimate_path_storage_requirements_in_bytes(
+ num_entries_for_chunks as u32,
+ data.len() / num_chunks,
+ start_of_extensions.map(|ofs| ofs / num_chunks),
+ object_hash,
+ version,
+ );
+ let mut path_backing =
+ Vec::with_capacity(path_backing_buffer_size_for_chunks);
+ let mut is_sparse = false;
+ for offset in chunks {
+ let (
+ entries::Outcome {
+ is_sparse: chunk_is_sparse,
+ },
+ _data,
+ ) = entries::chunk(
+ &data[offset.from_beginning_of_file as usize..],
+ &mut entries,
+ &mut path_backing,
+ offset.num_entries,
+ object_hash,
+ version,
+ )?;
+ is_sparse |= chunk_is_sparse;
+ }
+ Ok::<_, Error>((
+ id,
+ EntriesOutcome {
+ entries,
+ path_backing,
+ is_sparse,
+ },
+ ))
+ })
+ .expect("valid name"),
+ );
+ }
+ let mut results =
+ InOrderIter::from(threads.into_iter().map(|thread| thread.join().unwrap()));
+ let mut acc = results.next().expect("have at least two results, one per thread");
+ // We explicitly don't adjust the reserve in acc and rather allow for more copying
+ // to happens as vectors grow to keep the peak memory size low.
+ // NOTE: one day, we might use a memory pool for paths. We could encode the block of memory
+ // in some bytes in the path offset. That way there is more indirection/slower access
+ // to the path, but it would save time here.
+ // As it stands, `git` is definitely more efficient at this and probably uses less memory too.
+ // Maybe benchmarks can tell if that is noticeable later at 200/400GB/s memory bandwidth, or maybe just
+ // 100GB/s on a single core.
+ while let (Ok(lhs), Some(res)) = (acc.as_mut(), results.next()) {
+ match res {
+ Ok(rhs) => {
+ lhs.is_sparse |= rhs.is_sparse;
+ let ofs = lhs.path_backing.len();
+ lhs.path_backing.extend(rhs.path_backing);
+ lhs.entries.extend(rhs.entries.into_iter().map(|mut e| {
+ e.path.start += ofs;
+ e.path.end += ofs;
+ e
+ }));
+ }
+ Err(err) => {
+ acc = Err(err);
+ }
+ }
+ }
+ acc.map(|acc| (acc, &data[data.len() - object_hash.len_in_bytes()..]))
+ }
+ None => entries(
+ post_header_data,
+ path_backing_buffer_size,
+ num_entries,
+ object_hash,
+ version,
+ ),
+ };
+ let ext_res = extension_loading
+ .map(|thread| thread.join().unwrap())
+ .unwrap_or_else(|| extension::decode::all(extensions_data, object_hash));
+ (entries_res, ext_res)
+ });
+ let (ext, data) = ext_res?;
+ (entries_res?.0, ext, data)
+ }
+ None | Some(_) => {
+ let (entries, data) = entries(
+ post_header_data,
+ path_backing_buffer_size,
+ num_entries,
+ object_hash,
+ version,
+ )?;
+ let (ext, data) = extension::decode::all(data, object_hash)?;
+ (entries, ext, data)
+ }
+ };
+
+ if data.len() != object_hash.len_in_bytes() {
+ return Err(Error::UnexpectedTrailerLength {
+ expected: object_hash.len_in_bytes(),
+ actual: data.len(),
+ });
+ }
+
+ let checksum = gix_hash::ObjectId::from(data);
+ if let Some(expected_checksum) = expected_checksum {
+ if checksum != expected_checksum {
+ return Err(Error::ChecksumMismatch {
+ actual_checksum: checksum,
+ expected_checksum,
+ });
+ }
+ }
+ let EntriesOutcome {
+ entries,
+ path_backing,
+ mut is_sparse,
+ } = entries;
+ let extension::decode::Outcome {
+ tree,
+ link,
+ resolve_undo,
+ untracked,
+ fs_monitor,
+ is_sparse: is_sparse_from_ext, // a marker is needed in case there are no directories
+ } = ext;
+ is_sparse |= is_sparse_from_ext;
+
+ Ok((
+ State {
+ object_hash,
+ timestamp,
+ version,
+ entries,
+ path_backing,
+ is_sparse,
+
+ tree,
+ link,
+ resolve_undo,
+ untracked,
+ fs_monitor,
+ },
+ checksum,
+ ))
+ }
+}
+
+struct EntriesOutcome {
+ pub entries: Vec<Entry>,
+ pub path_backing: Vec<u8>,
+ pub is_sparse: bool,
+}
+
+fn entries(
+ post_header_data: &[u8],
+ path_backing_buffer_size: usize,
+ num_entries: u32,
+ object_hash: gix_hash::Kind,
+ version: Version,
+) -> Result<(EntriesOutcome, &[u8]), Error> {
+ let mut entries = Vec::with_capacity(num_entries as usize);
+ let mut path_backing = Vec::with_capacity(path_backing_buffer_size);
+ entries::chunk(
+ post_header_data,
+ &mut entries,
+ &mut path_backing,
+ num_entries,
+ object_hash,
+ version,
+ )
+ .map(|(entries::Outcome { is_sparse }, data): (entries::Outcome, &[u8])| {
+ (
+ EntriesOutcome {
+ entries,
+ path_backing,
+ is_sparse,
+ },
+ data,
+ )
+ })
+}
+
+pub(crate) fn stat(data: &[u8]) -> Option<(entry::Stat, &[u8])> {
+ let (ctime_secs, data) = read_u32(data)?;
+ let (ctime_nsecs, data) = read_u32(data)?;
+ let (mtime_secs, data) = read_u32(data)?;
+ let (mtime_nsecs, data) = read_u32(data)?;
+ let (dev, data) = read_u32(data)?;
+ let (ino, data) = read_u32(data)?;
+ let (uid, data) = read_u32(data)?;
+ let (gid, data) = read_u32(data)?;
+ let (size, data) = read_u32(data)?;
+ Some((
+ entry::Stat {
+ mtime: entry::Time {
+ secs: ctime_secs,
+ nsecs: ctime_nsecs,
+ },
+ ctime: entry::Time {
+ secs: mtime_secs,
+ nsecs: mtime_nsecs,
+ },
+ dev,
+ ino,
+ uid,
+ gid,
+ size,
+ },
+ data,
+ ))
+}
diff --git a/vendor/gix-index/src/entry/flags.rs b/vendor/gix-index/src/entry/flags.rs
new file mode 100644
index 000000000..ec00a78db
--- /dev/null
+++ b/vendor/gix-index/src/entry/flags.rs
@@ -0,0 +1,130 @@
+use bitflags::bitflags;
+
+use crate::entry::Stage;
+
+bitflags! {
+ /// In-memory flags
+ pub struct Flags: u32 {
+ /// The mask to apply to obtain the stage number of an entry.
+ const STAGE_MASK = 0x3000;
+ /// If set, additional bits need to be written to storage.
+ const EXTENDED = 0x4000;
+ // TODO: could we use the pathlen ourselves to save 8 bytes? And how to handle longer paths than that? 0 as sentinel maybe?
+ /// The mask to obtain the length of the path associated with this entry.
+ const PATH_LEN = 0x0fff;
+ /// If set, the entry be assumed to match with the version on the working tree, as a way to avoid `lstat()` checks.
+ const ASSUME_VALID = 1 << 15;
+ /// Indicates that an entry needs to be updated as it's in-memory representation doesn't match what's on disk.
+ const UPDATE = 1 << 16;
+ /// Indicates an entry should be removed - this typically happens during writing, by simply skipping over them.
+ const REMOVE = 1 << 17;
+ /// Indicates that an entry is known to be uptodate.
+ const UPTODATE = 1 << 18;
+ /// Only temporarily used by unpack_trees() (in C)
+ const ADDED = 1 << 19;
+
+ /// Whether an up-to-date object hash exists for the entry.
+ const HASHED = 1 << 20;
+ /// Set if the filesystem monitor is valid.
+ const FSMONITOR_VALID = 1 << 21;
+ /// Remove in work directory
+ const WORKTREE_REMOVE = 1 << 22;
+ /// Set to indicate the entry exists in multiple stages at once due to conflicts.
+ const CONFLICTED = 1 << 23;
+
+ /// Indicates that the entry was already turned into a tree.
+ const UNPACKED = 1 << 24;
+ /// Only temporarily used by unpack_trees() (in C)
+ const NEW_SKIP_WORKTREE = 1 << 25;
+
+ /// temporarily mark paths matched by a path spec
+ const PATHSPEC_MATCHED = 1 << 26;
+
+ /// When the index is split, this indicates the entry is up-to-date in the shared portion of the index.
+ const UPDATE_IN_BASE = 1 << 27;
+ /// Indicates the entry name is present in the base/shared index, and thus doesn't have to be stored in this one.
+ const STRIP_NAME = 1 << 28;
+
+ ///
+ /// stored at rest, see at_rest::FlagsExtended
+ const INTENT_TO_ADD = 1 << 29;
+ /// Stored at rest
+ const SKIP_WORKTREE = 1 << 30;
+
+ /// For future extension
+ const EXTENDED_2 = 1 << 31;
+ }
+}
+
+impl Flags {
+ /// Return the stage as extracted from the bits of this instance.
+ pub fn stage(&self) -> Stage {
+ (*self & Flags::STAGE_MASK).bits >> 12
+ }
+
+ /// Transform ourselves to a storage representation to keep all flags which are to be persisted,
+ /// skipping all extended flags. Note that the caller has to check for the `EXTENDED` bit to be present
+ /// and write extended flags as well if so.
+ pub fn to_storage(mut self) -> at_rest::Flags {
+ at_rest::Flags::from_bits(
+ {
+ self.remove(Self::PATH_LEN);
+ self
+ }
+ .bits() as u16,
+ )
+ .unwrap()
+ }
+}
+
+pub(crate) mod at_rest {
+ use bitflags::bitflags;
+
+ bitflags! {
+ /// Flags how they are serialized to a storage location
+ pub struct Flags: u16 {
+ /// A portion of a the flags that encodes the length of the path that follows.
+ const PATH_LEN = 0x0fff;
+ const STAGE_MASK = 0x3000;
+ /// If set, there is more extended flags past this one
+ const EXTENDED = 0x4000;
+ /// If set, the entry be assumed to match with the version on the working tree, as a way to avoid `lstat()` checks.
+ const ASSUME_VALID = 0x8000;
+ }
+ }
+
+ impl Flags {
+ pub fn to_memory(self) -> super::Flags {
+ super::Flags::from_bits(self.bits as u32).expect("PATHLEN is part of memory representation")
+ }
+ }
+
+ bitflags! {
+ /// Extended flags - add flags for serialization here and offset them down to u16.
+ pub struct FlagsExtended: u16 {
+ const INTENT_TO_ADD = 1 << (29 - 16);
+ const SKIP_WORKTREE = 1 << (30 - 16);
+ }
+ }
+
+ impl FlagsExtended {
+ pub fn from_flags(flags: super::Flags) -> Self {
+ Self::from_bits(((flags & (super::Flags::INTENT_TO_ADD | super::Flags::SKIP_WORKTREE)).bits >> 16) as u16)
+ .expect("valid")
+ }
+ pub fn to_flags(self) -> Option<super::Flags> {
+ super::Flags::from_bits((self.bits as u32) << 16)
+ }
+ }
+
+ #[cfg(test)]
+ mod tests {
+ use super::*;
+
+ #[test]
+ fn flags_from_bits_with_conflict() {
+ let input = 0b1110_0010_1000_1011;
+ assert_eq!(Flags::from_bits(input).unwrap().bits, input);
+ }
+ }
+}
diff --git a/vendor/gix-index/src/entry/mod.rs b/vendor/gix-index/src/entry/mod.rs
new file mode 100644
index 000000000..165df801e
--- /dev/null
+++ b/vendor/gix-index/src/entry/mod.rs
@@ -0,0 +1,109 @@
+/// The stage of an entry, one of 0 = base, 1 = ours, 2 = theirs
+pub type Stage = u32;
+
+mod mode;
+pub use mode::Mode;
+
+mod flags;
+pub(crate) use flags::at_rest;
+pub use flags::Flags;
+
+mod write;
+
+/// The time component in a [`Stat`] struct.
+#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub struct Time {
+ /// The amount of seconds elapsed since EPOCH
+ pub secs: u32,
+ /// The amount of nanoseconds elapsed in the current second, ranging from 0 to 999.999.999 .
+ pub nsecs: u32,
+}
+
+/// An entry's filesystem stat information.
+#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub struct Stat {
+ /// Modification time
+ pub mtime: Time,
+ /// Creation time
+ pub ctime: Time,
+ /// Device number
+ pub dev: u32,
+ /// Inode number
+ pub ino: u32,
+ /// User id of the owner
+ pub uid: u32,
+ /// Group id of the owning group
+ pub gid: u32,
+ /// The size of bytes on disk. Capped to u32 so files bigger than that will need thorough additional checking
+ pub size: u32,
+}
+
+mod access {
+ use bstr::{BStr, ByteSlice};
+
+ use crate::{entry, Entry, State};
+
+ impl Entry {
+ /// Return an entry's path, relative to the repository, which is extracted from its owning `state`.
+ pub fn path<'a>(&self, state: &'a State) -> &'a BStr {
+ state.path_backing[self.path.clone()].as_bstr()
+ }
+
+ /// Return an entry's path using the given `backing`.
+ pub fn path_in<'backing>(&self, backing: &'backing crate::PathStorageRef) -> &'backing BStr {
+ backing[self.path.clone()].as_bstr()
+ }
+
+ /// Return an entry's stage.
+ pub fn stage(&self) -> entry::Stage {
+ self.flags.stage()
+ }
+ }
+}
+
+mod _impls {
+ use std::{cmp::Ordering, ops::Add, time::SystemTime};
+
+ use bstr::BStr;
+
+ use crate::{entry::Time, Entry, State};
+
+ impl From<SystemTime> for Time {
+ fn from(s: SystemTime) -> Self {
+ let d = s
+ .duration_since(std::time::UNIX_EPOCH)
+ .expect("system time is not before unix epoch!");
+ Time {
+ secs: d.as_secs() as u32,
+ nsecs: d.subsec_nanos(),
+ }
+ }
+ }
+
+ impl From<Time> for SystemTime {
+ fn from(s: Time) -> Self {
+ std::time::UNIX_EPOCH.add(std::time::Duration::new(s.secs.into(), s.nsecs))
+ }
+ }
+
+ impl Entry {
+ /// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
+ /// entry length and stage.
+ pub fn cmp(&self, other: &Self, state: &State) -> Ordering {
+ let lhs = self.path(state);
+ let rhs = other.path(state);
+ Entry::cmp_filepaths(lhs, rhs).then_with(|| self.stage().cmp(&other.stage()))
+ }
+
+ /// Compare one entry to another by their path, by comparing only their common path portion byte by byte, then resorting to
+ /// entry length.
+ pub fn cmp_filepaths(a: &BStr, b: &BStr) -> Ordering {
+ let common_len = a.len().min(b.len());
+ a[..common_len]
+ .cmp(&b[..common_len])
+ .then_with(|| a.len().cmp(&b.len()))
+ }
+ }
+}
diff --git a/vendor/gix-index/src/entry/mode.rs b/vendor/gix-index/src/entry/mode.rs
new file mode 100644
index 000000000..1045dfd5b
--- /dev/null
+++ b/vendor/gix-index/src/entry/mode.rs
@@ -0,0 +1,24 @@
+use bitflags::bitflags;
+bitflags! {
+ /// The kind of file of an entry.
+ pub struct Mode: u32 {
+ /// directory (only used for sparse checkouts), equivalent to a tree, which is _excluded_ from the index via
+ /// cone-mode.
+ const DIR = 0o040000;
+ /// regular file
+ const FILE = 0o100644;
+ /// regular file, executable
+ const FILE_EXECUTABLE = 0o100755;
+ /// Symbolic link
+ const SYMLINK = 0o120000;
+ /// A git commit for submodules
+ const COMMIT = 0o160000;
+ }
+}
+
+impl Mode {
+ /// Return true if this is a sparse entry, as it points to a directory which usually isn't what an unsparse index tracks.
+ pub fn is_sparse(&self) -> bool {
+ *self == Self::DIR
+ }
+}
diff --git a/vendor/gix-index/src/entry/write.rs b/vendor/gix-index/src/entry/write.rs
new file mode 100644
index 000000000..2a6ad5588
--- /dev/null
+++ b/vendor/gix-index/src/entry/write.rs
@@ -0,0 +1,39 @@
+use std::convert::TryInto;
+
+use crate::{entry, Entry, State};
+
+impl Entry {
+ /// Serialize ourselves to `out` with path access via `state`, without padding.
+ pub fn write_to(&self, mut out: impl std::io::Write, state: &State) -> std::io::Result<()> {
+ let stat = self.stat;
+ out.write_all(&stat.ctime.secs.to_be_bytes())?;
+ out.write_all(&stat.ctime.nsecs.to_be_bytes())?;
+ out.write_all(&stat.mtime.secs.to_be_bytes())?;
+ out.write_all(&stat.mtime.nsecs.to_be_bytes())?;
+ out.write_all(&stat.dev.to_be_bytes())?;
+ out.write_all(&stat.ino.to_be_bytes())?;
+ out.write_all(&self.mode.bits().to_be_bytes())?;
+ out.write_all(&stat.uid.to_be_bytes())?;
+ out.write_all(&stat.gid.to_be_bytes())?;
+ out.write_all(&stat.size.to_be_bytes())?;
+ out.write_all(self.id.as_bytes())?;
+ let path = self.path(state);
+ let path_len: u16 = if path.len() >= entry::Flags::PATH_LEN.bits() as usize {
+ entry::Flags::PATH_LEN.bits() as u16
+ } else {
+ path.len()
+ .try_into()
+ .expect("we just checked that the length is smaller than 0xfff")
+ };
+ out.write_all(&(self.flags.to_storage().bits() | path_len).to_be_bytes())?;
+ if self.flags.contains(entry::Flags::EXTENDED) {
+ out.write_all(
+ &entry::at_rest::FlagsExtended::from_flags(self.flags)
+ .bits()
+ .to_be_bytes(),
+ )?;
+ }
+ out.write_all(path)?;
+ out.write_all(b"\0")
+ }
+}
diff --git a/vendor/gix-index/src/extension/decode.rs b/vendor/gix-index/src/extension/decode.rs
new file mode 100644
index 000000000..af032f4e3
--- /dev/null
+++ b/vendor/gix-index/src/extension/decode.rs
@@ -0,0 +1,80 @@
+use std::convert::TryInto;
+
+use crate::{extension, extension::Signature, util::from_be_u32};
+
+pub(crate) fn header(data: &[u8]) -> (Signature, u32, &[u8]) {
+ let (signature, data) = data.split_at(4);
+ let (size, data) = data.split_at(4);
+ (signature.try_into().unwrap(), from_be_u32(size), data)
+}
+
+mod error {
+ use crate::extension;
+
+ /// The error returned when decoding extensions.
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error(
+ "Encountered mandatory extension '{}' which isn't implemented yet",
+ String::from_utf8_lossy(signature)
+ )]
+ MandatoryUnimplemented { signature: extension::Signature },
+ #[error("Could not parse mandatory link extension")]
+ Link(#[from] extension::link::decode::Error),
+ }
+}
+pub use error::Error;
+
+pub(crate) fn all(
+ maybe_beginning_of_extensions: &[u8],
+ object_hash: gix_hash::Kind,
+) -> Result<(Outcome, &[u8]), Error> {
+ let mut ext_iter = match extension::Iter::new_without_checksum(maybe_beginning_of_extensions, object_hash) {
+ Some(iter) => iter,
+ None => return Ok((Outcome::default(), maybe_beginning_of_extensions)),
+ };
+
+ let mut ext = Outcome::default();
+ for (signature, ext_data) in ext_iter.by_ref() {
+ match signature {
+ extension::tree::SIGNATURE => {
+ ext.tree = extension::tree::decode(ext_data, object_hash);
+ }
+ extension::resolve_undo::SIGNATURE => {
+ ext.resolve_undo = extension::resolve_undo::decode(ext_data, object_hash);
+ }
+ extension::untracked_cache::SIGNATURE => {
+ ext.untracked = extension::untracked_cache::decode(ext_data, object_hash);
+ }
+ extension::fs_monitor::SIGNATURE => {
+ ext.fs_monitor = extension::fs_monitor::decode(ext_data);
+ }
+ extension::end_of_index_entry::SIGNATURE => {} // skip already done
+ extension::index_entry_offset_table::SIGNATURE => {} // not relevant/obtained already
+ mandatory if mandatory[0].is_ascii_lowercase() => match mandatory {
+ extension::link::SIGNATURE => ext.link = extension::link::decode(ext_data, object_hash)?.into(),
+ extension::sparse::SIGNATURE => {
+ if !ext_data.is_empty() {
+ // only used as a marker, if this changes we need this implementation.
+ return Err(Error::MandatoryUnimplemented { signature: mandatory });
+ }
+ ext.is_sparse = true
+ }
+ unknown => return Err(Error::MandatoryUnimplemented { signature: unknown }),
+ },
+ _unknown => {} // skip unknown extensions, too
+ }
+ }
+ Ok((ext, &maybe_beginning_of_extensions[ext_iter.consumed..]))
+}
+
+#[derive(Default)]
+pub(crate) struct Outcome {
+ pub tree: Option<extension::Tree>,
+ pub link: Option<extension::Link>,
+ pub resolve_undo: Option<extension::resolve_undo::Paths>,
+ pub untracked: Option<extension::UntrackedCache>,
+ pub fs_monitor: Option<extension::FsMonitor>,
+ pub is_sparse: bool,
+}
diff --git a/vendor/gix-index/src/extension/end_of_index_entry/decode.rs b/vendor/gix-index/src/extension/end_of_index_entry/decode.rs
new file mode 100644
index 000000000..f8002ab7c
--- /dev/null
+++ b/vendor/gix-index/src/extension/end_of_index_entry/decode.rs
@@ -0,0 +1,56 @@
+use crate::{
+ decode::header,
+ extension,
+ extension::end_of_index_entry::{MIN_SIZE, MIN_SIZE_WITH_HEADER, SIGNATURE},
+ util::from_be_u32,
+};
+
+/// Decode the end of index entry extension, which is no more than a glorified offset to the first byte of all extensions to allow
+/// loading entries and extensions in parallel.
+///
+/// Itself it's located at the end of the index file, which allows its location to be known and thus addressable.
+/// From there it's possible to traverse the chunks of all set extensions, hash them, and compare that hash with all extensions
+/// stored prior to this one to assure they are correct.
+///
+/// If the checksum wasn't matched, we will ignore this extension entirely.
+pub fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Option<usize> {
+ let hash_len = object_hash.len_in_bytes();
+ if data.len() < MIN_SIZE_WITH_HEADER + hash_len {
+ return None;
+ }
+
+ let start_of_eoie = data.len() - MIN_SIZE_WITH_HEADER - hash_len;
+ let ext_data = &data[start_of_eoie..data.len() - hash_len];
+
+ let (signature, ext_size, ext_data) = extension::decode::header(ext_data);
+ if signature != SIGNATURE || ext_size as usize != MIN_SIZE {
+ return None;
+ }
+
+ let (offset, checksum) = ext_data.split_at(4);
+ let offset = from_be_u32(offset) as usize;
+ if offset < header::SIZE || offset > start_of_eoie || checksum.len() != gix_hash::Kind::Sha1.len_in_bytes() {
+ return None;
+ }
+
+ let mut hasher = gix_features::hash::hasher(gix_hash::Kind::Sha1);
+ let mut last_chunk = None;
+ for (signature, chunk) in extension::Iter::new(&data[offset..data.len() - MIN_SIZE_WITH_HEADER - hash_len]) {
+ hasher.update(&signature);
+ hasher.update(&(chunk.len() as u32).to_be_bytes());
+ last_chunk = Some(chunk);
+ }
+
+ if hasher.digest() != checksum {
+ return None;
+ }
+ // The last-to-this chunk ends where ours starts
+ if last_chunk
+ .map(|s| s.as_ptr_range().end != (&data[start_of_eoie]) as *const _)
+ .unwrap_or(true)
+ {
+ return None;
+ }
+
+ Some(offset)
+}
diff --git a/vendor/gix-index/src/extension/end_of_index_entry/mod.rs b/vendor/gix-index/src/extension/end_of_index_entry/mod.rs
new file mode 100644
index 000000000..d55496aee
--- /dev/null
+++ b/vendor/gix-index/src/extension/end_of_index_entry/mod.rs
@@ -0,0 +1,14 @@
+use crate::{extension, extension::Signature};
+
+/// The signature of the end-of-index-entry extension
+pub const SIGNATURE: Signature = *b"EOIE";
+/// The minimal size of the extension, depending on the shortest hash.
+pub const MIN_SIZE: usize = 4 /* offset to extensions */ + gix_hash::Kind::shortest().len_in_bytes();
+/// The smallest size of the extension varying by hash kind, along with the standard extension header.
+pub const MIN_SIZE_WITH_HEADER: usize = extension::MIN_SIZE + MIN_SIZE;
+
+mod decode;
+pub use decode::decode;
+
+mod write;
+pub use write::write_to;
diff --git a/vendor/gix-index/src/extension/end_of_index_entry/write.rs b/vendor/gix-index/src/extension/end_of_index_entry/write.rs
new file mode 100644
index 000000000..6752bd238
--- /dev/null
+++ b/vendor/gix-index/src/extension/end_of_index_entry/write.rs
@@ -0,0 +1,29 @@
+use crate::extension::{end_of_index_entry::SIGNATURE, Signature};
+
+/// Write this extension to out and generate a hash of `hash_kind` over all `prior_extensions` which are specified as `(signature, size)`
+/// pair. `one_past_entries` is the offset to the first byte past the entries, which is also the first byte of the signature of the
+/// first extension in `prior_extensions`. Note that `prior_extensions` must have been written prior to this one, as the name suggests,
+/// allowing this extension to be the last one in the index file.
+///
+/// Even if there are no `prior_extensions`, this extension will be written unconditionally.
+pub fn write_to(
+ mut out: impl std::io::Write,
+ hash_kind: gix_hash::Kind,
+ offset_to_extensions: u32,
+ prior_extensions: impl IntoIterator<Item = (Signature, u32)>,
+) -> Result<(), std::io::Error> {
+ out.write_all(&SIGNATURE)?;
+ let extension_size: u32 = 4 + hash_kind.len_in_bytes() as u32;
+ out.write_all(&extension_size.to_be_bytes())?;
+
+ out.write_all(&offset_to_extensions.to_be_bytes())?;
+
+ let mut hasher = gix_features::hash::hasher(hash_kind);
+ for (signature, size) in prior_extensions {
+ hasher.update(&signature);
+ hasher.update(&size.to_be_bytes());
+ }
+ out.write_all(&hasher.digest())?;
+
+ Ok(())
+}
diff --git a/vendor/gix-index/src/extension/fs_monitor.rs b/vendor/gix-index/src/extension/fs_monitor.rs
new file mode 100644
index 000000000..4bb5016ff
--- /dev/null
+++ b/vendor/gix-index/src/extension/fs_monitor.rs
@@ -0,0 +1,38 @@
+use bstr::BString;
+
+use crate::{
+ extension::{FsMonitor, Signature},
+ util::{read_u32, read_u64, split_at_byte_exclusive},
+};
+
+#[derive(Clone)]
+pub enum Token {
+ V1 { nanos_since_1970: u64 },
+ V2 { token: BString },
+}
+
+pub const SIGNATURE: Signature = *b"FSMN";
+
+pub fn decode(data: &[u8]) -> Option<FsMonitor> {
+ let (version, data) = read_u32(data)?;
+ let (token, data) = match version {
+ 1 => {
+ let (nanos_since_1970, data) = read_u64(data)?;
+ (Token::V1 { nanos_since_1970 }, data)
+ }
+ 2 => {
+ let (token, data) = split_at_byte_exclusive(data, 0)?;
+ (Token::V2 { token: token.into() }, data)
+ }
+ _ => return None,
+ };
+
+ let (ewah_size, data) = read_u32(data)?;
+ let (entry_dirty, data) = gix_bitmap::ewah::decode(&data[..ewah_size as usize]).ok()?;
+
+ if !data.is_empty() {
+ return None;
+ }
+
+ FsMonitor { token, entry_dirty }.into()
+}
diff --git a/vendor/gix-index/src/extension/index_entry_offset_table.rs b/vendor/gix-index/src/extension/index_entry_offset_table.rs
new file mode 100644
index 000000000..0a0108adf
--- /dev/null
+++ b/vendor/gix-index/src/extension/index_entry_offset_table.rs
@@ -0,0 +1,43 @@
+use crate::{extension, extension::Signature, util::read_u32};
+
+#[derive(Debug, Clone, Copy)]
+pub struct Offset {
+ pub from_beginning_of_file: u32,
+ pub num_entries: u32,
+}
+
+pub const SIGNATURE: Signature = *b"IEOT";
+
+pub fn decode(data: &[u8]) -> Option<Vec<Offset>> {
+ let (version, mut data) = read_u32(data)?;
+ match version {
+ 1 => {}
+ _unknown => return None,
+ }
+
+ let entry_size = 4 + 4;
+ let num_offsets = data.len() / entry_size;
+ if num_offsets == 0 || data.len() % entry_size != 0 {
+ return None;
+ }
+
+ let mut out = Vec::with_capacity(entry_size);
+ for _ in 0..num_offsets {
+ let (offset, chunk) = read_u32(data)?;
+ let (num_entries, chunk) = read_u32(chunk)?;
+ out.push(Offset {
+ from_beginning_of_file: offset,
+ num_entries,
+ });
+ data = chunk;
+ }
+ debug_assert!(data.is_empty());
+
+ out.into()
+}
+
+pub fn find(extensions: &[u8], object_hash: gix_hash::Kind) -> Option<Vec<Offset>> {
+ extension::Iter::new_without_checksum(extensions, object_hash)?
+ .find_map(|(sig, ext_data)| (sig == SIGNATURE).then_some(ext_data))
+ .and_then(decode)
+}
diff --git a/vendor/gix-index/src/extension/iter.rs b/vendor/gix-index/src/extension/iter.rs
new file mode 100644
index 000000000..f791b064e
--- /dev/null
+++ b/vendor/gix-index/src/extension/iter.rs
@@ -0,0 +1,58 @@
+use std::convert::TryInto;
+
+use crate::{extension, extension::Iter, util::from_be_u32};
+
+impl<'a> Iter<'a> {
+ /// Create a new extension iterator at the entrypoint for extensions until the end of the extensions.
+ pub fn new(data_at_beginning_of_extensions_and_truncated: &'a [u8]) -> Self {
+ Iter {
+ data: data_at_beginning_of_extensions_and_truncated,
+ consumed: 0,
+ }
+ }
+
+ /// Create a new iterator at with a data block to the end of the file, and we automatically remove the trailing
+ /// hash of type `object_hash`.
+ pub fn new_without_checksum(
+ data_at_beginning_of_extensions: &'a [u8],
+ object_hash: gix_hash::Kind,
+ ) -> Option<Self> {
+ let end = data_at_beginning_of_extensions
+ .len()
+ .checked_sub(object_hash.len_in_bytes())?;
+ Iter {
+ data: &data_at_beginning_of_extensions[..end],
+ consumed: 0,
+ }
+ .into()
+ }
+}
+
+impl<'a> Iterator for Iter<'a> {
+ type Item = (extension::Signature, &'a [u8]);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.data.len() < 4 + 4 {
+ return None;
+ }
+
+ let (signature, data) = self.data.split_at(4);
+ let (size, data) = data.split_at(4);
+ self.data = data;
+ self.consumed += 4 + 4;
+
+ let size = from_be_u32(size) as usize;
+
+ match data.get(..size) {
+ Some(ext_data) => {
+ self.data = &data[size..];
+ self.consumed += size;
+ Some((signature.try_into().unwrap(), ext_data))
+ }
+ None => {
+ self.data = &[];
+ None
+ }
+ }
+ }
+}
diff --git a/vendor/gix-index/src/extension/link.rs b/vendor/gix-index/src/extension/link.rs
new file mode 100644
index 000000000..20ce9cb21
--- /dev/null
+++ b/vendor/gix-index/src/extension/link.rs
@@ -0,0 +1,177 @@
+use crate::{
+ extension::{Link, Signature},
+ util::split_at_pos,
+};
+
+/// The signature of the link extension.
+pub const SIGNATURE: Signature = *b"link";
+
+/// Bitmaps to know which entries to delete or replace, even though details are still unknown.
+#[derive(Clone)]
+pub struct Bitmaps {
+ /// A bitmap to signal which entries to delete, maybe.
+ pub delete: gix_bitmap::ewah::Vec,
+ /// A bitmap to signal which entries to replace, maybe.
+ pub replace: gix_bitmap::ewah::Vec,
+}
+
+///
+pub mod decode {
+
+ /// The error returned when decoding link extensions.
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("{0}")]
+ Corrupt(&'static str),
+ #[error("{kind} bitmap corrupt")]
+ BitmapDecode {
+ err: gix_bitmap::ewah::decode::Error,
+ kind: &'static str,
+ },
+ }
+
+ impl From<std::num::TryFromIntError> for Error {
+ fn from(_: std::num::TryFromIntError) -> Self {
+ Self::Corrupt("error in bitmap iteration trying to convert from u64 to usize")
+ }
+ }
+}
+
+pub(crate) fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Result<Link, decode::Error> {
+ let (id, data) = split_at_pos(data, object_hash.len_in_bytes())
+ .ok_or(decode::Error::Corrupt(
+ "link extension too short to read share index checksum",
+ ))
+ .map(|(id, d)| (gix_hash::ObjectId::from(id), d))?;
+
+ if data.is_empty() {
+ return Ok(Link {
+ shared_index_checksum: id,
+ bitmaps: None,
+ });
+ }
+
+ let (delete, data) =
+ gix_bitmap::ewah::decode(data).map_err(|err| decode::Error::BitmapDecode { kind: "delete", err })?;
+ let (replace, data) =
+ gix_bitmap::ewah::decode(data).map_err(|err| decode::Error::BitmapDecode { kind: "replace", err })?;
+
+ if !data.is_empty() {
+ return Err(decode::Error::Corrupt("garbage trailing link extension"));
+ }
+
+ Ok(Link {
+ shared_index_checksum: id,
+ bitmaps: Some(Bitmaps { delete, replace }),
+ })
+}
+
+impl Link {
+ pub(crate) fn dissolve_into(
+ self,
+ split_index: &mut crate::File,
+ object_hash: gix_hash::Kind,
+ options: crate::decode::Options,
+ ) -> Result<(), crate::file::init::Error> {
+ let shared_index_path = split_index
+ .path
+ .parent()
+ .expect("split index file in .git folder")
+ .join(format!("sharedindex.{}", self.shared_index_checksum));
+ let mut shared_index = crate::File::at(
+ &shared_index_path,
+ object_hash,
+ crate::decode::Options {
+ expected_checksum: self.shared_index_checksum.into(),
+ ..options
+ },
+ )?;
+
+ if let Some(bitmaps) = self.bitmaps {
+ let mut split_entry_index = 0;
+
+ let mut err = None;
+ bitmaps.replace.for_each_set_bit(|replace_index| {
+ let shared_entry = match shared_index.entries.get_mut(replace_index) {
+ Some(e) => e,
+ None => {
+ err = decode::Error::Corrupt("replace bitmap length exceeds shared index length - more entries in bitmap than found in shared index").into();
+ return None
+ }
+ };
+
+ if shared_entry.flags.contains(crate::entry::Flags::REMOVE) {
+ err = decode::Error::Corrupt("entry is marked as both replace and delete").into();
+ return None
+ }
+
+ let split_entry = match split_index.entries.get(split_entry_index) {
+ Some(e) => e,
+ None => {
+ err = decode::Error::Corrupt("replace bitmap length exceeds split index length - more entries in bitmap than found in split index").into();
+ return None
+ }
+ };
+ if !split_entry.path.is_empty() {
+ err = decode::Error::Corrupt("paths in split index entries that are for replacement should be empty").into();
+ return None
+ }
+ if shared_entry.path.is_empty() {
+ err = decode::Error::Corrupt("paths in shared index entries that are replaced should not be empty").into();
+ return None
+ }
+ shared_entry.stat = split_entry.stat;
+ shared_entry.id = split_entry.id;
+ shared_entry.flags = split_entry.flags;
+ shared_entry.mode = split_entry.mode;
+
+ split_entry_index += 1;
+ Some(())
+ });
+ if let Some(err) = err {
+ return Err(err.into());
+ }
+
+ let split_index_path_backing = std::mem::take(&mut split_index.path_backing);
+ for mut split_entry in split_index.entries.drain(split_entry_index..) {
+ let start = shared_index.path_backing.len();
+ let split_index_path = split_entry.path.clone();
+
+ split_entry.path = start..start + split_entry.path.len();
+ shared_index.entries.push(split_entry);
+
+ shared_index
+ .path_backing
+ .extend_from_slice(&split_index_path_backing[split_index_path]);
+ }
+
+ bitmaps.delete.for_each_set_bit(|delete_index| {
+ let shared_entry = match shared_index.entries.get_mut(delete_index) {
+ Some(e) => e,
+ None => {
+ err = decode::Error::Corrupt("delete bitmap length exceeds shared index length - more entries in bitmap than found in shared index").into();
+ return None
+ }
+ };
+ shared_entry.flags.insert(crate::entry::Flags::REMOVE);
+ Some(())
+ });
+ if let Some(err) = err {
+ return Err(err.into());
+ }
+
+ shared_index
+ .entries
+ .retain(|e| !e.flags.contains(crate::entry::Flags::REMOVE));
+
+ let mut shared_entries = std::mem::take(&mut shared_index.entries);
+ shared_entries.sort_by(|a, b| a.cmp(b, &shared_index.state));
+
+ split_index.entries = shared_entries;
+ split_index.path_backing = std::mem::take(&mut shared_index.path_backing);
+ }
+
+ Ok(())
+ }
+}
diff --git a/vendor/gix-index/src/extension/mod.rs b/vendor/gix-index/src/extension/mod.rs
new file mode 100644
index 000000000..07edfdfe0
--- /dev/null
+++ b/vendor/gix-index/src/extension/mod.rs
@@ -0,0 +1,96 @@
+use bstr::BString;
+use smallvec::SmallVec;
+
+/// The size of the smallest possible extension, which is no more than a signature and a 0 indicating its size.
+pub const MIN_SIZE: usize = 4 /* signature */ + 4 /* size */;
+
+/// The kind of index extension.
+pub type Signature = [u8; 4];
+
+/// An iterator over the data of index extensions.
+pub struct Iter<'a> {
+ data: &'a [u8],
+ /// The amount of consumed bytes as seen from our internal data pointer. Useful to continue where the iterator left off.
+ pub consumed: usize,
+}
+
+/// A structure to associate object ids of a tree with sections in the index entries list.
+///
+/// It allows to more quickly build trees by avoiding as it can quickly re-use portions of the index and its associated tree ids
+/// if there was no change to them. Portions of this tree are invalidated as the index is changed.
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct Tree {
+ /// The name of the tree/directory, or empty if it's the root tree.
+ pub name: SmallVec<[u8; 23]>,
+ /// The id of the directory tree of the associated tree object.
+ pub id: gix_hash::ObjectId,
+ /// The amount of non-tree items in this directory tree, including sub-trees, recursively.
+ /// The value of the top-level tree is thus equal to the value of the total amount of entries.
+ /// If `None`, the tree is considered invalid and needs to be refreshed
+ pub num_entries: Option<u32>,
+ /// The child-trees below the current tree.
+ pub children: Vec<Tree>,
+}
+
+/// The link extension to track a shared index.
+#[derive(Clone)]
+pub struct Link {
+ /// The checksum of the shared index as last seen.
+ pub shared_index_checksum: gix_hash::ObjectId,
+ /// Bitmaps to tell us which entries to delete or replace.
+ pub bitmaps: Option<link::Bitmaps>,
+}
+
+/// The extension for untracked files.
+#[allow(dead_code)]
+#[derive(Clone)]
+pub struct UntrackedCache {
+ /// Something identifying the location and machine that this cache is for.
+ /// Should the repository be copied to a different machine, the entire cache can immediately be invalidated.
+ identifier: BString,
+ /// Stat for the .git/info/exclude file
+ info_exclude: Option<untracked_cache::OidStat>,
+ /// Stat for the `core.excludesfile`
+ excludes_file: Option<untracked_cache::OidStat>,
+ /// Usually `.gitignore`
+ exclude_filename_per_dir: BString,
+ dir_flags: u32,
+
+ /// A list of directories and sub-directories, with `directories[0]` being the root.
+ directories: Vec<untracked_cache::Directory>,
+}
+
+/// The extension for keeping state on recent information provided by the filesystem monitor.
+#[allow(dead_code)]
+#[derive(Clone)]
+pub struct FsMonitor {
+ token: fs_monitor::Token,
+ /// if a bit is true, the respective entry is NOT valid as per the fs monitor.
+ entry_dirty: gix_bitmap::ewah::Vec,
+}
+
+mod iter;
+
+pub(crate) mod fs_monitor;
+
+///
+pub mod decode;
+
+///
+pub mod tree;
+
+///
+pub mod end_of_index_entry;
+
+pub(crate) mod index_entry_offset_table;
+
+///
+pub mod link;
+
+pub(crate) mod resolve_undo;
+
+///
+pub mod untracked_cache;
+
+///
+pub mod sparse;
diff --git a/vendor/gix-index/src/extension/resolve_undo.rs b/vendor/gix-index/src/extension/resolve_undo.rs
new file mode 100644
index 000000000..eb6db9ad7
--- /dev/null
+++ b/vendor/gix-index/src/extension/resolve_undo.rs
@@ -0,0 +1,64 @@
+use bstr::BString;
+use gix_hash::ObjectId;
+
+use crate::{
+ extension::Signature,
+ util::{split_at_byte_exclusive, split_at_pos},
+};
+
+pub type Paths = Vec<ResolvePath>;
+
+#[allow(dead_code)]
+#[derive(Clone)]
+pub struct ResolvePath {
+ /// relative to the root of the repository, or what would be stored in the index
+ name: BString,
+
+ /// 0 = ancestor/common, 1 = ours, 2 = theirs
+ stages: [Option<Stage>; 3],
+}
+
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub struct Stage {
+ mode: u32,
+ id: ObjectId,
+}
+
+pub const SIGNATURE: Signature = *b"REUC";
+
+pub fn decode(mut data: &[u8], object_hash: gix_hash::Kind) -> Option<Paths> {
+ let hash_len = object_hash.len_in_bytes();
+ let mut out = Vec::new();
+
+ while !data.is_empty() {
+ let (path, rest) = split_at_byte_exclusive(data, 0)?;
+ data = rest;
+
+ let mut modes = [0u32; 3];
+ for mode in modes.iter_mut() {
+ let (mode_ascii, rest) = split_at_byte_exclusive(data, 0)?;
+ data = rest;
+ *mode = u32::from_str_radix(std::str::from_utf8(mode_ascii).ok()?, 8).ok()?;
+ }
+
+ let mut stages = [None, None, None];
+ for (mode, stage) in modes.iter().zip(stages.iter_mut()) {
+ if *mode == 0 {
+ continue;
+ }
+ let (hash, rest) = split_at_pos(data, hash_len)?;
+ data = rest;
+ *stage = Some(Stage {
+ mode: *mode,
+ id: ObjectId::from(hash),
+ });
+ }
+
+ out.push(ResolvePath {
+ name: path.into(),
+ stages,
+ });
+ }
+ out.into()
+}
diff --git a/vendor/gix-index/src/extension/sparse.rs b/vendor/gix-index/src/extension/sparse.rs
new file mode 100644
index 000000000..ab219c8d6
--- /dev/null
+++ b/vendor/gix-index/src/extension/sparse.rs
@@ -0,0 +1,11 @@
+use crate::extension::Signature;
+
+/// The signature of the sparse index extension, nothing more than an indicator at this time.
+pub const SIGNATURE: Signature = *b"sdir";
+
+/// Serialize the sparse index extension to `out`
+pub fn write_to(mut out: impl std::io::Write) -> Result<(), std::io::Error> {
+ out.write_all(&SIGNATURE)?;
+ out.write_all(&0_u32.to_be_bytes())?;
+ Ok(())
+}
diff --git a/vendor/gix-index/src/extension/tree/decode.rs b/vendor/gix-index/src/extension/tree/decode.rs
new file mode 100644
index 000000000..82221b48c
--- /dev/null
+++ b/vendor/gix-index/src/extension/tree/decode.rs
@@ -0,0 +1,63 @@
+use std::convert::TryInto;
+
+use gix_hash::ObjectId;
+
+use crate::{
+ extension::Tree,
+ util::{split_at_byte_exclusive, split_at_pos},
+};
+
+/// A recursive data structure
+pub fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Option<Tree> {
+ let (tree, data) = one_recursive(data, object_hash.len_in_bytes())?;
+ assert!(
+ data.is_empty(),
+ "BUG: should fully consume the entire tree extension chunk, got {} left",
+ data.len()
+ );
+ Some(tree)
+}
+
+fn one_recursive(data: &[u8], hash_len: usize) -> Option<(Tree, &[u8])> {
+ let (path, data) = split_at_byte_exclusive(data, 0)?;
+
+ let (entry_count, data) = split_at_byte_exclusive(data, b' ')?;
+ let num_entries: i32 = btoi::btoi(entry_count).ok()?;
+
+ let (subtree_count, data) = split_at_byte_exclusive(data, b'\n')?;
+ let subtree_count: usize = btoi::btou(subtree_count).ok()?;
+
+ let (id, mut data) = if num_entries >= 0 {
+ let (hash, data) = split_at_pos(data, hash_len)?;
+ (ObjectId::from(hash), data)
+ } else {
+ (
+ ObjectId::null(gix_hash::Kind::from_hex_len(hash_len * 2).expect("valid hex_len")),
+ data,
+ )
+ };
+
+ let mut subtrees = Vec::with_capacity(subtree_count);
+ for _ in 0..subtree_count {
+ let (tree, rest) = one_recursive(data, hash_len)?;
+ subtrees.push(tree);
+ data = rest;
+ }
+
+ subtrees.sort_by(|a, b| a.name.cmp(&b.name));
+ let num_trees = subtrees.len();
+ subtrees.dedup_by(|a, b| a.name == b.name);
+ if num_trees != subtrees.len() {
+ return None;
+ }
+
+ Some((
+ Tree {
+ id,
+ num_entries: num_entries.try_into().ok(),
+ name: path.into(),
+ children: subtrees,
+ },
+ data,
+ ))
+}
diff --git a/vendor/gix-index/src/extension/tree/mod.rs b/vendor/gix-index/src/extension/tree/mod.rs
new file mode 100644
index 000000000..f20759399
--- /dev/null
+++ b/vendor/gix-index/src/extension/tree/mod.rs
@@ -0,0 +1,21 @@
+use crate::extension::Signature;
+
+/// The signature for tree extensions
+pub const SIGNATURE: Signature = *b"TREE";
+
+///
+pub mod verify;
+
+mod decode;
+pub use decode::decode;
+
+mod write;
+
+#[cfg(test)]
+mod tests {
+
+ #[test]
+ fn size_of_tree() {
+ assert_eq!(std::mem::size_of::<crate::extension::Tree>(), 88);
+ }
+}
diff --git a/vendor/gix-index/src/extension/tree/verify.rs b/vendor/gix-index/src/extension/tree/verify.rs
new file mode 100644
index 000000000..82fd03c8c
--- /dev/null
+++ b/vendor/gix-index/src/extension/tree/verify.rs
@@ -0,0 +1,134 @@
+use std::cmp::Ordering;
+
+use bstr::{BString, ByteSlice};
+
+use crate::extension::Tree;
+
+/// The error returned by [Tree::verify()][crate::extension::Tree::verify()].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error("The entry {entry_id} at path '{name}' in parent tree {parent_id} wasn't found in the nodes children, making it incomplete")]
+ MissingTreeDirectory {
+ parent_id: gix_hash::ObjectId,
+ entry_id: gix_hash::ObjectId,
+ name: BString,
+ },
+ #[error("The tree with id {oid} wasn't found in the object database")]
+ TreeNodeNotFound { oid: gix_hash::ObjectId },
+ #[error("The tree with id {oid} should have {expected_childcount} children, but its cached representation had {actual_childcount} of them")]
+ TreeNodeChildcountMismatch {
+ oid: gix_hash::ObjectId,
+ expected_childcount: usize,
+ actual_childcount: usize,
+ },
+ #[error("The root tree was named '{name}', even though it should be empty")]
+ RootWithName { name: BString },
+ #[error(
+ "Expected not more than {expected} entries to be reachable from the top-level, but actual count was {actual}"
+ )]
+ EntriesCount { actual: u32, expected: u32 },
+ #[error(
+ "Parent tree '{parent_id}' contained out-of order trees prev = '{previous_path}' and next = '{current_path}'"
+ )]
+ OutOfOrder {
+ parent_id: gix_hash::ObjectId,
+ current_path: BString,
+ previous_path: BString,
+ },
+}
+
+impl Tree {
+ ///
+ pub fn verify<F>(&self, use_find: bool, mut find: F) -> Result<(), Error>
+ where
+ F: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Option<gix_object::TreeRefIter<'a>>,
+ {
+ fn verify_recursive<F>(
+ parent_id: gix_hash::ObjectId,
+ children: &[Tree],
+ mut find_buf: Option<&mut Vec<u8>>,
+ find: &mut F,
+ ) -> Result<Option<u32>, Error>
+ where
+ F: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Option<gix_object::TreeRefIter<'a>>,
+ {
+ if children.is_empty() {
+ return Ok(None);
+ }
+ let mut entries = 0;
+ let mut prev = None::<&Tree>;
+ for child in children {
+ entries += child.num_entries.unwrap_or(0);
+ if let Some(prev) = prev {
+ if prev.name.cmp(&child.name) != Ordering::Less {
+ return Err(Error::OutOfOrder {
+ parent_id,
+ previous_path: prev.name.as_bstr().into(),
+ current_path: child.name.as_bstr().into(),
+ });
+ }
+ }
+ prev = Some(child);
+ }
+ if let Some(buf) = find_buf.as_mut() {
+ let tree_entries = find(&parent_id, buf).ok_or(Error::TreeNodeNotFound { oid: parent_id })?;
+ let mut num_entries = 0;
+ for entry in tree_entries
+ .filter_map(Result::ok)
+ .filter(|e| e.mode == gix_object::tree::EntryMode::Tree)
+ {
+ children
+ .binary_search_by(|e| e.name.as_bstr().cmp(entry.filename))
+ .map_err(|_| Error::MissingTreeDirectory {
+ parent_id,
+ entry_id: entry.oid.to_owned(),
+ name: entry.filename.to_owned(),
+ })?;
+ num_entries += 1;
+ }
+
+ if num_entries != children.len() {
+ return Err(Error::TreeNodeChildcountMismatch {
+ oid: parent_id,
+ expected_childcount: num_entries,
+ actual_childcount: children.len(),
+ });
+ }
+ }
+ for child in children {
+ // This is actually needed here as it's a mut ref, which isn't copy. We do a re-borrow here.
+ #[allow(clippy::needless_option_as_deref)]
+ let actual_num_entries = verify_recursive(child.id, &child.children, find_buf.as_deref_mut(), find)?;
+ if let Some((actual, num_entries)) = actual_num_entries.zip(child.num_entries) {
+ if actual > num_entries {
+ return Err(Error::EntriesCount {
+ actual,
+ expected: num_entries,
+ });
+ }
+ }
+ }
+ Ok(entries.into())
+ }
+
+ if !self.name.is_empty() {
+ return Err(Error::RootWithName {
+ name: self.name.as_bstr().into(),
+ });
+ }
+
+ let mut buf = Vec::new();
+ let declared_entries = verify_recursive(self.id, &self.children, use_find.then_some(&mut buf), &mut find)?;
+ if let Some((actual, num_entries)) = declared_entries.zip(self.num_entries) {
+ if actual > num_entries {
+ return Err(Error::EntriesCount {
+ actual,
+ expected: num_entries,
+ });
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/vendor/gix-index/src/extension/tree/write.rs b/vendor/gix-index/src/extension/tree/write.rs
new file mode 100644
index 000000000..2b1e3d949
--- /dev/null
+++ b/vendor/gix-index/src/extension/tree/write.rs
@@ -0,0 +1,45 @@
+use std::convert::TryFrom;
+
+use crate::extension::{tree, Tree};
+
+impl Tree {
+ /// Serialize this instance to `out`.
+ pub fn write_to(&self, mut out: impl std::io::Write) -> Result<(), std::io::Error> {
+ fn tree_entry(out: &mut impl std::io::Write, tree: &Tree) -> Result<(), std::io::Error> {
+ let mut buf = itoa::Buffer::new();
+ let num_entries = match tree.num_entries {
+ Some(num_entries) => buf.format(num_entries),
+ None => buf.format(-1),
+ };
+
+ out.write_all(tree.name.as_slice())?;
+ out.write_all(b"\0")?;
+ out.write_all(num_entries.as_bytes())?;
+ out.write_all(b" ")?;
+ let num_children = buf.format(tree.children.len());
+ out.write_all(num_children.as_bytes())?;
+ out.write_all(b"\n")?;
+ if tree.num_entries.is_some() {
+ out.write_all(tree.id.as_bytes())?;
+ }
+
+ for child in &tree.children {
+ tree_entry(out, child)?;
+ }
+
+ Ok(())
+ }
+
+ let signature = tree::SIGNATURE;
+
+ let estimated_size = self.num_entries.unwrap_or(0) * (300 + 3 + 1 + 3 + 1 + 20);
+ let mut entries: Vec<u8> = Vec::with_capacity(estimated_size as usize);
+ tree_entry(&mut entries, self)?;
+
+ out.write_all(&signature)?;
+ out.write_all(&(u32::try_from(entries.len()).expect("less than 4GB tree extension")).to_be_bytes())?;
+ out.write_all(&entries)?;
+
+ Ok(())
+ }
+}
diff --git a/vendor/gix-index/src/extension/untracked_cache.rs b/vendor/gix-index/src/extension/untracked_cache.rs
new file mode 100644
index 000000000..9f72e0775
--- /dev/null
+++ b/vendor/gix-index/src/extension/untracked_cache.rs
@@ -0,0 +1,156 @@
+use std::convert::TryInto;
+
+use bstr::BString;
+use gix_hash::ObjectId;
+
+use crate::{
+ entry,
+ extension::{Signature, UntrackedCache},
+ util::{read_u32, split_at_byte_exclusive, split_at_pos, var_int},
+};
+
+/// A structure to track filesystem stat information along with an object id, linking a worktree file with what's in our ODB.
+#[derive(Clone)]
+pub struct OidStat {
+ /// The file system stat information
+ pub stat: entry::Stat,
+ /// The id of the file in our ODB.
+ pub id: ObjectId,
+}
+
+/// A directory with information about its untracked files, and its sub-directories
+#[derive(Clone)]
+pub struct Directory {
+ /// The directories name, or an empty string if this is the root directory.
+ pub name: BString,
+ /// Untracked files and directory names
+ pub untracked_entries: Vec<BString>,
+ /// indices for sub-directories similar to this one.
+ pub sub_directories: Vec<usize>,
+
+ /// The directories stat data, if available or valid // TODO: or is it the exclude file?
+ pub stat: Option<entry::Stat>,
+ /// The oid of a .gitignore file, if it exists
+ pub exclude_file_oid: Option<ObjectId>,
+ /// TODO: figure out what this really does
+ pub check_only: bool,
+}
+
+/// Only used as an indicator
+pub const SIGNATURE: Signature = *b"UNTR";
+
+// #[allow(unused)]
+/// Decode an untracked cache extension from `data`, assuming object hashes are of type `object_hash`.
+pub fn decode(data: &[u8], object_hash: gix_hash::Kind) -> Option<UntrackedCache> {
+ if !data.last().map(|b| *b == 0).unwrap_or(false) {
+ return None;
+ }
+ let (identifier_len, data) = var_int(data)?;
+ let (identifier, data) = split_at_pos(data, identifier_len.try_into().ok()?)?;
+
+ let hash_len = object_hash.len_in_bytes();
+ let (info_exclude, data) = decode_oid_stat(data, hash_len)?;
+ let (excludes_file, data) = decode_oid_stat(data, hash_len)?;
+ let (dir_flags, data) = read_u32(data)?;
+ let (exclude_filename_per_dir, data) = split_at_byte_exclusive(data, 0)?;
+
+ let (num_directory_blocks, data) = var_int(data)?;
+
+ let mut res = UntrackedCache {
+ identifier: identifier.into(),
+ info_exclude: (!info_exclude.id.is_null()).then_some(info_exclude),
+ excludes_file: (!excludes_file.id.is_null()).then_some(excludes_file),
+ exclude_filename_per_dir: exclude_filename_per_dir.into(),
+ dir_flags,
+ directories: Vec::new(),
+ };
+ if num_directory_blocks == 0 {
+ return data.is_empty().then_some(res);
+ }
+
+ let num_directory_blocks = num_directory_blocks.try_into().ok()?;
+ let directories = &mut res.directories;
+ directories.reserve(num_directory_blocks);
+
+ let data = decode_directory_block(data, directories)?;
+ if directories.len() != num_directory_blocks {
+ return None;
+ }
+ let (valid, data) = gix_bitmap::ewah::decode(data).ok()?;
+ let (check_only, data) = gix_bitmap::ewah::decode(data).ok()?;
+ let (hash_valid, mut data) = gix_bitmap::ewah::decode(data).ok()?;
+
+ if valid.num_bits() > num_directory_blocks
+ || check_only.num_bits() > num_directory_blocks
+ || hash_valid.num_bits() > num_directory_blocks
+ {
+ return None;
+ }
+
+ check_only.for_each_set_bit(|index| {
+ directories[index].check_only = true;
+ Some(())
+ })?;
+ valid.for_each_set_bit(|index| {
+ let (stat, rest) = crate::decode::stat(data)?;
+ directories[index].stat = stat.into();
+ data = rest;
+ Some(())
+ });
+ hash_valid.for_each_set_bit(|index| {
+ let (hash, rest) = split_at_pos(data, hash_len)?;
+ data = rest;
+ directories[index].exclude_file_oid = ObjectId::from(hash).into();
+ Some(())
+ });
+
+ // null-byte checked in the beginning
+ if data.len() != 1 {
+ return None;
+ }
+ res.into()
+}
+
+fn decode_directory_block<'a>(data: &'a [u8], directories: &mut Vec<Directory>) -> Option<&'a [u8]> {
+ let (num_untracked, data) = var_int(data)?;
+ let (num_dirs, data) = var_int(data)?;
+ let (name, mut data) = split_at_byte_exclusive(data, 0)?;
+ let mut untracked_entries = Vec::<BString>::with_capacity(num_untracked.try_into().ok()?);
+ for _ in 0..num_untracked {
+ let (name, rest) = split_at_byte_exclusive(data, 0)?;
+ data = rest;
+ untracked_entries.push(name.into());
+ }
+
+ let index = directories.len();
+ directories.push(Directory {
+ name: name.into(),
+ untracked_entries,
+ sub_directories: Vec::with_capacity(num_dirs.try_into().ok()?),
+ // the following are set later through their bitmaps
+ stat: None,
+ exclude_file_oid: None,
+ check_only: false,
+ });
+
+ for _ in 0..num_dirs {
+ let subdir_index = directories.len();
+ let rest = decode_directory_block(data, directories)?;
+ data = rest;
+ directories[index].sub_directories.push(subdir_index);
+ }
+
+ data.into()
+}
+
+fn decode_oid_stat(data: &[u8], hash_len: usize) -> Option<(OidStat, &[u8])> {
+ let (stat, data) = crate::decode::stat(data)?;
+ let (hash, data) = split_at_pos(data, hash_len)?;
+ Some((
+ OidStat {
+ stat,
+ id: ObjectId::from(hash),
+ },
+ data,
+ ))
+}
diff --git a/vendor/gix-index/src/file/init.rs b/vendor/gix-index/src/file/init.rs
new file mode 100644
index 000000000..534f1f08b
--- /dev/null
+++ b/vendor/gix-index/src/file/init.rs
@@ -0,0 +1,81 @@
+#![allow(unused)]
+
+use std::path::{Path, PathBuf};
+
+use memmap2::Mmap;
+
+use crate::{decode, extension, File, State};
+
+mod error {
+
+ /// The error returned by [File::at()][super::File::at()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("An IO error occurred while opening the index")]
+ Io(#[from] std::io::Error),
+ #[error(transparent)]
+ Decode(#[from] crate::decode::Error),
+ #[error(transparent)]
+ LinkExtension(#[from] crate::extension::link::decode::Error),
+ }
+}
+
+pub use error::Error;
+
+/// Initialization
+impl File {
+ /// Try to open the index file at `path` with `options`, assuming `object_hash` is used throughout the file, or create a new
+ /// index that merely exists in memory and is empty.
+ ///
+ /// Note that the `path` will not be written if it doesn't exist.
+ pub fn at_or_default(
+ path: impl Into<PathBuf>,
+ object_hash: gix_hash::Kind,
+ options: decode::Options,
+ ) -> Result<Self, Error> {
+ let path = path.into();
+ Ok(match Self::at(&path, object_hash, options) {
+ Ok(f) => f,
+ Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => {
+ File::from_state(State::new(object_hash), path)
+ }
+ Err(err) => return Err(err),
+ })
+ }
+
+ /// Open an index file at `path` with `options`, assuming `object_hash` is used throughout the file.
+ pub fn at(path: impl Into<PathBuf>, object_hash: gix_hash::Kind, options: decode::Options) -> Result<Self, Error> {
+ let path = path.into();
+ let (data, mtime) = {
+ // SAFETY: we have to take the risk of somebody changing the file underneath. Git never writes into the same file.
+ let file = std::fs::File::open(&path)?;
+ #[allow(unsafe_code)]
+ let data = unsafe { Mmap::map(&file)? };
+ (data, filetime::FileTime::from_last_modification_time(&file.metadata()?))
+ };
+
+ let (state, checksum) = State::from_bytes(&data, mtime, object_hash, options)?;
+ let mut file = File {
+ state,
+ path,
+ checksum: Some(checksum),
+ };
+ if let Some(mut link) = file.link.take() {
+ link.dissolve_into(&mut file, object_hash, options)?;
+ }
+
+ Ok(file)
+ }
+
+ /// Consume `state` and pretend it was read from `path`, setting our checksum to `null`.
+ ///
+ /// `File` instances created like that should be written to disk to set the correct checksum via `[File::write()]`.
+ pub fn from_state(state: crate::State, path: impl Into<PathBuf>) -> Self {
+ File {
+ state,
+ path: path.into(),
+ checksum: None,
+ }
+ }
+}
diff --git a/vendor/gix-index/src/file/mod.rs b/vendor/gix-index/src/file/mod.rs
new file mode 100644
index 000000000..40332abbd
--- /dev/null
+++ b/vendor/gix-index/src/file/mod.rs
@@ -0,0 +1,90 @@
+mod impls {
+ use std::ops::{Deref, DerefMut};
+
+ use crate::{File, State};
+
+ impl Deref for File {
+ type Target = State;
+
+ fn deref(&self) -> &Self::Target {
+ &self.state
+ }
+ }
+
+ impl DerefMut for File {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.state
+ }
+ }
+}
+
+mod impl_ {
+ use std::fmt::Formatter;
+
+ use crate::{File, State};
+
+ impl std::fmt::Debug for File {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("File")
+ .field("path", &self.path.display())
+ .field("checksum", &self.checksum)
+ .finish_non_exhaustive()
+ }
+ }
+
+ impl From<File> for State {
+ fn from(f: File) -> Self {
+ f.state
+ }
+ }
+}
+
+mod access {
+ use crate::File;
+
+ /// Consumption
+ impl File {
+ /// Take all non-copy parts of the index.
+ pub fn into_parts(self) -> (crate::State, std::path::PathBuf) {
+ (self.state, self.path)
+ }
+ }
+
+ /// Access
+ impl File {
+ /// The path from which the index was read or to which it is supposed to be written when used with [`File::from_state()`].
+ pub fn path(&self) -> &std::path::Path {
+ &self.path
+ }
+
+ /// The checksum over the file that was read or written to disk, or `None` if the state in memory was never serialized.
+ ///
+ /// Note that even if `Some`, it will only represent the state in memory right after reading or [writing][File::write()].
+ pub fn checksum(&self) -> Option<gix_hash::ObjectId> {
+ self.checksum
+ }
+ }
+}
+
+mod mutation {
+ use std::path::PathBuf;
+
+ use crate::File;
+
+ /// Mutating access
+ impl File {
+ /// Set the path at which we think we are located to the given `path`.
+ ///
+ /// This is useful to change the location of the index *once* it is written via [`write()`][File::write()].
+ pub fn set_path(&mut self, path: impl Into<PathBuf>) {
+ self.path = path.into();
+ }
+ }
+}
+
+///
+pub mod init;
+///
+pub mod verify;
+///
+pub mod write;
diff --git a/vendor/gix-index/src/file/verify.rs b/vendor/gix-index/src/file/verify.rs
new file mode 100644
index 000000000..6743b37a7
--- /dev/null
+++ b/vendor/gix-index/src/file/verify.rs
@@ -0,0 +1,41 @@
+use std::sync::atomic::AtomicBool;
+
+use crate::File;
+
+mod error {
+ /// The error returned by [File::verify_integrity()][super::File::verify_integrity()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("Could not read index file to generate hash")]
+ Io(#[from] std::io::Error),
+ #[error("Index checksum should have been {expected}, but was {actual}")]
+ ChecksumMismatch {
+ actual: gix_hash::ObjectId,
+ expected: gix_hash::ObjectId,
+ },
+ #[error("Checksum of in-memory index wasn't computed yet")]
+ NoChecksum,
+ }
+}
+pub use error::Error;
+
+impl File {
+ /// Verify the integrity of the index to assure its consistency.
+ pub fn verify_integrity(&self) -> Result<(), Error> {
+ let checksum = self.checksum.ok_or(Error::NoChecksum)?;
+ let num_bytes_to_hash = self.path.metadata()?.len() - checksum.as_bytes().len() as u64;
+ let should_interrupt = AtomicBool::new(false);
+ let actual = gix_features::hash::bytes_of_file(
+ &self.path,
+ num_bytes_to_hash as usize,
+ checksum.kind(),
+ &mut gix_features::progress::Discard,
+ &should_interrupt,
+ )?;
+ (actual == checksum).then_some(()).ok_or(Error::ChecksumMismatch {
+ actual,
+ expected: checksum,
+ })
+ }
+}
diff --git a/vendor/gix-index/src/file/write.rs b/vendor/gix-index/src/file/write.rs
new file mode 100644
index 000000000..1e8afc07d
--- /dev/null
+++ b/vendor/gix-index/src/file/write.rs
@@ -0,0 +1,51 @@
+use gix_features::hash;
+
+use crate::{write, File, Version};
+
+/// The error produced by [`File::write()`].
+#[derive(Debug, thiserror::Error)]
+#[allow(missing_docs)]
+pub enum Error {
+ #[error(transparent)]
+ Io(#[from] std::io::Error),
+ #[error("Could not acquire lock for index file")]
+ AcquireLock(#[from] gix_lock::acquire::Error),
+ #[error("Could not commit lock for index file")]
+ CommitLock(#[from] gix_lock::commit::Error<gix_lock::File>),
+}
+
+impl File {
+ /// Write the index to `out` with `options`, to be readable by [`File::at()`], returning the version that was actually written
+ /// to retain all information of this index.
+ pub fn write_to(
+ &self,
+ mut out: impl std::io::Write,
+ options: write::Options,
+ ) -> std::io::Result<(Version, gix_hash::ObjectId)> {
+ let mut hasher = hash::Write::new(&mut out, self.state.object_hash);
+ let version = self.state.write_to(&mut hasher, options)?;
+
+ let hash = hasher.hash.digest();
+ out.write_all(&hash)?;
+ Ok((version, gix_hash::ObjectId::from(hash)))
+ }
+
+ /// Write ourselves to the path we were read from after acquiring a lock, using `options`.
+ ///
+ /// Note that the hash produced will be stored which is why we need to be mutable.
+ pub fn write(&mut self, options: write::Options) -> Result<(), Error> {
+ let mut lock = std::io::BufWriter::new(gix_lock::File::acquire_to_update_resource(
+ &self.path,
+ gix_lock::acquire::Fail::Immediately,
+ None,
+ )?);
+ let (version, digest) = self.write_to(&mut lock, options)?;
+ match lock.into_inner() {
+ Ok(lock) => lock.commit()?,
+ Err(err) => return Err(err.into_error().into()),
+ };
+ self.state.version = version;
+ self.checksum = Some(digest);
+ Ok(())
+ }
+}
diff --git a/vendor/gix-index/src/init.rs b/vendor/gix-index/src/init.rs
new file mode 100644
index 000000000..abd71ffdd
--- /dev/null
+++ b/vendor/gix-index/src/init.rs
@@ -0,0 +1,155 @@
+mod from_tree {
+ use std::collections::VecDeque;
+
+ use bstr::{BStr, BString, ByteSlice, ByteVec};
+ use gix_object::{
+ tree::{self, EntryMode},
+ TreeRefIter,
+ };
+ use gix_traverse::tree::{breadthfirst, visit::Action, Visit};
+
+ use crate::{
+ entry::{Flags, Mode, Stat},
+ Entry, PathStorage, State, Version,
+ };
+
+ /// Initialization
+ impl State {
+ /// Return a new and empty in-memory index assuming the given `object_hash`.
+ pub fn new(object_hash: gix_hash::Kind) -> Self {
+ State {
+ object_hash,
+ timestamp: filetime::FileTime::now(),
+ version: Version::V2,
+ entries: vec![],
+ path_backing: vec![],
+ is_sparse: false,
+ tree: None,
+ link: None,
+ resolve_undo: None,
+ untracked: None,
+ fs_monitor: None,
+ }
+ }
+ /// Create an index [`State`][crate::State] by traversing `tree` recursively, accessing sub-trees
+ /// with `find`.
+ ///
+ /// **No extension data is currently produced**.
+ pub fn from_tree<Find>(tree: &gix_hash::oid, mut find: Find) -> Result<Self, breadthfirst::Error>
+ where
+ Find: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
+ {
+ let mut buf = Vec::new();
+ let root = find(tree, &mut buf).ok_or(breadthfirst::Error::NotFound { oid: tree.into() })?;
+
+ let mut delegate = CollectEntries::new();
+ breadthfirst(root, breadthfirst::State::default(), &mut find, &mut delegate)?;
+
+ let CollectEntries {
+ mut entries,
+ path_backing,
+ path: _,
+ path_deque: _,
+ } = delegate;
+
+ entries.sort_by(|a, b| Entry::cmp_filepaths(a.path_in(&path_backing), b.path_in(&path_backing)));
+
+ Ok(State {
+ object_hash: tree.kind(),
+ timestamp: filetime::FileTime::now(),
+ version: Version::V2,
+ entries,
+ path_backing,
+ is_sparse: false,
+ tree: None,
+ link: None,
+ resolve_undo: None,
+ untracked: None,
+ fs_monitor: None,
+ })
+ }
+ }
+
+ struct CollectEntries {
+ entries: Vec<Entry>,
+ path_backing: PathStorage,
+ path: BString,
+ path_deque: VecDeque<BString>,
+ }
+
+ impl CollectEntries {
+ pub fn new() -> CollectEntries {
+ CollectEntries {
+ entries: Vec::new(),
+ path_backing: Vec::new(),
+ path: BString::default(),
+ path_deque: VecDeque::new(),
+ }
+ }
+
+ fn push_element(&mut self, name: &BStr) {
+ if !self.path.is_empty() {
+ self.path.push(b'/');
+ }
+ self.path.push_str(name);
+ }
+
+ pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
+ let mode = match entry.mode {
+ EntryMode::Tree => unreachable!("visit_non_tree() called us"),
+ EntryMode::Blob => Mode::FILE,
+ EntryMode::BlobExecutable => Mode::FILE_EXECUTABLE,
+ EntryMode::Link => Mode::SYMLINK,
+ EntryMode::Commit => Mode::COMMIT,
+ };
+
+ let path_start = self.path_backing.len();
+ self.path_backing.extend_from_slice(&self.path);
+
+ let new_entry = Entry {
+ stat: Stat::default(),
+ id: entry.oid.into(),
+ flags: Flags::empty(),
+ mode,
+ path: path_start..self.path_backing.len(),
+ };
+
+ self.entries.push(new_entry);
+ }
+ }
+
+ impl Visit for CollectEntries {
+ fn pop_front_tracked_path_and_set_current(&mut self) {
+ self.path = self
+ .path_deque
+ .pop_front()
+ .expect("every call is matched with push_tracked_path_component");
+ }
+
+ fn push_back_tracked_path_component(&mut self, component: &bstr::BStr) {
+ self.push_element(component);
+ self.path_deque.push_back(self.path.clone());
+ }
+
+ fn push_path_component(&mut self, component: &bstr::BStr) {
+ self.push_element(component);
+ }
+
+ fn pop_path_component(&mut self) {
+ if let Some(pos) = self.path.rfind_byte(b'/') {
+ self.path.resize(pos, 0);
+ } else {
+ self.path.clear();
+ }
+ }
+
+ fn visit_tree(&mut self, _entry: &gix_object::tree::EntryRef<'_>) -> gix_traverse::tree::visit::Action {
+ Action::Continue
+ }
+
+ fn visit_nontree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> gix_traverse::tree::visit::Action {
+ self.add_entry(entry);
+ Action::Continue
+ }
+ }
+}
diff --git a/vendor/gix-index/src/lib.rs b/vendor/gix-index/src/lib.rs
new file mode 100644
index 000000000..d8451c545
--- /dev/null
+++ b/vendor/gix-index/src/lib.rs
@@ -0,0 +1,201 @@
+//! ## Feature Flags
+#![cfg_attr(
+ feature = "document-features",
+ cfg_attr(doc, doc = ::document_features::document_features!())
+)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+#![deny(unsafe_code, missing_docs, rust_2018_idioms)]
+
+use std::{ops::Range, path::PathBuf};
+
+use filetime::FileTime;
+pub use gix_hash as hash;
+
+///
+pub mod file;
+
+///
+pub mod extension;
+
+///
+pub mod entry;
+
+mod access;
+
+mod init;
+
+///
+pub mod decode;
+
+///
+pub mod verify;
+
+///
+pub mod write;
+
+/// All known versions of a git index file.
+#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
+#[cfg_attr(feature = "serde1", derive(serde::Serialize, serde::Deserialize))]
+pub enum Version {
+ /// Supports entries and various extensions.
+ V2 = 2,
+ /// Adds support for additional flags for each entry, called extended entries.
+ V3 = 3,
+ /// Supports deltified entry paths.
+ V4 = 4,
+}
+
+/// An entry in the index, identifying a non-tree item on disk.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct Entry {
+ /// The filesystem stat information for the file on disk.
+ pub stat: entry::Stat,
+ /// The object id for this entry's ODB representation (assuming it's up-to-date with it).
+ pub id: gix_hash::ObjectId,
+ /// Additional flags for use in algorithms and for efficiently storing stage information.
+ pub flags: entry::Flags,
+ /// The kind of item this entry represents - it's not all blobs in the index anymore.
+ pub mode: entry::Mode,
+ /// The range to lookup in the path backing to obtain the entry path relative to the repository.
+ /// This costs additional memory but is probably worth it given that paths can stay in one big allocation.
+ path: Range<usize>,
+}
+
+/// An index file whose state was read from a file on disk.
+#[derive(Clone)]
+pub struct File {
+ /// The state containing the actual index data.
+ pub(crate) state: State,
+ /// The path from which the index was read or to which it is supposed to be written.
+ pub(crate) path: PathBuf,
+ /// The checksum of all bytes prior to the checksum itself.
+ pub(crate) checksum: Option<gix_hash::ObjectId>,
+}
+
+/// The type to use and store paths to all entries.
+pub type PathStorage = Vec<u8>;
+/// The type to use and store paths to all entries, as reference
+pub type PathStorageRef = [u8];
+
+/// An in-memory cache of a fully parsed git index file.
+///
+/// As opposed to a snapshot, it's meant to be altered and eventually be written back to disk or converted into a tree.
+/// We treat index and its state synonymous.
+#[derive(Clone)]
+pub struct State {
+ /// The kind of object hash used when storing the underlying file.
+ ///
+ /// Empty states for example won't have a single object id, so deduction of the hash used isn't always possible.
+ object_hash: gix_hash::Kind,
+ /// The time at which the state was created, indicating its freshness compared to other files on disk.
+ ///
+ /// Note that on platforms that only have a precisions of a second for this time, we will treat all entries with the
+ /// same timestamp as this as potentially changed, checking more thoroughly if a change actually happened.
+ #[allow(dead_code)]
+ timestamp: FileTime,
+ version: Version,
+ entries: Vec<Entry>,
+ /// A memory area keeping all index paths, in full length, independently of the index version.
+ path_backing: PathStorage,
+ /// True if one entry in the index has a special marker mode
+ is_sparse: bool,
+
+ // Extensions
+ tree: Option<extension::Tree>,
+ link: Option<extension::Link>,
+ resolve_undo: Option<extension::resolve_undo::Paths>,
+ untracked: Option<extension::UntrackedCache>,
+ fs_monitor: Option<extension::FsMonitor>,
+}
+
+mod impls {
+ use std::fmt::{Debug, Formatter};
+
+ use crate::State;
+
+ impl Debug for State {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ for entry in &self.entries {
+ writeln!(
+ f,
+ "{} {}{:?} {} {}",
+ match entry.flags.stage() {
+ 0 => "BASE ",
+ 1 => "OURS ",
+ 2 => "THEIRS ",
+ _ => "UNKNOWN",
+ },
+ if entry.flags.is_empty() {
+ "".to_string()
+ } else {
+ format!("{:?} ", entry.flags)
+ },
+ entry.mode,
+ entry.id,
+ entry.path(self)
+ )?;
+ }
+ Ok(())
+ }
+ }
+}
+
+pub(crate) mod util {
+ use std::convert::TryInto;
+
+ #[inline]
+ pub fn var_int(data: &[u8]) -> Option<(u64, &[u8])> {
+ let (num, consumed) = gix_features::decode::leb64_from_read(data).ok()?;
+ let data = &data[consumed..];
+ (num, data).into()
+ }
+
+ #[inline]
+ pub fn read_u32(data: &[u8]) -> Option<(u32, &[u8])> {
+ split_at_pos(data, 4).map(|(num, data)| (u32::from_be_bytes(num.try_into().unwrap()), data))
+ }
+
+ #[inline]
+ pub fn read_u64(data: &[u8]) -> Option<(u64, &[u8])> {
+ split_at_pos(data, 8).map(|(num, data)| (u64::from_be_bytes(num.try_into().unwrap()), data))
+ }
+
+ #[inline]
+ pub fn from_be_u32(b: &[u8]) -> u32 {
+ u32::from_be_bytes(b.try_into().unwrap())
+ }
+
+ #[inline]
+ pub fn split_at_byte_exclusive(data: &[u8], byte: u8) -> Option<(&[u8], &[u8])> {
+ if data.len() < 2 {
+ return None;
+ }
+ data.iter().enumerate().find_map(|(idx, b)| {
+ (*b == byte).then(|| {
+ if idx == 0 {
+ (&[] as &[u8], &data[1..])
+ } else {
+ let (a, b) = data.split_at(idx);
+ (a, &b[1..])
+ }
+ })
+ })
+ }
+
+ #[inline]
+ pub fn split_at_pos(data: &[u8], pos: usize) -> Option<(&[u8], &[u8])> {
+ if data.len() < pos {
+ return None;
+ }
+ data.split_at(pos).into()
+ }
+}
+
+#[test]
+fn size_of_entry() {
+ assert_eq!(std::mem::size_of::<crate::Entry>(), 80);
+
+ // the reason we have our own time is half the size.
+ assert_eq!(std::mem::size_of::<crate::entry::Time>(), 8);
+ assert_eq!(std::mem::size_of::<filetime::FileTime>(), 16);
+}
diff --git a/vendor/gix-index/src/verify.rs b/vendor/gix-index/src/verify.rs
new file mode 100644
index 000000000..31b48d1cb
--- /dev/null
+++ b/vendor/gix-index/src/verify.rs
@@ -0,0 +1,73 @@
+use std::cmp::Ordering;
+
+use crate::State;
+
+///
+pub mod entries {
+ use bstr::BString;
+
+ /// The error returned by [State::verify_entries()][crate::State::verify_entries()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("Entry '{current_path}' (stage = {current_stage}) at index {current_index} should order after prior entry '{previous_path}' (stage = {previous_stage})")]
+ OutOfOrder {
+ current_index: usize,
+ current_path: BString,
+ current_stage: u8,
+ previous_path: BString,
+ previous_stage: u8,
+ },
+ }
+}
+
+///
+pub mod extensions {
+ use crate::extension;
+
+ /// An implementation of a `find` function that never finds or returns any object, a no-op.
+ pub fn no_find<'a>(_: &gix_hash::oid, _: &'a mut Vec<u8>) -> Option<gix_object::TreeRefIter<'a>> {
+ None
+ }
+
+ /// The error returned by [State::verify_extensions()][crate::State::verify_extensions()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error(transparent)]
+ Tree(#[from] extension::tree::verify::Error),
+ }
+}
+
+impl State {
+ /// Assure our entries are consistent.
+ pub fn verify_entries(&self) -> Result<(), entries::Error> {
+ let mut previous = None::<&crate::Entry>;
+ for (idx, entry) in self.entries.iter().enumerate() {
+ if let Some(prev) = previous {
+ if prev.cmp(entry, self) != Ordering::Less {
+ return Err(entries::Error::OutOfOrder {
+ current_index: idx,
+ current_path: entry.path(self).into(),
+ current_stage: entry.flags.stage() as u8,
+ previous_path: prev.path(self).into(),
+ previous_stage: prev.flags.stage() as u8,
+ });
+ }
+ }
+ previous = Some(entry);
+ }
+ Ok(())
+ }
+
+ /// Note: `find` cannot be `Option<F>` as we can't call it with a closure then due to the indirection through `Some`.
+ pub fn verify_extensions<F>(&self, use_find: bool, find: F) -> Result<(), extensions::Error>
+ where
+ F: for<'a> FnMut(&gix_hash::oid, &'a mut Vec<u8>) -> Option<gix_object::TreeRefIter<'a>>,
+ {
+ self.tree().map(|t| t.verify(use_find, find)).transpose()?;
+ // TODO: verify links by running the whole set of tests on the index
+ // - do that once we load it as well, or maybe that's lazy loaded? Too many questions for now.
+ Ok(())
+ }
+}
diff --git a/vendor/gix-index/src/write.rs b/vendor/gix-index/src/write.rs
new file mode 100644
index 000000000..23643734c
--- /dev/null
+++ b/vendor/gix-index/src/write.rs
@@ -0,0 +1,215 @@
+use std::{convert::TryInto, io::Write};
+
+use crate::{entry, extension, write::util::CountBytes, State, Version};
+
+/// A way to specify which of the optional extensions to write.
+#[derive(Debug, Copy, Clone)]
+pub enum Extensions {
+ /// Writes all available optional extensions to avoid loosing any information.
+ All,
+ /// Only write the given optional extensions, with each extension being marked by a boolean flag.
+ ///
+ /// # Note: mandatory extensions
+ ///
+ /// Mandatory extensions, like `sdir` or other lower-case ones, may not be configured here as they need to be present
+ /// or absent depending on the state of the index itself and for it to be valid.
+ Given {
+ /// Write the tree-cache extension, if present.
+ tree_cache: bool,
+ /// Write the end-of-index-entry extension.
+ end_of_index_entry: bool,
+ },
+ /// Write no optional extension at all for what should be the smallest possible index
+ None,
+}
+
+impl Default for Extensions {
+ fn default() -> Self {
+ Extensions::All
+ }
+}
+
+impl Extensions {
+ /// Returns `Some(signature)` if it should be written out.
+ pub fn should_write(&self, signature: extension::Signature) -> Option<extension::Signature> {
+ match self {
+ Extensions::None => None,
+ Extensions::All => Some(signature),
+ Extensions::Given {
+ tree_cache,
+ end_of_index_entry,
+ } => match signature {
+ extension::tree::SIGNATURE => tree_cache,
+ extension::end_of_index_entry::SIGNATURE => end_of_index_entry,
+ _ => &false,
+ }
+ .then(|| signature),
+ }
+ }
+}
+
+/// The options for use when [writing an index][State::write_to()].
+///
+/// Note that default options write either index V2 or V3 depending on the content of the entries.
+#[derive(Debug, Default, Clone, Copy)]
+pub struct Options {
+ /// Configures which extensions to write
+ pub extensions: Extensions,
+}
+
+impl State {
+ /// Serialize this instance to `out` with [`options`][Options].
+ pub fn write_to(&self, out: impl std::io::Write, Options { extensions }: Options) -> std::io::Result<Version> {
+ let version = self.detect_required_version();
+
+ let mut write = CountBytes::new(out);
+ let num_entries: u32 = self
+ .entries()
+ .len()
+ .try_into()
+ .expect("definitely not 4billion entries");
+ let removed_entries: u32 = self
+ .entries()
+ .iter()
+ .filter(|e| e.flags.contains(entry::Flags::REMOVE))
+ .count()
+ .try_into()
+ .expect("definitely not too many entries");
+
+ let offset_to_entries = header(&mut write, version, num_entries - removed_entries)?;
+ let offset_to_extensions = entries(&mut write, self, offset_to_entries)?;
+ let (extension_toc, out) = self.write_extensions(write, offset_to_extensions, extensions)?;
+
+ if num_entries > 0
+ && extensions
+ .should_write(extension::end_of_index_entry::SIGNATURE)
+ .is_some()
+ && !extension_toc.is_empty()
+ {
+ extension::end_of_index_entry::write_to(out, self.object_hash, offset_to_extensions, extension_toc)?
+ }
+
+ Ok(version)
+ }
+
+ fn write_extensions<T>(
+ &self,
+ mut write: CountBytes<T>,
+ offset_to_extensions: u32,
+ extensions: Extensions,
+ ) -> std::io::Result<(Vec<(extension::Signature, u32)>, T)>
+ where
+ T: std::io::Write,
+ {
+ type WriteExtFn<'a> = &'a dyn Fn(&mut dyn std::io::Write) -> Option<std::io::Result<extension::Signature>>;
+ let extensions: &[WriteExtFn<'_>] = &[
+ &|write| {
+ extensions
+ .should_write(extension::tree::SIGNATURE)
+ .and_then(|signature| self.tree().map(|tree| tree.write_to(write).map(|_| signature)))
+ },
+ &|write| {
+ self.is_sparse()
+ .then(|| extension::sparse::write_to(write).map(|_| extension::sparse::SIGNATURE))
+ },
+ ];
+
+ let mut offset_to_previous_ext = offset_to_extensions;
+ let mut out = Vec::with_capacity(5);
+ for write_ext in extensions {
+ if let Some(signature) = write_ext(&mut write).transpose()? {
+ let offset_past_ext = write.count;
+ let ext_size = offset_past_ext - offset_to_previous_ext - (extension::MIN_SIZE as u32);
+ offset_to_previous_ext = offset_past_ext;
+ out.push((signature, ext_size));
+ }
+ }
+ Ok((out, write.inner))
+ }
+}
+
+impl State {
+ fn detect_required_version(&self) -> Version {
+ self.entries
+ .iter()
+ .find_map(|e| e.flags.contains(entry::Flags::EXTENDED).then_some(Version::V3))
+ .unwrap_or(Version::V2)
+ }
+}
+
+fn header<T: std::io::Write>(
+ out: &mut CountBytes<T>,
+ version: Version,
+ num_entries: u32,
+) -> Result<u32, std::io::Error> {
+ let version = match version {
+ Version::V2 => 2_u32.to_be_bytes(),
+ Version::V3 => 3_u32.to_be_bytes(),
+ Version::V4 => 4_u32.to_be_bytes(),
+ };
+
+ out.write_all(crate::decode::header::SIGNATURE)?;
+ out.write_all(&version)?;
+ out.write_all(&num_entries.to_be_bytes())?;
+
+ Ok(out.count)
+}
+
+fn entries<T: std::io::Write>(out: &mut CountBytes<T>, state: &State, header_size: u32) -> Result<u32, std::io::Error> {
+ for entry in state.entries() {
+ if entry.flags.contains(entry::Flags::REMOVE) {
+ continue;
+ }
+ entry.write_to(&mut *out, state)?;
+ match (out.count - header_size) % 8 {
+ 0 => {}
+ n => {
+ let eight_null_bytes = [0u8; 8];
+ out.write_all(&eight_null_bytes[n as usize..])?;
+ }
+ };
+ }
+
+ Ok(out.count)
+}
+
+mod util {
+ use std::convert::TryFrom;
+
+ pub struct CountBytes<T> {
+ pub count: u32,
+ pub inner: T,
+ }
+
+ impl<T> CountBytes<T>
+ where
+ T: std::io::Write,
+ {
+ pub fn new(inner: T) -> Self {
+ CountBytes { inner, count: 0 }
+ }
+ }
+
+ impl<T> std::io::Write for CountBytes<T>
+ where
+ T: std::io::Write,
+ {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ let written = self.inner.write(buf)?;
+ self.count = self
+ .count
+ .checked_add(u32::try_from(written).expect("we don't write 4GB buffers"))
+ .ok_or_else(|| {
+ std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "Cannot write indices larger than 4 gigabytes",
+ )
+ })?;
+ Ok(written)
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.inner.flush()
+ }
+ }
+}