Import rustc_1.85.0+dfsg3.orig-extra.tar.xz
authorFabian Grünbichler <debian@fabian.gruenbichler.email>
Thu, 24 Apr 2025 15:47:57 +0000 (17:47 +0200)
committerFabian Grünbichler <debian@fabian.gruenbichler.email>
Thu, 24 Apr 2025 15:47:57 +0000 (17:47 +0200)
[dgit import orig rustc_1.85.0+dfsg3.orig-extra.tar.xz]

114 files changed:
git2-curl/.cargo-checksum.json [new file with mode: 0644]
git2-curl/CHANGELOG.md [new file with mode: 0644]
git2-curl/Cargo.toml [new file with mode: 0644]
git2-curl/LICENSE-APACHE [new file with mode: 0644]
git2-curl/LICENSE-MIT [new file with mode: 0644]
git2-curl/src/lib.rs [new file with mode: 0644]
git2-curl/tests/all.rs [new file with mode: 0644]
git2/.cargo-checksum.json [new file with mode: 0644]
git2/CHANGELOG.md [new file with mode: 0644]
git2/CONTRIBUTING.md [new file with mode: 0644]
git2/Cargo.lock [new file with mode: 0644]
git2/Cargo.toml [new file with mode: 0644]
git2/FUNDING.json [new file with mode: 0644]
git2/LICENSE-APACHE [new file with mode: 0644]
git2/LICENSE-MIT [new file with mode: 0644]
git2/README.md [new file with mode: 0644]
git2/ci/publish.sh [new file with mode: 0755]
git2/examples/add.rs [new file with mode: 0644]
git2/examples/blame.rs [new file with mode: 0644]
git2/examples/cat-file.rs [new file with mode: 0644]
git2/examples/clone.rs [new file with mode: 0644]
git2/examples/diff.rs [new file with mode: 0644]
git2/examples/fetch.rs [new file with mode: 0644]
git2/examples/init.rs [new file with mode: 0644]
git2/examples/log.rs [new file with mode: 0644]
git2/examples/ls-remote.rs [new file with mode: 0644]
git2/examples/pull.rs [new file with mode: 0644]
git2/examples/rev-list.rs [new file with mode: 0644]
git2/examples/rev-parse.rs [new file with mode: 0644]
git2/examples/status.rs [new file with mode: 0644]
git2/examples/tag.rs [new file with mode: 0644]
git2/src/apply.rs [new file with mode: 0644]
git2/src/attr.rs [new file with mode: 0644]
git2/src/blame.rs [new file with mode: 0644]
git2/src/blob.rs [new file with mode: 0644]
git2/src/branch.rs [new file with mode: 0644]
git2/src/buf.rs [new file with mode: 0644]
git2/src/build.rs [new file with mode: 0644]
git2/src/call.rs [new file with mode: 0644]
git2/src/cert.rs [new file with mode: 0644]
git2/src/cherrypick.rs [new file with mode: 0644]
git2/src/commit.rs [new file with mode: 0644]
git2/src/config.rs [new file with mode: 0644]
git2/src/cred.rs [new file with mode: 0644]
git2/src/describe.rs [new file with mode: 0644]
git2/src/diff.rs [new file with mode: 0644]
git2/src/email.rs [new file with mode: 0644]
git2/src/error.rs [new file with mode: 0644]
git2/src/index.rs [new file with mode: 0644]
git2/src/indexer.rs [new file with mode: 0644]
git2/src/lib.rs [new file with mode: 0644]
git2/src/mailmap.rs [new file with mode: 0644]
git2/src/mempack.rs [new file with mode: 0644]
git2/src/merge.rs [new file with mode: 0644]
git2/src/message.rs [new file with mode: 0644]
git2/src/note.rs [new file with mode: 0644]
git2/src/object.rs [new file with mode: 0644]
git2/src/odb.rs [new file with mode: 0644]
git2/src/oid.rs [new file with mode: 0644]
git2/src/oid_array.rs [new file with mode: 0644]
git2/src/opts.rs [new file with mode: 0644]
git2/src/packbuilder.rs [new file with mode: 0644]
git2/src/panic.rs [new file with mode: 0644]
git2/src/patch.rs [new file with mode: 0644]
git2/src/pathspec.rs [new file with mode: 0644]
git2/src/proxy_options.rs [new file with mode: 0644]
git2/src/push_update.rs [new file with mode: 0644]
git2/src/rebase.rs [new file with mode: 0644]
git2/src/reference.rs [new file with mode: 0644]
git2/src/reflog.rs [new file with mode: 0644]
git2/src/refspec.rs [new file with mode: 0644]
git2/src/remote.rs [new file with mode: 0644]
git2/src/remote_callbacks.rs [new file with mode: 0644]
git2/src/repo.rs [new file with mode: 0644]
git2/src/revert.rs [new file with mode: 0644]
git2/src/revspec.rs [new file with mode: 0644]
git2/src/revwalk.rs [new file with mode: 0644]
git2/src/signature.rs [new file with mode: 0644]
git2/src/stash.rs [new file with mode: 0644]
git2/src/status.rs [new file with mode: 0644]
git2/src/string_array.rs [new file with mode: 0644]
git2/src/submodule.rs [new file with mode: 0644]
git2/src/tag.rs [new file with mode: 0644]
git2/src/tagforeach.rs [new file with mode: 0644]
git2/src/test.rs [new file with mode: 0644]
git2/src/time.rs [new file with mode: 0644]
git2/src/tracing.rs [new file with mode: 0644]
git2/src/transaction.rs [new file with mode: 0644]
git2/src/transport.rs [new file with mode: 0644]
git2/src/tree.rs [new file with mode: 0644]
git2/src/treebuilder.rs [new file with mode: 0644]
git2/src/util.rs [new file with mode: 0644]
git2/src/version.rs [new file with mode: 0644]
git2/src/worktree.rs [new file with mode: 0644]
git2/tests/add_extensions.rs [new file with mode: 0644]
git2/tests/get_extensions.rs [new file with mode: 0644]
git2/tests/global_state.rs [new file with mode: 0644]
git2/tests/remove_extensions.rs [new file with mode: 0644]
libgit2-sys/.cargo-checksum.json [new file with mode: 0644]
libgit2-sys/CHANGELOG.md [new file with mode: 0644]
libgit2-sys/Cargo.toml [new file with mode: 0644]
libgit2-sys/LICENSE-APACHE [new file with mode: 0644]
libgit2-sys/LICENSE-MIT [new file with mode: 0644]
libgit2-sys/build.rs [new file with mode: 0644]
libgit2-sys/lib.rs [new file with mode: 0644]
sha1-checked-0.10.0/.cargo-checksum.json [new file with mode: 0644]
sha1-checked-0.10.0/CHANGELOG.md [new file with mode: 0644]
sha1-checked-0.10.0/Cargo.toml [new file with mode: 0644]
sha1-checked-0.10.0/LICENSE-APACHE [new file with mode: 0644]
sha1-checked-0.10.0/LICENSE-MIT [new file with mode: 0644]
sha1-checked-0.10.0/README.md [new file with mode: 0644]
sha1-checked-0.10.0/src/compress.rs [new file with mode: 0644]
sha1-checked-0.10.0/src/lib.rs [new file with mode: 0644]
sha1-checked-0.10.0/src/ubc_check.rs [new file with mode: 0644]

diff --git a/git2-curl/.cargo-checksum.json b/git2-curl/.cargo-checksum.json
new file mode 100644 (file)
index 0000000..d7d9bf1
--- /dev/null
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"433aae66b26ec1e1d867def155919cee89d831b0d14e5d20b9e80e1f11a20101","Cargo.toml":"49cac7eabb933177c492b5fa3a57813fb19e7471bb64d76777d172b81588738d","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","src/lib.rs":"b1919f3ae4b6e4eba32fe74167ca13393baecccb04f986cdd4de71b122d48701","tests/all.rs":"5b48bca5608c533f4763f850a9b838cffe09e73e529261f16c331e10296f8e14"},"package":"be8dcabbc09ece4d30a9aa983d5804203b7e2f8054a171f792deff59b56d31fa"}
\ No newline at end of file
diff --git a/git2-curl/CHANGELOG.md b/git2-curl/CHANGELOG.md
new file mode 100644 (file)
index 0000000..1d51c64
--- /dev/null
@@ -0,0 +1,31 @@
+# Changelog
+
+## 0.20.0 - 2024-06-13
+[0.19.0...0.20.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.19.0...git2-curl-0.20.0)
+
+- Updated to [git2 0.19.0](../CHANGELOG.md#0190---2024-06-13)
+
+## 0.19.0 - 2023-08-28
+[0.18.0...0.19.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.18.0...git2-curl-0.19.0)
+
+- Updated to [git2 0.18.0](../CHANGELOG.md#0180---2023-08-26)
+
+## 0.18.0 - 2023-04-02
+[0.17.0...0.18.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.17.0...git2-curl-0.18.0)
+
+- Updated to [git2 0.17.0](../CHANGELOG.md#0170---2023-04-02)
+
+## 0.17.0 - 2023-01-10
+[0.16.0...0.17.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.16.0...git2-curl-0.17.0)
+
+- Updated to [git2 0.16.0](../CHANGELOG.md#0160---2023-01-10)
+
+## 0.16.0 - 2022-07-28
+[0.15.0...0.16.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.15.0...git2-curl-0.16.0)
+
+- Updated to [git2 0.15.0](../CHANGELOG.md#0150---2022-07-28)
+
+## 0.15.0 - 2022-02-28
+[0.14.1...0.15.0](https://github.com/rust-lang/git2-rs/compare/git2-curl-0.14.1...git2-curl-0.15.0)
+
+- Updated to [git2 0.14.0](../CHANGELOG.md#0140---2022-02-24)
diff --git a/git2-curl/Cargo.toml b/git2-curl/Cargo.toml
new file mode 100644 (file)
index 0000000..9313f18
--- /dev/null
@@ -0,0 +1,74 @@
+# 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 = "2018"
+name = "git2-curl"
+version = "0.21.0"
+authors = [
+    "Josh Triplett <josh@joshtriplett.org>",
+    "Alex Crichton <alex@alexcrichton.com>",
+]
+build = false
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = """
+Backend for an HTTP transport in libgit2 powered by libcurl.
+
+Intended to be used with the git2 crate.
+"""
+documentation = "https://docs.rs/git2-curl"
+readme = false
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/git2-rs"
+
+[lib]
+name = "git2_curl"
+path = "src/lib.rs"
+
+[[test]]
+name = "all"
+path = "tests/all.rs"
+harness = false
+
+[dependencies.curl]
+version = "0.4.33"
+
+[dependencies.git2]
+version = "0.20"
+default-features = false
+
+[dependencies.log]
+version = "0.4"
+
+[dependencies.url]
+version = "2.0"
+
+[dev-dependencies.civet]
+version = "0.11"
+
+[dev-dependencies.conduit]
+version = "0.8"
+
+[dev-dependencies.conduit-git-http-backend]
+version = "0.8"
+
+[dev-dependencies.tempfile]
+version = "3.0"
+
+[features]
+zlib-ng-compat = [
+    "git2/zlib-ng-compat",
+    "curl/zlib-ng-compat",
+]
diff --git a/git2-curl/LICENSE-APACHE b/git2-curl/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/git2-curl/LICENSE-MIT b/git2-curl/LICENSE-MIT
new file mode 100644 (file)
index 0000000..39e0ed6
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/git2-curl/src/lib.rs b/git2-curl/src/lib.rs
new file mode 100644 (file)
index 0000000..6fa4532
--- /dev/null
@@ -0,0 +1,291 @@
+//! A crate for using libcurl as a backend for HTTP git requests with git2-rs.
+//!
+//! This crate provides one public function, `register`, which will register
+//! a custom HTTP transport with libcurl for any HTTP requests made by libgit2.
+//! At this time the `register` function is unsafe for the same reasons that
+//! `git2::transport::register` is also unsafe.
+//!
+//! It is not recommended to use this crate wherever possible. The current
+//! libcurl backend used, `curl-rust`, only supports executing a request in one
+//! method call implying no streaming support. This consequently means that
+//! when a repository is cloned the entire contents of the repo are downloaded
+//! into memory, and *then* written off to disk by libgit2 afterwards. It
+//! should be possible to alleviate this problem in the future.
+//!
+//! > **NOTE**: At this time this crate likely does not support a `git push`
+//! >           operation, only clones.
+
+#![doc(html_root_url = "https://docs.rs/git2-curl/0.21")]
+#![deny(missing_docs)]
+#![warn(rust_2018_idioms)]
+#![cfg_attr(test, deny(warnings))]
+
+use std::error;
+use std::io::prelude::*;
+use std::io::{self, Cursor};
+use std::str;
+use std::sync::{Arc, Mutex, Once};
+
+use curl::easy::{Easy, List};
+use git2::transport::SmartSubtransportStream;
+use git2::transport::{Service, SmartSubtransport, Transport};
+use git2::Error;
+use log::{debug, info};
+use url::Url;
+
+struct CurlTransport {
+    handle: Arc<Mutex<Easy>>,
+    /// The URL of the remote server, e.g. `https://github.com/user/repo`
+    ///
+    /// This is an empty string until the first action is performed.
+    /// If there is an HTTP redirect, this will be updated with the new URL.
+    base_url: Arc<Mutex<String>>,
+}
+
+struct CurlSubtransport {
+    handle: Arc<Mutex<Easy>>,
+    service: &'static str,
+    url_path: &'static str,
+    base_url: Arc<Mutex<String>>,
+    method: &'static str,
+    reader: Option<Cursor<Vec<u8>>>,
+    sent_request: bool,
+}
+
+/// Register the libcurl backend for HTTP requests made by libgit2.
+///
+/// This function takes one parameter, a `handle`, which is used to perform all
+/// future HTTP requests. The handle can be previously configured with
+/// information such as proxies, SSL information, etc.
+///
+/// This function is unsafe largely for the same reasons as
+/// `git2::transport::register`:
+///
+/// * The function needs to be synchronized against all other creations of
+///   transport (any API calls to libgit2).
+/// * The function will leak `handle` as once registered it is not currently
+///   possible to unregister the backend.
+///
+/// This function may be called concurrently, but only the first `handle` will
+/// be used. All others will be discarded.
+pub unsafe fn register(handle: Easy) {
+    static INIT: Once = Once::new();
+
+    let handle = Arc::new(Mutex::new(handle));
+    let handle2 = handle.clone();
+    INIT.call_once(move || {
+        git2::transport::register("http", move |remote| factory(remote, handle.clone())).unwrap();
+        git2::transport::register("https", move |remote| factory(remote, handle2.clone())).unwrap();
+    });
+}
+
+fn factory(remote: &git2::Remote<'_>, handle: Arc<Mutex<Easy>>) -> Result<Transport, Error> {
+    Transport::smart(
+        remote,
+        true,
+        CurlTransport {
+            handle: handle,
+            base_url: Arc::new(Mutex::new(String::new())),
+        },
+    )
+}
+
+impl SmartSubtransport for CurlTransport {
+    fn action(
+        &self,
+        url: &str,
+        action: Service,
+    ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
+        let mut base_url = self.base_url.lock().unwrap();
+        if base_url.len() == 0 {
+            *base_url = url.to_string();
+        }
+        let (service, path, method) = match action {
+            Service::UploadPackLs => ("upload-pack", "/info/refs?service=git-upload-pack", "GET"),
+            Service::UploadPack => ("upload-pack", "/git-upload-pack", "POST"),
+            Service::ReceivePackLs => {
+                ("receive-pack", "/info/refs?service=git-receive-pack", "GET")
+            }
+            Service::ReceivePack => ("receive-pack", "/git-receive-pack", "POST"),
+        };
+        info!("action {} {}", service, path);
+        Ok(Box::new(CurlSubtransport {
+            handle: self.handle.clone(),
+            service: service,
+            url_path: path,
+            base_url: self.base_url.clone(),
+            method: method,
+            reader: None,
+            sent_request: false,
+        }))
+    }
+
+    fn close(&self) -> Result<(), Error> {
+        Ok(()) // ...
+    }
+}
+
+impl CurlSubtransport {
+    fn err<E: Into<Box<dyn error::Error + Send + Sync>>>(&self, err: E) -> io::Error {
+        io::Error::new(io::ErrorKind::Other, err)
+    }
+
+    fn execute(&mut self, data: &[u8]) -> io::Result<()> {
+        if self.sent_request {
+            return Err(self.err("already sent HTTP request"));
+        }
+        let agent = format!("git/1.0 (git2-curl {})", env!("CARGO_PKG_VERSION"));
+
+        // Parse our input URL to figure out the host
+        let url = format!("{}{}", self.base_url.lock().unwrap(), self.url_path);
+        let parsed = Url::parse(&url).map_err(|_| self.err("invalid url, failed to parse"))?;
+        let host = match parsed.host_str() {
+            Some(host) => host,
+            None => return Err(self.err("invalid url, did not have a host")),
+        };
+
+        // Prep the request
+        debug!("request to {}", url);
+        let mut h = self.handle.lock().unwrap();
+        h.url(&url)?;
+        h.useragent(&agent)?;
+        h.follow_location(true)?;
+        match self.method {
+            "GET" => h.get(true)?,
+            "PUT" => h.put(true)?,
+            "POST" => h.post(true)?,
+            other => h.custom_request(other)?,
+        }
+
+        let mut headers = List::new();
+        headers.append(&format!("Host: {}", host))?;
+        if data.len() > 0 {
+            h.post_fields_copy(data)?;
+            headers.append(&format!(
+                "Accept: application/x-git-{}-result",
+                self.service
+            ))?;
+            headers.append(&format!(
+                "Content-Type: \
+                 application/x-git-{}-request",
+                self.service
+            ))?;
+        } else {
+            headers.append("Accept: */*")?;
+        }
+        headers.append("Expect:")?;
+        h.http_headers(headers)?;
+
+        let mut content_type = None;
+        let mut data = Vec::new();
+        {
+            let mut h = h.transfer();
+
+            // Look for the Content-Type header
+            h.header_function(|header| {
+                let header = match str::from_utf8(header) {
+                    Ok(s) => s,
+                    Err(..) => return true,
+                };
+                let mut parts = header.splitn(2, ": ");
+                let name = parts.next().unwrap();
+                let value = match parts.next() {
+                    Some(value) => value,
+                    None => return true,
+                };
+                if name.eq_ignore_ascii_case("Content-Type") {
+                    content_type = Some(value.trim().to_string());
+                }
+
+                true
+            })?;
+
+            // Collect the request's response in-memory
+            h.write_function(|buf| {
+                data.extend_from_slice(buf);
+                Ok(buf.len())
+            })?;
+
+            // Send the request
+            h.perform()?;
+        }
+
+        let code = h.response_code()?;
+        if code != 200 {
+            return Err(self.err(
+                &format!(
+                    "failed to receive HTTP 200 response: \
+                     got {}",
+                    code
+                )[..],
+            ));
+        }
+
+        // Check returned headers
+        let expected = match self.method {
+            "GET" => format!("application/x-git-{}-advertisement", self.service),
+            _ => format!("application/x-git-{}-result", self.service),
+        };
+        match content_type {
+            Some(ref content_type) if *content_type != expected => {
+                return Err(self.err(
+                    &format!(
+                        "expected a Content-Type header \
+                         with `{}` but found `{}`",
+                        expected, content_type
+                    )[..],
+                ))
+            }
+            Some(..) => {}
+            None => {
+                return Err(self.err(
+                    &format!(
+                        "expected a Content-Type header \
+                         with `{}` but didn't find one",
+                        expected
+                    )[..],
+                ))
+            }
+        }
+
+        // Ok, time to read off some data.
+        let rdr = Cursor::new(data);
+        self.reader = Some(rdr);
+
+        // If there was a redirect, update the `CurlTransport` with the new base.
+        if let Ok(Some(effective_url)) = h.effective_url() {
+            let new_base = if effective_url.ends_with(self.url_path) {
+                // Strip the action from the end.
+                &effective_url[..effective_url.len() - self.url_path.len()]
+            } else {
+                // I'm not sure if this code path makes sense, but it's what
+                // libgit does.
+                effective_url
+            };
+            *self.base_url.lock().unwrap() = new_base.to_string();
+        }
+
+        Ok(())
+    }
+}
+
+impl Read for CurlSubtransport {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        if self.reader.is_none() {
+            self.execute(&[])?;
+        }
+        self.reader.as_mut().unwrap().read(buf)
+    }
+}
+
+impl Write for CurlSubtransport {
+    fn write(&mut self, data: &[u8]) -> io::Result<usize> {
+        if self.reader.is_none() {
+            self.execute(data)?;
+        }
+        Ok(data.len())
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
diff --git a/git2-curl/tests/all.rs b/git2-curl/tests/all.rs
new file mode 100644 (file)
index 0000000..c7f09dd
--- /dev/null
@@ -0,0 +1,74 @@
+use civet::{Config, Server};
+use conduit_git_http_backend as git_backend;
+use std::fs::File;
+use std::path::Path;
+use tempfile::TempDir;
+
+const PORT: u16 = 7848;
+
+fn main() {
+    unsafe {
+        git2_curl::register(curl::easy::Easy::new());
+    }
+
+    // Spin up a server for git-http-backend
+    let td = TempDir::new().unwrap();
+    let mut cfg = Config::new();
+    cfg.port(PORT).threads(1);
+    let _a = Server::start(cfg, git_backend::Serve(td.path().to_path_buf()));
+
+    // Prep a repo with one file called `foo`
+    let sig = git2::Signature::now("foo", "bar").unwrap();
+    let r1 = git2::Repository::init(td.path()).unwrap();
+    File::create(&td.path().join(".git").join("git-daemon-export-ok")).unwrap();
+    {
+        let mut index = r1.index().unwrap();
+        File::create(&td.path().join("foo")).unwrap();
+        index.add_path(Path::new("foo")).unwrap();
+        index.write().unwrap();
+        let tree_id = index.write_tree().unwrap();
+        r1.commit(
+            Some("HEAD"),
+            &sig,
+            &sig,
+            "test",
+            &r1.find_tree(tree_id).unwrap(),
+            &[],
+        )
+        .unwrap();
+    }
+
+    // Clone through the git-http-backend
+    let td2 = TempDir::new().unwrap();
+    let r = git2::Repository::clone(&format!("http://localhost:{}", PORT), td2.path()).unwrap();
+    assert!(File::open(&td2.path().join("foo")).is_ok());
+    {
+        File::create(&td.path().join("bar")).unwrap();
+        let mut index = r1.index().unwrap();
+        index.add_path(&Path::new("bar")).unwrap();
+        index.write().unwrap();
+        let tree_id = index.write_tree().unwrap();
+        let parent = r1.head().ok().and_then(|h| h.target()).unwrap();
+        let parent = r1.find_commit(parent).unwrap();
+        r1.commit(
+            Some("HEAD"),
+            &sig,
+            &sig,
+            "test",
+            &r1.find_tree(tree_id).unwrap(),
+            &[&parent],
+        )
+        .unwrap();
+    }
+
+    let mut remote = r.find_remote("origin").unwrap();
+    remote
+        .fetch(&["refs/heads/*:refs/heads/*"], None, None)
+        .unwrap();
+    let b = r.find_branch("master", git2::BranchType::Local).unwrap();
+    let id = b.get().target().unwrap();
+    let obj = r.find_object(id, None).unwrap();
+    r.reset(&obj, git2::ResetType::Hard, None).unwrap();
+
+    assert!(File::open(&td2.path().join("bar")).is_ok());
+}
diff --git a/git2/.cargo-checksum.json b/git2/.cargo-checksum.json
new file mode 100644 (file)
index 0000000..2a96f97
--- /dev/null
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"483ffa6c5a0e015fa4bb3376aebb1287274d9e7c0cc6a3f63b17451fd7f6092e","CONTRIBUTING.md":"6a996c629901ea1907887ef180f1bc034b31de05e26bf2b93ea61c066557fcd2","Cargo.lock":"1dc16599ba4edba4e0112bba53c4ae8f4d869b1f9e87b3abfbd7879e8c8d99d0","Cargo.toml":"e5aecc6fc09a347ddabc49fbae90e9207f68258cb2fc7e172c5c45a5c234b9f0","FUNDING.json":"c06c025744e060bc3dad39a052e15207fae3a7e31fc414af09b4e40e27778e79","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","README.md":"8ef68e5e41b8960a67e612105cac7c65e2bde60494ac4f71c470826495129877","ci/publish.sh":"5b55345634baf7b0eb3aa3c84f44d1727a22a5197d0acd6f0312a47a1bfef889","examples/add.rs":"73ffbf602e48fced127415b8c2117df1e7c935bba212b6e360349550bed09afa","examples/blame.rs":"f6a8cff18376b407cc0bcb96f962df3845839cd59826f7ce8fdeb93daf277792","examples/cat-file.rs":"b7f7499d233fa1482d96e0988d49575b932025d99b9371acfed9d05b402682f7","examples/clone.rs":"0f50645cd7c31c370a6c8f9e7c15a9eca0ac214d69bffdd90c98933364f39c37","examples/diff.rs":"5b52278056b0c51c5e42a1ff96a80de34c7037ade01b49ac6a985d9f7a474c15","examples/fetch.rs":"f784e22534c71da2e8d30a59eba40d4b014cf1bf1a38cc4f0160a77a6dbbd334","examples/init.rs":"b9adb23a4bd950a2d3c3004d068c033b2bb8898d22608db16f695da48b54f293","examples/log.rs":"c6b622dcfbedb1e60cf6c41988daa74382da484506612834ea625f299741a1b4","examples/ls-remote.rs":"c21e3a4a661ce44e63aefeac680f27648e0ba3c895c9f0843557b25c20fc2390","examples/pull.rs":"f9ecf2b67170c48cc9f97133d3046578096f5d86beed7fc8d1f7ba0c906d59d5","examples/rev-list.rs":"28bfc6158ac782316dc06822a77d9952c70ea9ac2cafd3aa4809e88298b65ac8","examples/rev-parse.rs":"93e84d0e0211b68c49c397c4a55502f977b25fa0efbcff023b7f57f8f45be6e2","examples/status.rs":"f601427eb7f9b0dccee6e08dfd2b06b453a118f272c08ea0841e6a33896cac49","examples/tag.rs":"5cfb15225a1ae35b2490e01667a35deb50603293660344cb0210785568bdb4d2","src/apply.rs":"1ca5b0c06b96cd0af13dc322bf858e375a50f76d887c61052115ea8af2245847","src/attr.rs":"bc1957f495cf3aed4a663b50b43dd8deee6770ab259283673a883ac3c66908a7","src/blame.rs":"87cefd39c2bbb6fc7f9ecb549d44541f44ce61505a92368a93378b81df0b3c22","src/blob.rs":"7cb0326521dec9548a05c155283495c33f629678d9f7263e9de500c32052fb74","src/branch.rs":"1afa850c4c3a22531baddf5636bf55c9122a76b0d010e8392fbf04f1b76067ed","src/buf.rs":"d4d9795afc04eba6eb6a08c5dbcbc8e3e8341adc11701c65198d514f6cdfbac0","src/build.rs":"7e71b5821d8cfb86aa19bf3f7975c06a12e1589693f8de2ff10b332eb42714c6","src/call.rs":"e61e208050d3d17351da033a8fb48efbe2b7ef1ea0a1e81c98ad5df295162b5a","src/cert.rs":"6e8a78340cb8b3908977892d6d1723cebe70ff63819b28a63688e0fb4428b859","src/cherrypick.rs":"1ce882b6be825243d5551f11e1f48f53acc4aa52c4c5fb5510e69fd077a512bc","src/commit.rs":"a6bd389c2126e8646ac6b0544509e3016cd46450c9da686564cce710f0046b0c","src/config.rs":"c6d541117cceb6483b2bf43e3e9ea7575bf08a924e963ee89ee0cf1607b40516","src/cred.rs":"2b3578f37ecb442b363053028b64c70d99bf20f9a511e3de1fa58a383ff30849","src/describe.rs":"3adb68b3118bfa18c09f7ca767afa38d76e22130b1cebf20cc3b97c9a198957a","src/diff.rs":"8953973353edfc1a480dcb2fdeff256d668cdd90c2570563abb465ae6963d169","src/email.rs":"b95f32739a2adac55016e892d913fb4b03d10c0583569a613f6c13946238759d","src/error.rs":"06912d98095ec104b4bd416fcd8632422a2e78016f65a0bd07eb82eece473883","src/index.rs":"1f34cbc31ecb7d536f4138a2c4a35d9e24703c4d78575e4528c7bdf78c30e87c","src/indexer.rs":"c652d76bb2df02dc757ddeaa1395f022a61939d8ae08236f4f4369ab06c5511d","src/lib.rs":"96c40507a9339ac1150299d370045040a6bd416a063dfddf77174f2dfc8b68ad","src/mailmap.rs":"a034b1c86c8a8d4362939e2e2c1d0b1b2f5c71bac883b2b1b65b64078c70349e","src/mempack.rs":"8549f984360ac12868018e4f107a60386474c58cfb6c41993cbb0a820bd28908","src/merge.rs":"681f2f1e59cec49a8618a894e22645915e8f8a815a71d4ca7c6ab10fbdaeb919","src/message.rs":"ba445c543e2637b07c995ebc7a4f7b3c352061e2fecb83f9d95fce9c24f95d6c","src/note.rs":"55b286c3736833432d391184136f0fb0928e337990d1558166e2d998dc63f0dd","src/object.rs":"8b66f1c34570b37d204d0b3f626edbdd0baa7eb9734e3e444b72bf3e8fc46a27","src/odb.rs":"b3cd8696513b34fab005f6e37a05fc962f04ec40b35e2c89e6182aa63ea60625","src/oid.rs":"0f06937c0c4ac73024323a6cb119a8bd119f2b01054ed7e57a7b3fec6f346c6e","src/oid_array.rs":"4820657a5d4460a77a31fe0ba04c5575f6900a1767925a40464fadcacaf6a066","src/opts.rs":"3ee8746591c85a3257fd3ec74499c709174850a15e89d8df28b39c7e94f8cdfe","src/packbuilder.rs":"ea7acc367fee87e3a51e17e14fc64b44c3862b39054e17f0d38352b31325e26f","src/panic.rs":"62ef0684379f4cdebf9477e2f63c9f0ecc80fe608de2df32bca13ce0086249d6","src/patch.rs":"740fa2a29148605e3dac16f58fd603b408283cfe148b922fbe6b5a5d62f18db0","src/pathspec.rs":"54905396f243e254817bb017a8218f569a670c91346e21bb8ba2acf58ae9b943","src/proxy_options.rs":"cfa029317ae00a15074d4fd5de5d7c8da982459b915399c1d6010ddbee43ab28","src/push_update.rs":"1f10449f944b7000dcf6e242957adee0be5f45f8d458f2c83413293224cbbff7","src/rebase.rs":"179fc17e402d902300de8d37a65ceb0c29d6e8f4ba50cc8b7a7d8b09cfe9ef55","src/reference.rs":"87e4dad999f550050e25078157b2b413617698f10dcb844cdc2e4ea98b165271","src/reflog.rs":"b13ed72912f5375996b9ffb3a415371ae6c220de5a01226b13ca04fe97692f77","src/refspec.rs":"5107582963524fe9ff312a280232dba08d25be06fdf8f278c0bbfdbfa7a50402","src/remote.rs":"fc2d85a2fa80edff718bcb1397c625502ef1346af52803b1a61099444edcc527","src/remote_callbacks.rs":"cabd55a0e6d8aee101d1df7adae080e33872d816c7aed9e901304945139ae351","src/repo.rs":"df55248091999b9030225b1927a100315769d7cc488e4fcdb3b057126b37f45c","src/revert.rs":"b51dd98a9775e80dfd56b2fe13737e15baf64d3ac9c56546b2bbdfd53d1de7f9","src/revspec.rs":"29df0754775603ed1189bbe28f933f5851d50936eda380a57f175ec35496d637","src/revwalk.rs":"5b792871595ddd2bbc259de7d52174087ca34080c31fd907e23704001d7f4c61","src/signature.rs":"55c2ff56b05b8b48fa0b3a64a19557f49310749cc2100ec05f3216594b5dc4dc","src/stash.rs":"332cbec27ea7bff2f832173b7adfd3ed00e936022dc43797f31f06284bc1b93d","src/status.rs":"13712d5a07780fde16286fa897b965e2bf7881ed64468ebed1bbf9380f7c2a91","src/string_array.rs":"04501c0ca8440ec4460cc89a1c7f004dda027f8f0dc9b94a0f372a2391328d65","src/submodule.rs":"1d41cafa2523fdf4d8aae7d5dc38647c6062586d0ee1d91bc99be5d87ec6b2ee","src/tag.rs":"b7292fc74485950acfc484d4542d8a1bd135b957f0ad9c70e92762b805d73cd3","src/tagforeach.rs":"04ea7ff4072a270c5febaee8133ae93040eb81a139ed987238e0b500e9626c4c","src/test.rs":"cea22e72872aa7a875d1fc67390bd3c6d976719e0f2ad2a59d9954df78e0e7c2","src/time.rs":"7fd2bcdda9baf24c6292edd04be6112c83a57e8f9e566d6d219563ad98c12203","src/tracing.rs":"a08db58c46665280c9b364cde20d4e393d339e4920cd42d3da38cb8d1663f44e","src/transaction.rs":"6bb080f30646b5b3f22ac37f61945e58ce823944248e97fc58389cc8ca6c5895","src/transport.rs":"cec1afc572148833a5b43dc23252141d7dcb2b50246e01e86c67513d7b4a84ed","src/tree.rs":"a6996530d5feee417124ce7b27e69c20f7fc49cea7807ae4a96fe448578e4963","src/treebuilder.rs":"b04a16cd22e40f6c2e4d26d4ce343a2853e943474080e95b0e2250a5961e6c1d","src/util.rs":"f1089150444d090023d0eb31030c3e462f528a328f71165d70bc3434f8121078","src/version.rs":"bcc26c9dcc5bf872afd0741233f07981df0a4be28faeba9ad52eb437961eb30f","src/worktree.rs":"9690b76977ed44b8f8ea8b5530fae3a3b02f4bcbcb0826f4d74b2e3dff8bf76a","tests/add_extensions.rs":"af582f545b04ab475b7dcccbfc080876e643dc6d26ee3712347abeb19d4e1558","tests/get_extensions.rs":"9983f7ca5e117f74cbc55932f5478047b43087a4a83dc9324db9a62e6d3a298a","tests/global_state.rs":"c75947eca9718277da08722ca9a58d9b4154b36b1aca6453bb198238c17904ce","tests/remove_extensions.rs":"d267ffd5d2db7fe0c65b1940da5bc69d2ecc19d87f2955a7966f59a0e7a6879a"},"package":"3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff"}
\ No newline at end of file
diff --git a/git2/CHANGELOG.md b/git2/CHANGELOG.md
new file mode 100644 (file)
index 0000000..f76c3ba
--- /dev/null
@@ -0,0 +1,251 @@
+# Changelog
+
+## 0.19.0 - 2024-06-13
+[0.18.3...0.19.0](https://github.com/rust-lang/git2-rs/compare/git2-0.18.3...git2-0.19.0)
+
+### Added
+
+- Added `opts` functions to control server timeouts (`get_server_connect_timeout_in_milliseconds`, `set_server_connect_timeout_in_milliseconds`, `get_server_timeout_in_milliseconds`, `set_server_timeout_in_milliseconds`), and add `ErrorCode::Timeout`.
+  [#1052](https://github.com/rust-lang/git2-rs/pull/1052)
+
+### Changed
+
+- ❗ Updated to libgit2 [1.8.1](https://github.com/libgit2/libgit2/releases/tag/v1.8.1)
+  [#1032](https://github.com/rust-lang/git2-rs/pull/1032)
+- Reduced size of the `Error` struct.
+  [#1053](https://github.com/rust-lang/git2-rs/pull/1053)
+
+### Fixed
+
+- Fixed some callbacks to relay the error from the callback to libgit2.
+  [#1043](https://github.com/rust-lang/git2-rs/pull/1043)
+
+## 0.18.3 - 2024-03-18
+[0.18.2...0.18.3](https://github.com/rust-lang/git2-rs/compare/git2-0.18.2...git2-0.18.3)
+
+### Added
+
+- Added `opts::` functions to get / set libgit2 mwindow options
+  [#1035](https://github.com/rust-lang/git2-rs/pull/1035)
+
+
+### Changed
+
+- Updated examples to use clap instead of structopt
+  [#1007](https://github.com/rust-lang/git2-rs/pull/1007)
+
+## 0.18.2 - 2024-02-06
+[0.18.1...0.18.2](https://github.com/rust-lang/git2-rs/compare/git2-0.18.1...git2-0.18.2)
+
+### Added
+
+- Added `opts::set_ssl_cert_file` and `opts::set_ssl_cert_dir` for setting Certificate Authority file locations.
+  [#997](https://github.com/rust-lang/git2-rs/pull/997)
+- Added `TreeIter::nth` which makes jumping ahead in the iterator more efficient.
+  [#1004](https://github.com/rust-lang/git2-rs/pull/1004)
+- Added `Repository::find_commit_by_prefix` to find a commit by a shortened hash.
+  [#1011](https://github.com/rust-lang/git2-rs/pull/1011)
+- Added `Repository::find_tag_by_prefix` to find a tag by a shortened hash.
+  [#1015](https://github.com/rust-lang/git2-rs/pull/1015)
+- Added `Repository::find_object_by_prefix` to find an object by a shortened hash.
+  [#1014](https://github.com/rust-lang/git2-rs/pull/1014)
+
+### Changed
+
+- ❗ Updated to libgit2 [1.7.2](https://github.com/libgit2/libgit2/releases/tag/v1.7.2).
+  This fixes [CVE-2024-24575](https://github.com/libgit2/libgit2/security/advisories/GHSA-54mf-x2rh-hq9v) and [CVE-2024-24577](https://github.com/libgit2/libgit2/security/advisories/GHSA-j2v7-4f6v-gpg8).
+  [#1017](https://github.com/rust-lang/git2-rs/pull/1017)
+
+## 0.18.1 - 2023-09-20
+[0.18.0...0.18.1](https://github.com/rust-lang/git2-rs/compare/git2-0.18.0...git2-0.18.1)
+
+### Added
+
+- Added `FetchOptions::depth` to set the depth of a fetch or clone, adding support for shallow clones.
+  [#979](https://github.com/rust-lang/git2-rs/pull/979)
+
+### Fixed
+
+- Fixed an internal data type (`TreeWalkCbData`) to not assume it is a transparent type while casting.
+  [#989](https://github.com/rust-lang/git2-rs/pull/989)
+- Fixed so that `DiffPatchidOptions` and `StashSaveOptions` are publicly exported allowing the corresponding APIs to actually be used.
+  [#988](https://github.com/rust-lang/git2-rs/pull/988)
+
+## 0.18.0 - 2023-08-28
+[0.17.2...0.18.0](https://github.com/rust-lang/git2-rs/compare/0.17.2...git2-0.18.0)
+
+### Added
+
+- Added `Blame::blame_buffer` for getting blame data for a file that has been modified in memory.
+  [#981](https://github.com/rust-lang/git2-rs/pull/981)
+
+### Changed
+
+- Updated to libgit2 [1.7.0](https://github.com/libgit2/libgit2/releases/tag/v1.7.0).
+  [#968](https://github.com/rust-lang/git2-rs/pull/968)
+- Updated to libgit2 [1.7.1](https://github.com/libgit2/libgit2/releases/tag/v1.7.1).
+  [#982](https://github.com/rust-lang/git2-rs/pull/982)
+- Switched from bitflags 1.x to 2.1. This brings some small changes to types generated by bitflags.
+  [#973](https://github.com/rust-lang/git2-rs/pull/973)
+- Changed `Revwalk::with_hide_callback` to take a mutable reference to its callback to enforce type safety.
+  [#970](https://github.com/rust-lang/git2-rs/pull/970)
+- Implemented `FusedIterator` for many iterators that can support it.
+  [#955](https://github.com/rust-lang/git2-rs/pull/955)
+
+### Fixed
+
+- Fixed builds with cargo's `-Zminimal-versions`.
+  [#960](https://github.com/rust-lang/git2-rs/pull/960)
+
+## 0.17.2 - 2023-05-27
+[0.17.1...0.17.2](https://github.com/rust-lang/git2-rs/compare/0.17.1...0.17.2)
+
+### Added
+- Added support for stashing with options (which can support partial stashing).
+  [#930](https://github.com/rust-lang/git2-rs/pull/930)
+
+## 0.17.1 - 2023-04-13
+[0.17.0...0.17.1](https://github.com/rust-lang/git2-rs/compare/0.17.0...0.17.1)
+
+### Changed
+
+- Updated to libgit2 [1.6.4](https://github.com/libgit2/libgit2/releases/tag/v1.6.4).
+  [#948](https://github.com/rust-lang/git2-rs/pull/948)
+
+## 0.17.0 - 2023-04-02
+[0.16.1...0.17.0](https://github.com/rust-lang/git2-rs/compare/0.16.1...0.17.0)
+
+### Added
+
+- Added `IntoIterator` implementation for `Statuses`.
+  [#880](https://github.com/rust-lang/git2-rs/pull/880)
+- Added `Reference::symbolic_set_target`
+  [#893](https://github.com/rust-lang/git2-rs/pull/893)
+- Added `Copy`, `Clone`, `Debug`, `PartialEq`, and `Eq` implementations for `AutotagOption` and `FetchPrune`.
+  [#889](https://github.com/rust-lang/git2-rs/pull/889)
+- Added `Eq` and `PartialEq` implementations for `Signature`.
+  [#890](https://github.com/rust-lang/git2-rs/pull/890)
+- Added `Repository::discover_path`.
+  [#883](https://github.com/rust-lang/git2-rs/pull/883)
+- Added `Submodule::repo_init`.
+  [#914](https://github.com/rust-lang/git2-rs/pull/914)
+- Added `Tag::is_valid_name`.
+  [#882](https://github.com/rust-lang/git2-rs/pull/882)
+- Added `Repository::set_head_bytes`.
+  [#931](https://github.com/rust-lang/git2-rs/pull/931)
+- Added the `Indexer` type which is a low-level API for storing and indexing pack files.
+  [#911](https://github.com/rust-lang/git2-rs/pull/911)
+- Added `Index::find_prefix`.
+  [#903](https://github.com/rust-lang/git2-rs/pull/903)
+- Added support for the deprecated group-writeable blob mode. This adds a new variant to `FileMode`.
+  [#887](https://github.com/rust-lang/git2-rs/pull/887)
+- Added `PushCallbacks::push_negotiation` callback and the corresponding `PushUpdate` type for getting receiving information about the updates to perform.
+  [#926](https://github.com/rust-lang/git2-rs/pull/926)
+
+### Changed
+
+- Updated to libgit2 [1.6.3](https://github.com/libgit2/libgit2/blob/main/docs/changelog.md#v163).
+  This brings in many changes, including better SSH host key support on Windows and better SSH host key algorithm negotiation.
+  1.6.3 is now the minimum supported version.
+  [#935](https://github.com/rust-lang/git2-rs/pull/935)
+- Updated libssh2-sys from 0.2 to 0.3.
+  This brings in numerous changes, including SHA2 algorithm support with RSA.
+  [#919](https://github.com/rust-lang/git2-rs/pull/919)
+- Changed `RemoteCallbacks::credentials` callback error handler to correctly set the libgit2 error class.
+  [#918](https://github.com/rust-lang/git2-rs/pull/918)
+- `DiffOptions::flag` now takes a `git_diff_option_t` type.
+  [#935](https://github.com/rust-lang/git2-rs/pull/935)
+
+
+## 0.16.1 - 2023-01-20
+[0.16.0...0.16.1](https://github.com/rust-lang/git2-rs/compare/0.16.0...0.16.1)
+
+### Changed
+- Updated to [libgit2-sys 0.14.2+1.5.1](libgit2-sys/CHANGELOG.md#0142151---2023-01-20)
+
+## 0.16.0 - 2023-01-10
+[0.15.0...0.16.0](https://github.com/rust-lang/git2-rs/compare/0.15.0...0.16.0)
+
+### Changed
+- Added ability to get the SSH host key and its type.
+  This includes an API breaking change to the `certificate_check` callback.
+  [#909](https://github.com/rust-lang/git2-rs/pull/909)
+- Updated to [libgit2-sys 0.14.1+1.5.0](libgit2-sys/CHANGELOG.md#0141150---2023-01-10)
+
+## 0.15.0 - 2022-07-28
+[0.14.4...0.15.0](https://github.com/rust-lang/git2-rs/compare/0.14.4...0.15.0)
+
+### Added
+- Added `Repository::tag_annotation_create` binding `git_tag_annotation_create`.
+  [#845](https://github.com/rust-lang/git2-rs/pull/845)
+- Added the `Email` type which represents a patch in mbox format for sending via email.
+  Added the `EmailCreateOptions` struct to control formatting of the email.
+  Deprecates `Diff::format_email`, use `Email::from_diff` instead.
+  [#847](https://github.com/rust-lang/git2-rs/pull/847)
+- Added `ErrorCode::Owner` to map to the new `GIT_EOWNER` errors.
+  [#839](https://github.com/rust-lang/git2-rs/pull/839)
+- Added `opts::set_verify_owner_validation` to set whether or not ownership validation is performed.
+  [#839](https://github.com/rust-lang/git2-rs/pull/839)
+
+### Changed
+- Updated to [libgit2-sys 0.14.0+1.5.0](libgit2-sys/CHANGELOG.md#0140150---2022-07-28)
+- Removed the `Iterator` implementation for `ConfigEntries` due to the unsound usage of the API which allowed values to be used after free.
+  Added `ConfigEntries::next` and `ConfigEntries::for_each` for iterating over all entries in a safe manor.
+  [#854](https://github.com/rust-lang/git2-rs/pull/854)
+
+## 0.14.4 - 2022-05-19
+[0.14.3...0.14.4](https://github.com/rust-lang/git2-rs/compare/0.14.3...0.14.4)
+
+### Added
+- Added `Commit::body` and `Commit::body_bytes` for retrieving the commit message body.
+  [#835](https://github.com/rust-lang/git2-rs/pull/835)
+- Added `Tree::get_name_bytes` to handle non-UTF-8 entry names.
+  [#841](https://github.com/rust-lang/git2-rs/pull/841)
+
+### Changed
+- Updated to [libgit2-sys 0.13.4+1.4.2](libgit2-sys/CHANGELOG.md#0134142---2022-05-10)
+
+## 0.14.3 - 2022-04-27
+[0.14.2...0.14.3](https://github.com/rust-lang/git2-rs/compare/0.14.2...0.14.3)
+
+### Changed
+- Updated to [libgit2-sys 0.13.3+1.4.2](libgit2-sys/CHANGELOG.md#0133142---2022-04-27)
+
+### Fixed
+- Fixed the lifetime of `Remote::create_detached`.
+  [#825](https://github.com/rust-lang/git2-rs/pull/825)
+
+## 0.14.2 - 2022-03-10
+[0.14.1...0.14.2](https://github.com/rust-lang/git2-rs/compare/0.14.1...0.14.2)
+
+### Added
+- Added `Odb::exists_ext` to checks if an object database has an object, with extended flags.
+  [#818](https://github.com/rust-lang/git2-rs/pull/818)
+
+### Changed
+- Updated to [libgit2-sys 0.13.2+1.4.2](libgit2-sys/CHANGELOG.md#0132142---2022-03-10)
+
+## 0.14.1 - 2022-02-28
+[0.14.0...0.14.1](https://github.com/rust-lang/git2-rs/compare/0.14.0...0.14.1)
+
+### Changed
+- Updated to [libgit2-sys 0.13.1+1.4.2](libgit2-sys/CHANGELOG.md#0131142---2022-02-28)
+
+## 0.14.0 - 2022-02-24
+[0.13.25...0.14.0](https://github.com/rust-lang/git2-rs/compare/0.13.25...0.14.0)
+
+### Added
+- Added `opts::get_extensions` and `opts::set_extensions` to support git extensions.
+  [#791](https://github.com/rust-lang/git2-rs/pull/791)
+- Added `PackBuilder::name` and `PackBuilder::name_bytes`.
+  [#806](https://github.com/rust-lang/git2-rs/pull/806)
+    - Deprecated `PackBuilder::hash`, use `PackBuilder::name` instead.
+- Added `FetchOptions::follow_redirects` and `PushOptions::follow_redirects`.
+  [#806](https://github.com/rust-lang/git2-rs/pull/806)
+- Added `StatusOptions::rename_threshold`.
+  [#806](https://github.com/rust-lang/git2-rs/pull/806)
+
+### Changed
+- Updated to [libgit2-sys 0.13.0+1.4.1](libgit2-sys/CHANGELOG.md#0130141---2022-02-24)
+  [#806](https://github.com/rust-lang/git2-rs/pull/806)
+  [#811](https://github.com/rust-lang/git2-rs/pull/811)
diff --git a/git2/CONTRIBUTING.md b/git2/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..c842f1e
--- /dev/null
@@ -0,0 +1,63 @@
+# Contributing
+
+## Updating libgit2
+
+The following steps can be used to update libgit2:
+
+1. Update the submodule.
+   There are several ways to go about this.
+   One way is to go to the `libgit2-sys/libgit2` directory and run `git fetch origin` to download the latest updates, and then check out a specific tag (such as `git checkout v1.4.1`).
+2. Update all the references to the version:
+    * Update [`libgit2-sys/build.rs`](https://github.com/rust-lang/git2-rs/blob/master/libgit2-sys/build.rs).
+      There is a version probe (search for `cfg.range_version`) which should be updated.
+    * Update the version in
+      [`libgit2-sys/Cargo.toml`](https://github.com/rust-lang/git2-rs/blob/master/libgit2-sys/Cargo.toml).
+      Update the metadata portion (the part after the `+`) to match libgit2.
+      Also bump the Cargo version (the part before the `+`), keeping in mind
+      if this will be a SemVer breaking change or not.
+    * Update the dependency version in [`Cargo.toml`](https://github.com/rust-lang/git2-rs/blob/master/Cargo.toml) to match the version in the last step (do not include the `+` metadata).
+      Also update the version of the `git2` crate itself so it will pick up the change to `libgit2-sys` (also keeping in mind if it is a SemVer breaking release).
+    * Update the version in [`README.md`](https://github.com/rust-lang/git2-rs/blob/master/README.md) if needed.
+      There are two places, the `Cargo.toml` example and the description of the libgit2 version it binds with.
+    * If there was a SemVer-breaking version bump for either library, also update the `html_root_url` attribute in the `lib.rs` of each library.
+3. Run tests.
+   `cargo test -p git2 -p git2-curl` is a good starting point.
+4. Run `systest`.
+   This will validate for any C-level API problems.
+
+   `cargo run -p systest`
+
+   The changelog at <https://github.com/libgit2/libgit2/blob/main/docs/changelog.md>
+   can be helpful for seeing what has changed.
+   The project has recently started labeling API and ABI breaking changes with labels:
+   <https://github.com/libgit2/libgit2/pulls?q=is%3Apr+label%3A%22api+breaking%22%2C%22abi+breaking%22+is%3Aclosed>
+   Alternatively, running `git diff [PREV_VERSION]..[NEW_VERSION] --ignore-all-space -- include/` can provide an overview of changes made to the API.
+4. Once you have everything functional, publish a PR with the updates.
+
+## Release process
+
+Checklist for preparing for a release:
+
+- Make sure the versions have been bumped and are pointing at what is expected.
+    - Version of `libgit2-sys`
+    - Version of `git2`
+    - Version of `git2-curl`
+    - `git2`'s dependency on `libgit2-sys`
+    - `git2-curl`'s dependency on `git2`
+    - The libgit2 version probe in `libgit2-sys/build.rs`
+    - Update the version in `README.md`
+    - Check the `html_root_url` values in the source code.
+- Update the change logs:
+    - [`CHANGELOG.md`](https://github.com/rust-lang/git2-rs/blob/master/CHANGELOG.md)
+    - [`libgit2-sys/CHANGELOG.md`](https://github.com/rust-lang/git2-rs/blob/master/libgit2-sys/CHANGELOG.md)
+    - [`git2-curl/CHANGELOG.md`](https://github.com/rust-lang/git2-rs/blob/master/git2-curl/CHANGELOG.md)
+
+There is a GitHub workflow to handle publishing to crates.io and tagging the release. There are two different ways to run it:
+
+- In the GitHub web UI:
+    1. Go to <https://github.com/rust-lang/git2-rs/actions/workflows/publish.yml> (you can navigate here via the "Actions" tab at the top).
+    2. Click the "Run workflow" drop-down on the right.
+    3. Choose which crates to publish. It's OK to leave everything checked, it will skip if it is already published. Uncheck a crate if the version has been bumped in git, but you don't want to publish that particular one, yet.
+    4. Click "Run workflow"
+- In the CLI:
+    1. Run `gh workflow run publish.yml -R rust-lang/git2-rs`
diff --git a/git2/Cargo.lock b/git2/Cargo.lock
new file mode 100644 (file)
index 0000000..e9fde35
--- /dev/null
@@ -0,0 +1,842 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "cc"
+version = "1.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "cmake"
+version = "0.1.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "git2"
+version = "0.20.0"
+dependencies = [
+ "bitflags",
+ "clap",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "tempfile",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itoa"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.169"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.18.0+1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec"
+dependencies = [
+ "cc",
+ "libc",
+ "libssh2-sys",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libssh2-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
+dependencies = [
+ "cc",
+ "cmake",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+
+[[package]]
+name = "litemap"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-src"
+version = "300.4.1+3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "time"
+version = "0.3.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/git2/Cargo.toml b/git2/Cargo.toml
new file mode 100644 (file)
index 0000000..edabcd9
--- /dev/null
@@ -0,0 +1,165 @@
+# 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 = "2018"
+name = "git2"
+version = "0.20.0"
+authors = [
+    "Josh Triplett <josh@joshtriplett.org>",
+    "Alex Crichton <alex@alexcrichton.com>",
+]
+build = false
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = """
+Bindings to libgit2 for interoperating with git repositories. This library is
+both threadsafe and memory safe and allows both reading and writing git
+repositories.
+"""
+documentation = "https://docs.rs/git2"
+readme = "README.md"
+keywords = ["git"]
+categories = ["api-bindings"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/git2-rs"
+
+[lib]
+name = "git2"
+path = "src/lib.rs"
+
+[[example]]
+name = "add"
+path = "examples/add.rs"
+
+[[example]]
+name = "blame"
+path = "examples/blame.rs"
+
+[[example]]
+name = "cat-file"
+path = "examples/cat-file.rs"
+
+[[example]]
+name = "clone"
+path = "examples/clone.rs"
+
+[[example]]
+name = "diff"
+path = "examples/diff.rs"
+
+[[example]]
+name = "fetch"
+path = "examples/fetch.rs"
+
+[[example]]
+name = "init"
+path = "examples/init.rs"
+
+[[example]]
+name = "log"
+path = "examples/log.rs"
+
+[[example]]
+name = "ls-remote"
+path = "examples/ls-remote.rs"
+
+[[example]]
+name = "pull"
+path = "examples/pull.rs"
+
+[[example]]
+name = "rev-list"
+path = "examples/rev-list.rs"
+
+[[example]]
+name = "rev-parse"
+path = "examples/rev-parse.rs"
+
+[[example]]
+name = "status"
+path = "examples/status.rs"
+
+[[example]]
+name = "tag"
+path = "examples/tag.rs"
+
+[[test]]
+name = "add_extensions"
+path = "tests/add_extensions.rs"
+
+[[test]]
+name = "get_extensions"
+path = "tests/get_extensions.rs"
+
+[[test]]
+name = "global_state"
+path = "tests/global_state.rs"
+
+[[test]]
+name = "remove_extensions"
+path = "tests/remove_extensions.rs"
+
+[dependencies.bitflags]
+version = "2.1.0"
+
+[dependencies.libc]
+version = "0.2"
+
+[dependencies.libgit2-sys]
+version = "0.18.0"
+
+[dependencies.log]
+version = "0.4.8"
+
+[dependencies.url]
+version = "2.0"
+
+[dev-dependencies.clap]
+version = "4.4.13"
+features = ["derive"]
+
+[dev-dependencies.tempfile]
+version = "3.1.0"
+
+[dev-dependencies.time]
+version = "0.3.37"
+features = ["formatting"]
+
+[features]
+default = [
+    "ssh",
+    "https",
+]
+https = [
+    "libgit2-sys/https",
+    "openssl-sys",
+    "openssl-probe",
+]
+ssh = ["libgit2-sys/ssh"]
+unstable = []
+vendored-libgit2 = ["libgit2-sys/vendored"]
+vendored-openssl = [
+    "openssl-sys/vendored",
+    "libgit2-sys/vendored-openssl",
+]
+zlib-ng-compat = ["libgit2-sys/zlib-ng-compat"]
+
+[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.openssl-probe]
+version = "0.1"
+optional = true
+
+[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies.openssl-sys]
+version = "0.9.45"
+optional = true
diff --git a/git2/FUNDING.json b/git2/FUNDING.json
new file mode 100644 (file)
index 0000000..480d415
--- /dev/null
@@ -0,0 +1,7 @@
+{
+  "drips": {
+    "ethereum": {
+      "ownedBy": "0x298f6e7CC02D6aa94E2b135f46F1761da7A44E58"
+    }
+  }
+}
diff --git a/git2/LICENSE-APACHE b/git2/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/git2/LICENSE-MIT b/git2/LICENSE-MIT
new file mode 100644 (file)
index 0000000..39e0ed6
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/git2/README.md b/git2/README.md
new file mode 100644 (file)
index 0000000..ae1c377
--- /dev/null
@@ -0,0 +1,76 @@
+# git2-rs
+
+[Documentation](https://docs.rs/git2)
+
+libgit2 bindings for Rust.
+
+```toml
+[dependencies]
+git2 = "0.20.0"
+```
+
+## Rust version requirements
+
+git2-rs works with stable Rust, and typically works with the most recent prior
+stable release as well.
+
+## Version of libgit2
+
+Currently this library requires libgit2 1.9.0 (or newer patch versions). The
+source for libgit2 is included in the libgit2-sys crate so there's no need to
+pre-install the libgit2 library, the libgit2-sys crate will figure that and/or
+build that for you. On the other hand, if an appropriate version of `libgit2`
+is present, `git2` will attempt to dynamically link it.
+
+To be more precise, the vendored `libgit2` is linked statically if two
+conditions both hold:
+
+- The environment variable `LIBGIT2_NO_VENDOR=1` is **not** set
+- **and** either a) The Cargo feature `vendored-libgit2` is set or b) an
+  appropriate version of `libgit2` cannot be found on the system.
+
+In particular, note that the environment variable overrides the Cargo feature.
+
+## Building git2-rs
+
+```sh
+$ git clone https://github.com/rust-lang/git2-rs
+$ cd git2-rs
+$ cargo build
+```
+
+### Automating Testing
+
+Running tests and handling all of the associated edge cases on every commit
+proves tedious very quickly.  To automate tests and handle proper stashing and
+unstashing of unstaged changes and thus avoid nasty surprises, use the
+pre-commit hook found [here][pre-commit-hook] and place it into the
+`.git/hooks/` with the name `pre-commit`.  You may need to add execution
+permissions with `chmod +x`.
+
+To skip tests on a simple commit or doc-fixes, use `git commit --no-verify`.
+
+## Building on macOS 10.10+
+
+If the `ssh` feature is enabled (and it is by default) then this library depends
+on libssh2 which depends on OpenSSL. To get OpenSSL working follow the
+[`openssl` crate's instructions](https://github.com/sfackler/rust-openssl/blob/master/openssl/src/lib.rs#L31).
+
+# License
+
+This project is licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
+   https://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or
+   https://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in git2-rs by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[pre-commit-hook]: https://gist.github.com/glfmn/0c5e9e2b41b48007ed3497d11e3dbbfa
diff --git a/git2/ci/publish.sh b/git2/ci/publish.sh
new file mode 100755 (executable)
index 0000000..cfc0846
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+set -e
+
+function publish {
+    publish_this="$1"
+    crate_name="$2"
+    manifest="$3"
+
+    if [ "$publish_this" != "true" ]
+    then
+        echo "Skipping $crate_name, publish not requested."
+        return
+    fi
+
+    # Get the version from Cargo.toml
+    version=`sed -n -E 's/^version = "(.*)"/\1/p' $manifest`
+
+    # Check crates.io if it is already published
+    set +e
+    output=`curl --fail --silent --head --location https://static.crates.io/crates/$crate_name/$version/download`
+    res="$?"
+    set -e
+    case $res in
+        0)
+            echo "${crate_name}@${version} appears to already be published"
+            return
+            ;;
+        22) ;;
+        *)
+            echo "Failed to check ${crate_name}@${version} res: $res"
+            echo "$output"
+            exit 1
+            ;;
+    esac
+
+    cargo publish --manifest-path $manifest --no-verify
+
+    tag="${crate_name}-${version}"
+    git tag $tag
+    git push origin "$tag"
+}
+
+publish $PUBLISH_LIBGIT2_SYS libgit2-sys libgit2-sys/Cargo.toml
+publish $PUBLISH_GIT2 git2 Cargo.toml
+publish $PUBLISH_GIT2_CURL git2-curl git2-curl/Cargo.toml
diff --git a/git2/examples/add.rs b/git2/examples/add.rs
new file mode 100644 (file)
index 0000000..57c9bb1
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * libgit2 "add" example - shows how to modify the index
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+#![allow(trivial_casts)]
+
+use clap::Parser;
+use git2::Repository;
+use std::path::Path;
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "spec")]
+    arg_spec: Vec<String>,
+    #[structopt(name = "dry_run", short = 'n', long)]
+    /// dry run
+    flag_dry_run: bool,
+    #[structopt(name = "verbose", short, long)]
+    /// be verbose
+    flag_verbose: bool,
+    #[structopt(name = "update", short, long)]
+    /// update tracked files
+    flag_update: bool,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let repo = Repository::open(&Path::new("."))?;
+    let mut index = repo.index()?;
+
+    let cb = &mut |path: &Path, _matched_spec: &[u8]| -> i32 {
+        let status = repo.status_file(path).unwrap();
+
+        let ret = if status.contains(git2::Status::WT_MODIFIED)
+            || status.contains(git2::Status::WT_NEW)
+        {
+            println!("add '{}'", path.display());
+            0
+        } else {
+            1
+        };
+
+        if args.flag_dry_run {
+            1
+        } else {
+            ret
+        }
+    };
+    let cb = if args.flag_verbose || args.flag_update {
+        Some(cb as &mut git2::IndexMatchedPath)
+    } else {
+        None
+    };
+
+    if args.flag_update {
+        index.update_all(args.arg_spec.iter(), cb)?;
+    } else {
+        index.add_all(args.arg_spec.iter(), git2::IndexAddOption::DEFAULT, cb)?;
+    }
+
+    index.write()?;
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/blame.rs b/git2/examples/blame.rs
new file mode 100644 (file)
index 0000000..202989b
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * libgit2 "blame" example - shows how to use the blame API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{BlameOptions, Repository};
+use std::io::{BufRead, BufReader};
+use std::path::Path;
+
+#[derive(Parser)]
+#[allow(non_snake_case)]
+struct Args {
+    #[structopt(name = "path")]
+    arg_path: String,
+    #[structopt(name = "spec")]
+    arg_spec: Option<String>,
+    #[structopt(short = 'M')]
+    /// find line moves within and across files
+    flag_M: bool,
+    #[structopt(short = 'C')]
+    /// find line copies within and across files
+    flag_C: bool,
+    #[structopt(short = 'F')]
+    /// follow only the first parent commits
+    flag_F: bool,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let repo = Repository::open(".")?;
+    let path = Path::new(&args.arg_path[..]);
+
+    // Prepare our blame options
+    let mut opts = BlameOptions::new();
+    opts.track_copies_same_commit_moves(args.flag_M)
+        .track_copies_same_commit_copies(args.flag_C)
+        .first_parent(args.flag_F);
+
+    let mut commit_id = "HEAD".to_string();
+
+    // Parse spec
+    if let Some(spec) = args.arg_spec.as_ref() {
+        let revspec = repo.revparse(spec)?;
+
+        let (oldest, newest) = if revspec.mode().contains(git2::RevparseMode::SINGLE) {
+            (None, revspec.from())
+        } else if revspec.mode().contains(git2::RevparseMode::RANGE) {
+            (revspec.from(), revspec.to())
+        } else {
+            (None, None)
+        };
+
+        if let Some(commit) = oldest {
+            opts.oldest_commit(commit.id());
+        }
+
+        if let Some(commit) = newest {
+            opts.newest_commit(commit.id());
+            if !commit.id().is_zero() {
+                commit_id = format!("{}", commit.id())
+            }
+        }
+    }
+
+    let spec = format!("{}:{}", commit_id, path.display());
+    let blame = repo.blame_file(path, Some(&mut opts))?;
+    let object = repo.revparse_single(&spec[..])?;
+    let blob = repo.find_blob(object.id())?;
+    let reader = BufReader::new(blob.content());
+
+    for (i, line) in reader.lines().enumerate() {
+        if let (Ok(line), Some(hunk)) = (line, blame.get_line(i + 1)) {
+            let sig = hunk.final_signature();
+            println!(
+                "{} {} <{}> {}",
+                hunk.final_commit_id(),
+                String::from_utf8_lossy(sig.name_bytes()),
+                String::from_utf8_lossy(sig.email_bytes()),
+                line
+            );
+        }
+    }
+
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/cat-file.rs b/git2/examples/cat-file.rs
new file mode 100644 (file)
index 0000000..6196b9b
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * libgit2 "cat-file" example - shows how to print data from the ODB
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use std::io::{self, Write};
+
+use clap::Parser;
+use git2::{Blob, Commit, ObjectType, Repository, Signature, Tag, Tree};
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "object")]
+    arg_object: String,
+    #[structopt(short = 't')]
+    /// show the object type
+    flag_t: bool,
+    #[structopt(short = 's')]
+    /// show the object size
+    flag_s: bool,
+    #[structopt(short = 'e')]
+    /// suppress all output
+    flag_e: bool,
+    #[structopt(short = 'p')]
+    /// pretty print the contents of the object
+    flag_p: bool,
+    #[structopt(name = "quiet", short, long)]
+    /// suppress output
+    flag_q: bool,
+    #[structopt(name = "verbose", short, long)]
+    flag_v: bool,
+    #[structopt(name = "dir", long = "git-dir")]
+    /// use the specified directory as the base directory
+    flag_git_dir: Option<String>,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
+    let repo = Repository::open(path)?;
+
+    let obj = repo.revparse_single(&args.arg_object)?;
+    if args.flag_v && !args.flag_q {
+        println!("{} {}\n--", obj.kind().unwrap().str(), obj.id());
+    }
+
+    if args.flag_t {
+        println!("{}", obj.kind().unwrap().str());
+    } else if args.flag_s || args.flag_e {
+        /* ... */
+    } else if args.flag_p {
+        match obj.kind() {
+            Some(ObjectType::Blob) => {
+                show_blob(obj.as_blob().unwrap());
+            }
+            Some(ObjectType::Commit) => {
+                show_commit(obj.as_commit().unwrap());
+            }
+            Some(ObjectType::Tag) => {
+                show_tag(obj.as_tag().unwrap());
+            }
+            Some(ObjectType::Tree) => {
+                show_tree(obj.as_tree().unwrap());
+            }
+            Some(ObjectType::Any) | None => println!("unknown {}", obj.id()),
+        }
+    }
+    Ok(())
+}
+
+fn show_blob(blob: &Blob) {
+    io::stdout().write_all(blob.content()).unwrap();
+}
+
+fn show_commit(commit: &Commit) {
+    println!("tree {}", commit.tree_id());
+    for parent in commit.parent_ids() {
+        println!("parent {}", parent);
+    }
+    show_sig("author", Some(commit.author()));
+    show_sig("committer", Some(commit.committer()));
+    if let Some(msg) = commit.message() {
+        println!("\n{}", msg);
+    }
+}
+
+fn show_tag(tag: &Tag) {
+    println!("object {}", tag.target_id());
+    println!("type {}", tag.target_type().unwrap().str());
+    println!("tag {}", tag.name().unwrap());
+    show_sig("tagger", tag.tagger());
+
+    if let Some(msg) = tag.message() {
+        println!("\n{}", msg);
+    }
+}
+
+fn show_tree(tree: &Tree) {
+    for entry in tree.iter() {
+        println!(
+            "{:06o} {} {}\t{}",
+            entry.filemode(),
+            entry.kind().unwrap().str(),
+            entry.id(),
+            entry.name().unwrap()
+        );
+    }
+}
+
+fn show_sig(header: &str, sig: Option<Signature>) {
+    let sig = match sig {
+        Some(s) => s,
+        None => return,
+    };
+    let offset = sig.when().offset_minutes();
+    let (sign, offset) = if offset < 0 {
+        ('-', -offset)
+    } else {
+        ('+', offset)
+    };
+    let (hours, minutes) = (offset / 60, offset % 60);
+    println!(
+        "{} {} {} {}{:02}{:02}",
+        header,
+        sig,
+        sig.when().seconds(),
+        sign,
+        hours,
+        minutes
+    );
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/clone.rs b/git2/examples/clone.rs
new file mode 100644 (file)
index 0000000..f6d00b1
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * libgit2 "clone" example
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::build::{CheckoutBuilder, RepoBuilder};
+use git2::{FetchOptions, Progress, RemoteCallbacks};
+use std::cell::RefCell;
+use std::io::{self, Write};
+use std::path::{Path, PathBuf};
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "url")]
+    arg_url: String,
+    #[structopt(name = "path")]
+    arg_path: String,
+}
+
+struct State {
+    progress: Option<Progress<'static>>,
+    total: usize,
+    current: usize,
+    path: Option<PathBuf>,
+    newline: bool,
+}
+
+fn print(state: &mut State) {
+    let stats = state.progress.as_ref().unwrap();
+    let network_pct = (100 * stats.received_objects()) / stats.total_objects();
+    let index_pct = (100 * stats.indexed_objects()) / stats.total_objects();
+    let co_pct = if state.total > 0 {
+        (100 * state.current) / state.total
+    } else {
+        0
+    };
+    let kbytes = stats.received_bytes() / 1024;
+    if stats.received_objects() == stats.total_objects() {
+        if !state.newline {
+            println!();
+            state.newline = true;
+        }
+        print!(
+            "Resolving deltas {}/{}\r",
+            stats.indexed_deltas(),
+            stats.total_deltas()
+        );
+    } else {
+        print!(
+            "net {:3}% ({:4} kb, {:5}/{:5})  /  idx {:3}% ({:5}/{:5})  \
+             /  chk {:3}% ({:4}/{:4}) {}\r",
+            network_pct,
+            kbytes,
+            stats.received_objects(),
+            stats.total_objects(),
+            index_pct,
+            stats.indexed_objects(),
+            stats.total_objects(),
+            co_pct,
+            state.current,
+            state.total,
+            state
+                .path
+                .as_ref()
+                .map(|s| s.to_string_lossy().into_owned())
+                .unwrap_or_default()
+        )
+    }
+    io::stdout().flush().unwrap();
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let state = RefCell::new(State {
+        progress: None,
+        total: 0,
+        current: 0,
+        path: None,
+        newline: false,
+    });
+    let mut cb = RemoteCallbacks::new();
+    cb.transfer_progress(|stats| {
+        let mut state = state.borrow_mut();
+        state.progress = Some(stats.to_owned());
+        print(&mut *state);
+        true
+    });
+
+    let mut co = CheckoutBuilder::new();
+    co.progress(|path, cur, total| {
+        let mut state = state.borrow_mut();
+        state.path = path.map(|p| p.to_path_buf());
+        state.current = cur;
+        state.total = total;
+        print(&mut *state);
+    });
+
+    let mut fo = FetchOptions::new();
+    fo.remote_callbacks(cb);
+    RepoBuilder::new()
+        .fetch_options(fo)
+        .with_checkout(co)
+        .clone(&args.arg_url, Path::new(&args.arg_path))?;
+    println!();
+
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/diff.rs b/git2/examples/diff.rs
new file mode 100644 (file)
index 0000000..7440149
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * libgit2 "diff" example - shows how to use the diff API
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Blob, Diff, DiffOptions, Error, Object, ObjectType, Oid, Repository};
+use git2::{DiffDelta, DiffFindOptions, DiffFormat, DiffHunk, DiffLine};
+use std::str;
+
+#[derive(Parser)]
+#[allow(non_snake_case)]
+struct Args {
+    #[structopt(name = "from_oid")]
+    arg_from_oid: Option<String>,
+    #[structopt(name = "to_oid")]
+    arg_to_oid: Option<String>,
+    #[structopt(name = "blobs", long)]
+    /// treat from_oid and to_oid as blob ids
+    flag_blobs: bool,
+    #[structopt(name = "patch", short, long)]
+    /// show output in patch format
+    flag_patch: bool,
+    #[structopt(name = "cached", long)]
+    /// use staged changes as diff
+    flag_cached: bool,
+    #[structopt(name = "nocached", long)]
+    /// do not use staged changes
+    flag_nocached: bool,
+    #[structopt(name = "name-only", long)]
+    /// show only names of changed files
+    flag_name_only: bool,
+    #[structopt(name = "name-status", long)]
+    /// show only names and status changes
+    flag_name_status: bool,
+    #[structopt(name = "raw", long)]
+    /// generate the raw format
+    flag_raw: bool,
+    #[structopt(name = "format", long)]
+    /// specify format for stat summary
+    flag_format: Option<String>,
+    #[structopt(name = "color", long)]
+    /// use color output
+    flag_color: bool,
+    #[structopt(name = "no-color", long)]
+    /// never use color output
+    flag_no_color: bool,
+    #[structopt(short = 'R')]
+    /// swap two inputs
+    flag_R: bool,
+    #[structopt(name = "text", short = 'a', long)]
+    /// treat all files as text
+    flag_text: bool,
+    #[structopt(name = "ignore-space-at-eol", long)]
+    /// ignore changes in whitespace at EOL
+    flag_ignore_space_at_eol: bool,
+    #[structopt(name = "ignore-space-change", short = 'b', long)]
+    /// ignore changes in amount of whitespace
+    flag_ignore_space_change: bool,
+    #[structopt(name = "ignore-all-space", short = 'w', long)]
+    /// ignore whitespace when comparing lines
+    flag_ignore_all_space: bool,
+    #[structopt(name = "ignored", long)]
+    /// show untracked files
+    flag_ignored: bool,
+    #[structopt(name = "untracked", long)]
+    /// generate diff using the patience algorithm
+    flag_untracked: bool,
+    #[structopt(name = "patience", long)]
+    /// show ignored files as well
+    flag_patience: bool,
+    #[structopt(name = "minimal", long)]
+    /// spend extra time to find smallest diff
+    flag_minimal: bool,
+    #[structopt(name = "stat", long)]
+    /// generate a diffstat
+    flag_stat: bool,
+    #[structopt(name = "numstat", long)]
+    /// similar to --stat, but more machine friendly
+    flag_numstat: bool,
+    #[structopt(name = "shortstat", long)]
+    /// only output last line of --stat
+    flag_shortstat: bool,
+    #[structopt(name = "summary", long)]
+    /// output condensed summary of header info
+    flag_summary: bool,
+    #[structopt(name = "find-renames", short = 'M', long)]
+    /// set threshold for finding renames (default 50)
+    flag_find_renames: Option<u16>,
+    #[structopt(name = "find-copies", short = 'C', long)]
+    /// set threshold for finding copies (default 50)
+    flag_find_copies: Option<u16>,
+    #[structopt(name = "find-copies-harder", long)]
+    /// inspect unmodified files for sources of copies
+    flag_find_copies_harder: bool,
+    #[structopt(name = "break_rewrites", short = 'B', long)]
+    /// break complete rewrite changes into pairs
+    flag_break_rewrites: bool,
+    #[structopt(name = "unified", short = 'U', long)]
+    /// lints of context to show
+    flag_unified: Option<u32>,
+    #[structopt(name = "inter-hunk-context", long)]
+    /// maximum lines of change between hunks
+    flag_inter_hunk_context: Option<u32>,
+    #[structopt(name = "abbrev", long)]
+    /// length to abbreviate commits to
+    flag_abbrev: Option<u16>,
+    #[structopt(name = "src-prefix", long)]
+    /// show given source prefix instead of 'a/'
+    flag_src_prefix: Option<String>,
+    #[structopt(name = "dst-prefix", long)]
+    /// show given destination prefix instead of 'b/'
+    flag_dst_prefix: Option<String>,
+    #[structopt(name = "path", long = "git-dir")]
+    /// path to git repository to use
+    flag_git_dir: Option<String>,
+}
+
+const RESET: &str = "\u{1b}[m";
+const BOLD: &str = "\u{1b}[1m";
+const RED: &str = "\u{1b}[31m";
+const GREEN: &str = "\u{1b}[32m";
+const CYAN: &str = "\u{1b}[36m";
+
+#[derive(PartialEq, Eq, Copy, Clone)]
+enum Cache {
+    Normal,
+    Only,
+    None,
+}
+
+fn line_color(line: &DiffLine) -> Option<&'static str> {
+    match line.origin() {
+        '+' => Some(GREEN),
+        '-' => Some(RED),
+        '>' => Some(GREEN),
+        '<' => Some(RED),
+        'F' => Some(BOLD),
+        'H' => Some(CYAN),
+        _ => None,
+    }
+}
+
+fn print_diff_line(
+    _delta: DiffDelta,
+    _hunk: Option<DiffHunk>,
+    line: DiffLine,
+    args: &Args,
+) -> bool {
+    if args.color() {
+        print!("{}", RESET);
+        if let Some(color) = line_color(&line) {
+            print!("{}", color);
+        }
+    }
+    match line.origin() {
+        '+' | '-' | ' ' => print!("{}", line.origin()),
+        _ => {}
+    }
+    print!("{}", str::from_utf8(line.content()).unwrap());
+    true
+}
+
+fn run(args: &Args) -> Result<(), Error> {
+    let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
+    let repo = Repository::open(path)?;
+
+    // Prepare our diff options based on the arguments given
+    let mut opts = DiffOptions::new();
+    opts.reverse(args.flag_R)
+        .force_text(args.flag_text)
+        .ignore_whitespace_eol(args.flag_ignore_space_at_eol)
+        .ignore_whitespace_change(args.flag_ignore_space_change)
+        .ignore_whitespace(args.flag_ignore_all_space)
+        .include_ignored(args.flag_ignored)
+        .include_untracked(args.flag_untracked)
+        .patience(args.flag_patience)
+        .minimal(args.flag_minimal);
+    if let Some(amt) = args.flag_unified {
+        opts.context_lines(amt);
+    }
+    if let Some(amt) = args.flag_inter_hunk_context {
+        opts.interhunk_lines(amt);
+    }
+    if let Some(amt) = args.flag_abbrev {
+        opts.id_abbrev(amt);
+    }
+    if let Some(ref s) = args.flag_src_prefix {
+        opts.old_prefix(&s);
+    }
+    if let Some(ref s) = args.flag_dst_prefix {
+        opts.new_prefix(&s);
+    }
+    if let Some("diff-index") = args.flag_format.as_ref().map(|s| &s[..]) {
+        opts.id_abbrev(40);
+    }
+
+    if args.flag_blobs {
+        let b1 = resolve_blob(&repo, args.arg_from_oid.as_ref())?;
+        let b2 = resolve_blob(&repo, args.arg_to_oid.as_ref())?;
+        repo.diff_blobs(
+            b1.as_ref(),
+            None,
+            b2.as_ref(),
+            None,
+            Some(&mut opts),
+            None,
+            None,
+            None,
+            Some(&mut |d, h, l| print_diff_line(d, h, l, args)),
+        )?;
+        if args.color() {
+            print!("{}", RESET);
+        }
+        return Ok(());
+    }
+
+    // Prepare the diff to inspect
+    let t1 = tree_to_treeish(&repo, args.arg_from_oid.as_ref())?;
+    let t2 = tree_to_treeish(&repo, args.arg_to_oid.as_ref())?;
+    let head = tree_to_treeish(&repo, Some(&"HEAD".to_string()))?.unwrap();
+    let mut diff = match (t1, t2, args.cache()) {
+        (Some(t1), Some(t2), _) => {
+            repo.diff_tree_to_tree(t1.as_tree(), t2.as_tree(), Some(&mut opts))?
+        }
+        (t1, None, Cache::None) => {
+            let t1 = t1.unwrap_or(head);
+            repo.diff_tree_to_workdir(t1.as_tree(), Some(&mut opts))?
+        }
+        (t1, None, Cache::Only) => {
+            let t1 = t1.unwrap_or(head);
+            repo.diff_tree_to_index(t1.as_tree(), None, Some(&mut opts))?
+        }
+        (Some(t1), None, _) => {
+            repo.diff_tree_to_workdir_with_index(t1.as_tree(), Some(&mut opts))?
+        }
+        (None, None, _) => repo.diff_index_to_workdir(None, Some(&mut opts))?,
+        (None, Some(_), _) => unreachable!(),
+    };
+
+    // Apply rename and copy detection if requested
+    if args.flag_break_rewrites
+        || args.flag_find_copies_harder
+        || args.flag_find_renames.is_some()
+        || args.flag_find_copies.is_some()
+    {
+        let mut opts = DiffFindOptions::new();
+        if let Some(t) = args.flag_find_renames {
+            opts.rename_threshold(t);
+            opts.renames(true);
+        }
+        if let Some(t) = args.flag_find_copies {
+            opts.copy_threshold(t);
+            opts.copies(true);
+        }
+        opts.copies_from_unmodified(args.flag_find_copies_harder)
+            .rewrites(args.flag_break_rewrites);
+        diff.find_similar(Some(&mut opts))?;
+    }
+
+    // Generate simple output
+    let stats = args.flag_stat | args.flag_numstat | args.flag_shortstat | args.flag_summary;
+    if stats {
+        print_stats(&diff, args)?;
+    }
+    if args.flag_patch || !stats {
+        diff.print(args.diff_format(), |d, h, l| print_diff_line(d, h, l, args))?;
+        if args.color() {
+            print!("{}", RESET);
+        }
+    }
+
+    Ok(())
+}
+
+fn print_stats(diff: &Diff, args: &Args) -> Result<(), Error> {
+    let stats = diff.stats()?;
+    let mut format = git2::DiffStatsFormat::NONE;
+    if args.flag_stat {
+        format |= git2::DiffStatsFormat::FULL;
+    }
+    if args.flag_shortstat {
+        format |= git2::DiffStatsFormat::SHORT;
+    }
+    if args.flag_numstat {
+        format |= git2::DiffStatsFormat::NUMBER;
+    }
+    if args.flag_summary {
+        format |= git2::DiffStatsFormat::INCLUDE_SUMMARY;
+    }
+    let buf = stats.to_buf(format, 80)?;
+    print!("{}", str::from_utf8(&*buf).unwrap());
+    Ok(())
+}
+
+fn tree_to_treeish<'a>(
+    repo: &'a Repository,
+    arg: Option<&String>,
+) -> Result<Option<Object<'a>>, Error> {
+    let arg = match arg {
+        Some(s) => s,
+        None => return Ok(None),
+    };
+    let obj = repo.revparse_single(arg)?;
+    let tree = obj.peel(ObjectType::Tree)?;
+    Ok(Some(tree))
+}
+
+fn resolve_blob<'a>(repo: &'a Repository, arg: Option<&String>) -> Result<Option<Blob<'a>>, Error> {
+    let arg = match arg {
+        Some(s) => Oid::from_str(s)?,
+        None => return Ok(None),
+    };
+    repo.find_blob(arg).map(|b| Some(b))
+}
+
+impl Args {
+    fn cache(&self) -> Cache {
+        if self.flag_cached {
+            Cache::Only
+        } else if self.flag_nocached {
+            Cache::None
+        } else {
+            Cache::Normal
+        }
+    }
+    fn color(&self) -> bool {
+        self.flag_color && !self.flag_no_color
+    }
+    fn diff_format(&self) -> DiffFormat {
+        if self.flag_patch {
+            DiffFormat::Patch
+        } else if self.flag_name_only {
+            DiffFormat::NameOnly
+        } else if self.flag_name_status {
+            DiffFormat::NameStatus
+        } else if self.flag_raw {
+            DiffFormat::Raw
+        } else {
+            match self.flag_format.as_ref().map(|s| &s[..]) {
+                Some("name") => DiffFormat::NameOnly,
+                Some("name-status") => DiffFormat::NameStatus,
+                Some("raw") => DiffFormat::Raw,
+                Some("diff-index") => DiffFormat::Raw,
+                _ => DiffFormat::Patch,
+            }
+        }
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/fetch.rs b/git2/examples/fetch.rs
new file mode 100644 (file)
index 0000000..af4aa98
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * libgit2 "fetch" example - shows how to fetch remote data
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{AutotagOption, FetchOptions, RemoteCallbacks, RemoteUpdateFlags, Repository};
+use std::io::{self, Write};
+use std::str;
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "remote")]
+    arg_remote: Option<String>,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let repo = Repository::open(".")?;
+    let remote = args.arg_remote.as_ref().map(|s| &s[..]).unwrap_or("origin");
+
+    // Figure out whether it's a named remote or a URL
+    println!("Fetching {} for repo", remote);
+    let mut cb = RemoteCallbacks::new();
+    let mut remote = repo
+        .find_remote(remote)
+        .or_else(|_| repo.remote_anonymous(remote))?;
+    cb.sideband_progress(|data| {
+        print!("remote: {}", str::from_utf8(data).unwrap());
+        io::stdout().flush().unwrap();
+        true
+    });
+
+    // This callback gets called for each remote-tracking branch that gets
+    // updated. The message we output depends on whether it's a new one or an
+    // update.
+    cb.update_tips(|refname, a, b| {
+        if a.is_zero() {
+            println!("[new]     {:20} {}", b, refname);
+        } else {
+            println!("[updated] {:10}..{:10} {}", a, b, refname);
+        }
+        true
+    });
+
+    // Here we show processed and total objects in the pack and the amount of
+    // received data. Most frontends will probably want to show a percentage and
+    // the download rate.
+    cb.transfer_progress(|stats| {
+        if stats.received_objects() == stats.total_objects() {
+            print!(
+                "Resolving deltas {}/{}\r",
+                stats.indexed_deltas(),
+                stats.total_deltas()
+            );
+        } else if stats.total_objects() > 0 {
+            print!(
+                "Received {}/{} objects ({}) in {} bytes\r",
+                stats.received_objects(),
+                stats.total_objects(),
+                stats.indexed_objects(),
+                stats.received_bytes()
+            );
+        }
+        io::stdout().flush().unwrap();
+        true
+    });
+
+    // Download the packfile and index it. This function updates the amount of
+    // received data and the indexer stats which lets you inform the user about
+    // progress.
+    let mut fo = FetchOptions::new();
+    fo.remote_callbacks(cb);
+    remote.download(&[] as &[&str], Some(&mut fo))?;
+
+    {
+        // If there are local objects (we got a thin pack), then tell the user
+        // how many objects we saved from having to cross the network.
+        let stats = remote.stats();
+        if stats.local_objects() > 0 {
+            println!(
+                "\rReceived {}/{} objects in {} bytes (used {} local \
+                 objects)",
+                stats.indexed_objects(),
+                stats.total_objects(),
+                stats.received_bytes(),
+                stats.local_objects()
+            );
+        } else {
+            println!(
+                "\rReceived {}/{} objects in {} bytes",
+                stats.indexed_objects(),
+                stats.total_objects(),
+                stats.received_bytes()
+            );
+        }
+    }
+
+    // Disconnect the underlying connection to prevent from idling.
+    remote.disconnect()?;
+
+    // Update the references in the remote's namespace to point to the right
+    // commits. This may be needed even if there was no packfile to download,
+    // which can happen e.g. when the branches have been changed but all the
+    // needed objects are available locally.
+    remote.update_tips(
+        None,
+        RemoteUpdateFlags::UPDATE_FETCHHEAD,
+        AutotagOption::Unspecified,
+        None,
+    )?;
+
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/init.rs b/git2/examples/init.rs
new file mode 100644 (file)
index 0000000..3ae7908
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * libgit2 "init" example - shows how to initialize a new repo (also includes how to do an initial commit)
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Error, Repository, RepositoryInitMode, RepositoryInitOptions};
+use std::path::{Path, PathBuf};
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "directory")]
+    arg_directory: String,
+    #[structopt(name = "quiet", short, long)]
+    /// don't print information to stdout
+    flag_quiet: bool,
+    #[structopt(name = "bare", long)]
+    /// initialize a new bare repository
+    flag_bare: bool,
+    #[structopt(name = "dir", long = "template")]
+    /// use <dir> as an initialization template
+    flag_template: Option<String>,
+    #[structopt(name = "separate-git-dir", long)]
+    /// use <dir> as the .git directory
+    flag_separate_git_dir: Option<String>,
+    #[structopt(name = "initial-commit", long)]
+    /// create an initial empty commit
+    flag_initial_commit: bool,
+    #[structopt(name = "perms", long = "shared")]
+    /// permissions to create the repository with
+    flag_shared: Option<String>,
+}
+
+fn run(args: &Args) -> Result<(), Error> {
+    let mut path = PathBuf::from(&args.arg_directory);
+    let repo = if !args.flag_bare
+        && args.flag_template.is_none()
+        && args.flag_shared.is_none()
+        && args.flag_separate_git_dir.is_none()
+    {
+        Repository::init(&path)?
+    } else {
+        let mut opts = RepositoryInitOptions::new();
+        opts.bare(args.flag_bare);
+        if let Some(ref s) = args.flag_template {
+            opts.template_path(Path::new(s));
+        }
+
+        // If you specified a separate git directory, then initialize
+        // the repository at that path and use the second path as the
+        // working directory of the repository (with a git-link file)
+        if let Some(ref s) = args.flag_separate_git_dir {
+            opts.workdir_path(&path);
+            path = PathBuf::from(s);
+        }
+
+        if let Some(ref s) = args.flag_shared {
+            opts.mode(parse_shared(s)?);
+        }
+        Repository::init_opts(&path, &opts)?
+    };
+
+    // Print a message to stdout like "git init" does
+    if !args.flag_quiet {
+        if args.flag_bare || args.flag_separate_git_dir.is_some() {
+            path = repo.path().to_path_buf();
+        } else {
+            path = repo.workdir().unwrap().to_path_buf();
+        }
+        println!("Initialized empty Git repository in {}", path.display());
+    }
+
+    if args.flag_initial_commit {
+        create_initial_commit(&repo)?;
+        println!("Created empty initial commit");
+    }
+
+    Ok(())
+}
+
+/// Unlike regular "git init", this example shows how to create an initial empty
+/// commit in the repository. This is the helper function that does that.
+fn create_initial_commit(repo: &Repository) -> Result<(), Error> {
+    // First use the config to initialize a commit signature for the user.
+    let sig = repo.signature()?;
+
+    // Now let's create an empty tree for this commit
+    let tree_id = {
+        let mut index = repo.index()?;
+
+        // Outside of this example, you could call index.add_path()
+        // here to put actual files into the index. For our purposes, we'll
+        // leave it empty for now.
+
+        index.write_tree()?
+    };
+
+    let tree = repo.find_tree(tree_id)?;
+
+    // Ready to create the initial commit.
+    //
+    // Normally creating a commit would involve looking up the current HEAD
+    // commit and making that be the parent of the initial commit, but here this
+    // is the first commit so there will be no parent.
+    repo.commit(Some("HEAD"), &sig, &sig, "Initial commit", &tree, &[])?;
+
+    Ok(())
+}
+
+fn parse_shared(shared: &str) -> Result<RepositoryInitMode, Error> {
+    match shared {
+        "false" | "umask" => Ok(git2::RepositoryInitMode::SHARED_UMASK),
+        "true" | "group" => Ok(git2::RepositoryInitMode::SHARED_GROUP),
+        "all" | "world" => Ok(git2::RepositoryInitMode::SHARED_ALL),
+        _ => {
+            if shared.starts_with('0') {
+                match u32::from_str_radix(&shared[1..], 8).ok() {
+                    Some(n) => Ok(RepositoryInitMode::from_bits_truncate(n)),
+                    None => Err(Error::from_str("invalid octal value for --shared")),
+                }
+            } else {
+                Err(Error::from_str("unknown value for --shared"))
+            }
+        }
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/log.rs b/git2/examples/log.rs
new file mode 100644 (file)
index 0000000..5f6fe0f
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * libgit2 "log" example - shows how to walk history and get commit info
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Commit, DiffOptions, ObjectType, Repository, Signature, Time};
+use git2::{DiffFormat, Error, Pathspec};
+use std::str;
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "topo-order", long)]
+    /// sort commits in topological order
+    flag_topo_order: bool,
+    #[structopt(name = "date-order", long)]
+    /// sort commits in date order
+    flag_date_order: bool,
+    #[structopt(name = "reverse", long)]
+    /// sort commits in reverse
+    flag_reverse: bool,
+    #[structopt(name = "author", long)]
+    /// author to sort by
+    flag_author: Option<String>,
+    #[structopt(name = "committer", long)]
+    /// committer to sort by
+    flag_committer: Option<String>,
+    #[structopt(name = "pat", long = "grep")]
+    /// pattern to filter commit messages by
+    flag_grep: Option<String>,
+    #[structopt(name = "dir", long = "git-dir")]
+    /// alternative git directory to use
+    flag_git_dir: Option<String>,
+    #[structopt(name = "skip", long)]
+    /// number of commits to skip
+    flag_skip: Option<usize>,
+    #[structopt(name = "max-count", short = 'n', long)]
+    /// maximum number of commits to show
+    flag_max_count: Option<usize>,
+    #[structopt(name = "merges", long)]
+    /// only show merge commits
+    flag_merges: bool,
+    #[structopt(name = "no-merges", long)]
+    /// don't show merge commits
+    flag_no_merges: bool,
+    #[structopt(name = "no-min-parents", long)]
+    /// don't require a minimum number of parents
+    flag_no_min_parents: bool,
+    #[structopt(name = "no-max-parents", long)]
+    /// don't require a maximum number of parents
+    flag_no_max_parents: bool,
+    #[structopt(name = "max-parents")]
+    /// specify a maximum number of parents for a commit
+    flag_max_parents: Option<usize>,
+    #[structopt(name = "min-parents")]
+    /// specify a minimum number of parents for a commit
+    flag_min_parents: Option<usize>,
+    #[structopt(name = "patch", long, short)]
+    /// show commit diff
+    flag_patch: bool,
+    #[structopt(name = "commit")]
+    arg_commit: Vec<String>,
+    #[structopt(name = "spec", last = true)]
+    arg_spec: Vec<String>,
+}
+
+fn run(args: &Args) -> Result<(), Error> {
+    let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
+    let repo = Repository::open(path)?;
+    let mut revwalk = repo.revwalk()?;
+
+    // Prepare the revwalk based on CLI parameters
+    let base = if args.flag_reverse {
+        git2::Sort::REVERSE
+    } else {
+        git2::Sort::NONE
+    };
+    revwalk.set_sorting(
+        base | if args.flag_topo_order {
+            git2::Sort::TOPOLOGICAL
+        } else if args.flag_date_order {
+            git2::Sort::TIME
+        } else {
+            git2::Sort::NONE
+        },
+    )?;
+    for commit in &args.arg_commit {
+        if commit.starts_with('^') {
+            let obj = repo.revparse_single(&commit[1..])?;
+            revwalk.hide(obj.id())?;
+            continue;
+        }
+        let revspec = repo.revparse(commit)?;
+        if revspec.mode().contains(git2::RevparseMode::SINGLE) {
+            revwalk.push(revspec.from().unwrap().id())?;
+        } else {
+            let from = revspec.from().unwrap().id();
+            let to = revspec.to().unwrap().id();
+            revwalk.push(to)?;
+            if revspec.mode().contains(git2::RevparseMode::MERGE_BASE) {
+                let base = repo.merge_base(from, to)?;
+                let o = repo.find_object(base, Some(ObjectType::Commit))?;
+                revwalk.push(o.id())?;
+            }
+            revwalk.hide(from)?;
+        }
+    }
+    if args.arg_commit.is_empty() {
+        revwalk.push_head()?;
+    }
+
+    // Prepare our diff options and pathspec matcher
+    let (mut diffopts, mut diffopts2) = (DiffOptions::new(), DiffOptions::new());
+    for spec in &args.arg_spec {
+        diffopts.pathspec(spec);
+        diffopts2.pathspec(spec);
+    }
+    let ps = Pathspec::new(args.arg_spec.iter())?;
+
+    // Filter our revwalk based on the CLI parameters
+    macro_rules! filter_try {
+        ($e:expr) => {
+            match $e {
+                Ok(t) => t,
+                Err(e) => return Some(Err(e)),
+            }
+        };
+    }
+    let revwalk = revwalk
+        .filter_map(|id| {
+            let id = filter_try!(id);
+            let commit = filter_try!(repo.find_commit(id));
+            let parents = commit.parents().len();
+            if parents < args.min_parents() {
+                return None;
+            }
+            if let Some(n) = args.max_parents() {
+                if parents >= n {
+                    return None;
+                }
+            }
+            if !args.arg_spec.is_empty() {
+                match commit.parents().len() {
+                    0 => {
+                        let tree = filter_try!(commit.tree());
+                        let flags = git2::PathspecFlags::NO_MATCH_ERROR;
+                        if ps.match_tree(&tree, flags).is_err() {
+                            return None;
+                        }
+                    }
+                    _ => {
+                        let m = commit.parents().all(|parent| {
+                            match_with_parent(&repo, &commit, &parent, &mut diffopts)
+                                .unwrap_or(false)
+                        });
+                        if !m {
+                            return None;
+                        }
+                    }
+                }
+            }
+            if !sig_matches(&commit.author(), &args.flag_author) {
+                return None;
+            }
+            if !sig_matches(&commit.committer(), &args.flag_committer) {
+                return None;
+            }
+            if !log_message_matches(commit.message(), &args.flag_grep) {
+                return None;
+            }
+            Some(Ok(commit))
+        })
+        .skip(args.flag_skip.unwrap_or(0))
+        .take(args.flag_max_count.unwrap_or(!0));
+
+    // print!
+    for commit in revwalk {
+        let commit = commit?;
+        print_commit(&commit);
+        if !args.flag_patch || commit.parents().len() > 1 {
+            continue;
+        }
+        let a = if commit.parents().len() == 1 {
+            let parent = commit.parent(0)?;
+            Some(parent.tree()?)
+        } else {
+            None
+        };
+        let b = commit.tree()?;
+        let diff = repo.diff_tree_to_tree(a.as_ref(), Some(&b), Some(&mut diffopts2))?;
+        diff.print(DiffFormat::Patch, |_delta, _hunk, line| {
+            match line.origin() {
+                ' ' | '+' | '-' => print!("{}", line.origin()),
+                _ => {}
+            }
+            print!("{}", str::from_utf8(line.content()).unwrap());
+            true
+        })?;
+    }
+
+    Ok(())
+}
+
+fn sig_matches(sig: &Signature, arg: &Option<String>) -> bool {
+    match *arg {
+        Some(ref s) => {
+            sig.name().map(|n| n.contains(s)).unwrap_or(false)
+                || sig.email().map(|n| n.contains(s)).unwrap_or(false)
+        }
+        None => true,
+    }
+}
+
+fn log_message_matches(msg: Option<&str>, grep: &Option<String>) -> bool {
+    match (grep, msg) {
+        (&None, _) => true,
+        (&Some(_), None) => false,
+        (&Some(ref s), Some(msg)) => msg.contains(s),
+    }
+}
+
+fn print_commit(commit: &Commit) {
+    println!("commit {}", commit.id());
+
+    if commit.parents().len() > 1 {
+        print!("Merge:");
+        for id in commit.parent_ids() {
+            print!(" {:.8}", id);
+        }
+        println!();
+    }
+
+    let author = commit.author();
+    println!("Author: {}", author);
+    print_time(&author.when(), "Date:   ");
+    println!();
+
+    for line in String::from_utf8_lossy(commit.message_bytes()).lines() {
+        println!("    {}", line);
+    }
+    println!();
+}
+
+fn print_time(time: &Time, prefix: &str) {
+    let offset = time.offset_minutes();
+    let (hours, minutes) = (offset / 60, offset % 60);
+    let dt = time::OffsetDateTime::from_unix_timestamp(time.seconds()).unwrap();
+    let dto = dt.to_offset(time::UtcOffset::from_hms(hours as i8, minutes as i8, 0).unwrap());
+    let format = time::format_description::parse("[weekday repr:short] [month repr:short] [day padding:space] [hour]:[minute]:[second] [year] [offset_hour sign:mandatory][offset_minute]")
+        .unwrap();
+    let time_str = dto.format(&format).unwrap();
+
+    println!("{}{}", prefix, time_str);
+}
+
+fn match_with_parent(
+    repo: &Repository,
+    commit: &Commit,
+    parent: &Commit,
+    opts: &mut DiffOptions,
+) -> Result<bool, Error> {
+    let a = parent.tree()?;
+    let b = commit.tree()?;
+    let diff = repo.diff_tree_to_tree(Some(&a), Some(&b), Some(opts))?;
+    Ok(diff.deltas().len() > 0)
+}
+
+impl Args {
+    fn min_parents(&self) -> usize {
+        if self.flag_no_min_parents {
+            return 0;
+        }
+        self.flag_min_parents
+            .unwrap_or(if self.flag_merges { 2 } else { 0 })
+    }
+
+    fn max_parents(&self) -> Option<usize> {
+        if self.flag_no_max_parents {
+            return None;
+        }
+        self.flag_max_parents
+            .or(if self.flag_no_merges { Some(1) } else { None })
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/ls-remote.rs b/git2/examples/ls-remote.rs
new file mode 100644 (file)
index 0000000..f88baaf
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * libgit2 "ls-remote" example
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Direction, Repository};
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "remote")]
+    arg_remote: String,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let repo = Repository::open(".")?;
+    let remote = &args.arg_remote;
+    let mut remote = repo
+        .find_remote(remote)
+        .or_else(|_| repo.remote_anonymous(remote))?;
+
+    // Connect to the remote and call the printing function for each of the
+    // remote references.
+    let connection = remote.connect_auth(Direction::Fetch, None, None)?;
+
+    // Get the list of references on the remote and print out their name next to
+    // what they point to.
+    for head in connection.list()?.iter() {
+        println!("{}\t{}", head.oid(), head.name());
+    }
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/pull.rs b/git2/examples/pull.rs
new file mode 100644 (file)
index 0000000..27f461e
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * libgit2 "pull" example - shows how to pull remote data into a local branch.
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+use clap::Parser;
+use git2::Repository;
+use std::io::{self, Write};
+use std::str;
+
+#[derive(Parser)]
+struct Args {
+    arg_remote: Option<String>,
+    arg_branch: Option<String>,
+}
+
+fn do_fetch<'a>(
+    repo: &'a git2::Repository,
+    refs: &[&str],
+    remote: &'a mut git2::Remote,
+) -> Result<git2::AnnotatedCommit<'a>, git2::Error> {
+    let mut cb = git2::RemoteCallbacks::new();
+
+    // Print out our transfer progress.
+    cb.transfer_progress(|stats| {
+        if stats.received_objects() == stats.total_objects() {
+            print!(
+                "Resolving deltas {}/{}\r",
+                stats.indexed_deltas(),
+                stats.total_deltas()
+            );
+        } else if stats.total_objects() > 0 {
+            print!(
+                "Received {}/{} objects ({}) in {} bytes\r",
+                stats.received_objects(),
+                stats.total_objects(),
+                stats.indexed_objects(),
+                stats.received_bytes()
+            );
+        }
+        io::stdout().flush().unwrap();
+        true
+    });
+
+    let mut fo = git2::FetchOptions::new();
+    fo.remote_callbacks(cb);
+    // Always fetch all tags.
+    // Perform a download and also update tips
+    fo.download_tags(git2::AutotagOption::All);
+    println!("Fetching {} for repo", remote.name().unwrap());
+    remote.fetch(refs, Some(&mut fo), None)?;
+
+    // If there are local objects (we got a thin pack), then tell the user
+    // how many objects we saved from having to cross the network.
+    let stats = remote.stats();
+    if stats.local_objects() > 0 {
+        println!(
+            "\rReceived {}/{} objects in {} bytes (used {} local \
+             objects)",
+            stats.indexed_objects(),
+            stats.total_objects(),
+            stats.received_bytes(),
+            stats.local_objects()
+        );
+    } else {
+        println!(
+            "\rReceived {}/{} objects in {} bytes",
+            stats.indexed_objects(),
+            stats.total_objects(),
+            stats.received_bytes()
+        );
+    }
+
+    let fetch_head = repo.find_reference("FETCH_HEAD")?;
+    Ok(repo.reference_to_annotated_commit(&fetch_head)?)
+}
+
+fn fast_forward(
+    repo: &Repository,
+    lb: &mut git2::Reference,
+    rc: &git2::AnnotatedCommit,
+) -> Result<(), git2::Error> {
+    let name = match lb.name() {
+        Some(s) => s.to_string(),
+        None => String::from_utf8_lossy(lb.name_bytes()).to_string(),
+    };
+    let msg = format!("Fast-Forward: Setting {} to id: {}", name, rc.id());
+    println!("{}", msg);
+    lb.set_target(rc.id(), &msg)?;
+    repo.set_head(&name)?;
+    repo.checkout_head(Some(
+        git2::build::CheckoutBuilder::default()
+            // For some reason the force is required to make the working directory actually get updated
+            // I suspect we should be adding some logic to handle dirty working directory states
+            // but this is just an example so maybe not.
+            .force(),
+    ))?;
+    Ok(())
+}
+
+fn normal_merge(
+    repo: &Repository,
+    local: &git2::AnnotatedCommit,
+    remote: &git2::AnnotatedCommit,
+) -> Result<(), git2::Error> {
+    let local_tree = repo.find_commit(local.id())?.tree()?;
+    let remote_tree = repo.find_commit(remote.id())?.tree()?;
+    let ancestor = repo
+        .find_commit(repo.merge_base(local.id(), remote.id())?)?
+        .tree()?;
+    let mut idx = repo.merge_trees(&ancestor, &local_tree, &remote_tree, None)?;
+
+    if idx.has_conflicts() {
+        println!("Merge conflicts detected...");
+        repo.checkout_index(Some(&mut idx), None)?;
+        return Ok(());
+    }
+    let result_tree = repo.find_tree(idx.write_tree_to(repo)?)?;
+    // now create the merge commit
+    let msg = format!("Merge: {} into {}", remote.id(), local.id());
+    let sig = repo.signature()?;
+    let local_commit = repo.find_commit(local.id())?;
+    let remote_commit = repo.find_commit(remote.id())?;
+    // Do our merge commit and set current branch head to that commit.
+    let _merge_commit = repo.commit(
+        Some("HEAD"),
+        &sig,
+        &sig,
+        &msg,
+        &result_tree,
+        &[&local_commit, &remote_commit],
+    )?;
+    // Set working tree to match head.
+    repo.checkout_head(None)?;
+    Ok(())
+}
+
+fn do_merge<'a>(
+    repo: &'a Repository,
+    remote_branch: &str,
+    fetch_commit: git2::AnnotatedCommit<'a>,
+) -> Result<(), git2::Error> {
+    // 1. do a merge analysis
+    let analysis = repo.merge_analysis(&[&fetch_commit])?;
+
+    // 2. Do the appropriate merge
+    if analysis.0.is_fast_forward() {
+        println!("Doing a fast forward");
+        // do a fast forward
+        let refname = format!("refs/heads/{}", remote_branch);
+        match repo.find_reference(&refname) {
+            Ok(mut r) => {
+                fast_forward(repo, &mut r, &fetch_commit)?;
+            }
+            Err(_) => {
+                // The branch doesn't exist so just set the reference to the
+                // commit directly. Usually this is because you are pulling
+                // into an empty repository.
+                repo.reference(
+                    &refname,
+                    fetch_commit.id(),
+                    true,
+                    &format!("Setting {} to {}", remote_branch, fetch_commit.id()),
+                )?;
+                repo.set_head(&refname)?;
+                repo.checkout_head(Some(
+                    git2::build::CheckoutBuilder::default()
+                        .allow_conflicts(true)
+                        .conflict_style_merge(true)
+                        .force(),
+                ))?;
+            }
+        };
+    } else if analysis.0.is_normal() {
+        // do a normal merge
+        let head_commit = repo.reference_to_annotated_commit(&repo.head()?)?;
+        normal_merge(&repo, &head_commit, &fetch_commit)?;
+    } else {
+        println!("Nothing to do...");
+    }
+    Ok(())
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let remote_name = args.arg_remote.as_ref().map(|s| &s[..]).unwrap_or("origin");
+    let remote_branch = args.arg_branch.as_ref().map(|s| &s[..]).unwrap_or("master");
+    let repo = Repository::open(".")?;
+    let mut remote = repo.find_remote(remote_name)?;
+    let fetch_commit = do_fetch(&repo, &[remote_branch], &mut remote)?;
+    do_merge(&repo, &remote_branch, fetch_commit)
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/rev-list.rs b/git2/examples/rev-list.rs
new file mode 100644 (file)
index 0000000..2eaed78
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * libgit2 "rev-list" example - shows how to transform a rev-spec into a list
+ * of commit ids
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Error, Oid, Repository, Revwalk};
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "topo-order", long)]
+    /// sort commits in topological order
+    flag_topo_order: bool,
+    #[structopt(name = "date-order", long)]
+    /// sort commits in date order
+    flag_date_order: bool,
+    #[structopt(name = "reverse", long)]
+    /// sort commits in reverse
+    flag_reverse: bool,
+    #[structopt(name = "not")]
+    /// don't show <spec>
+    flag_not: Vec<String>,
+    #[structopt(name = "spec", last = true)]
+    arg_spec: Vec<String>,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let repo = Repository::open(".")?;
+    let mut revwalk = repo.revwalk()?;
+
+    let base = if args.flag_reverse {
+        git2::Sort::REVERSE
+    } else {
+        git2::Sort::NONE
+    };
+    revwalk.set_sorting(
+        base | if args.flag_topo_order {
+            git2::Sort::TOPOLOGICAL
+        } else if args.flag_date_order {
+            git2::Sort::TIME
+        } else {
+            git2::Sort::NONE
+        },
+    )?;
+
+    let specs = args
+        .flag_not
+        .iter()
+        .map(|s| (s, true))
+        .chain(args.arg_spec.iter().map(|s| (s, false)))
+        .map(|(spec, hide)| {
+            if spec.starts_with('^') {
+                (&spec[1..], !hide)
+            } else {
+                (&spec[..], hide)
+            }
+        });
+    for (spec, hide) in specs {
+        let id = if spec.contains("..") {
+            let revspec = repo.revparse(spec)?;
+            if revspec.mode().contains(git2::RevparseMode::MERGE_BASE) {
+                return Err(Error::from_str("merge bases not implemented"));
+            }
+            push(&mut revwalk, revspec.from().unwrap().id(), !hide)?;
+            revspec.to().unwrap().id()
+        } else {
+            repo.revparse_single(spec)?.id()
+        };
+        push(&mut revwalk, id, hide)?;
+    }
+
+    for id in revwalk {
+        let id = id?;
+        println!("{}", id);
+    }
+    Ok(())
+}
+
+fn push(revwalk: &mut Revwalk, id: Oid, hide: bool) -> Result<(), Error> {
+    if hide {
+        revwalk.hide(id)
+    } else {
+        revwalk.push(id)
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/rev-parse.rs b/git2/examples/rev-parse.rs
new file mode 100644 (file)
index 0000000..8d3934e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * libgit2 "rev-parse" example - shows how to parse revspecs
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::Repository;
+
+#[derive(Parser)]
+struct Args {
+    #[structopt(name = "spec")]
+    arg_spec: String,
+    #[structopt(name = "dir", long = "git-dir")]
+    /// directory of the git repository to check
+    flag_git_dir: Option<String>,
+}
+
+fn run(args: &Args) -> Result<(), git2::Error> {
+    let path = args.flag_git_dir.as_ref().map(|s| &s[..]).unwrap_or(".");
+    let repo = Repository::open(path)?;
+
+    let revspec = repo.revparse(&args.arg_spec)?;
+
+    if revspec.mode().contains(git2::RevparseMode::SINGLE) {
+        println!("{}", revspec.from().unwrap().id());
+    } else if revspec.mode().contains(git2::RevparseMode::RANGE) {
+        let to = revspec.to().unwrap();
+        let from = revspec.from().unwrap();
+        println!("{}", to.id());
+
+        if revspec.mode().contains(git2::RevparseMode::MERGE_BASE) {
+            let base = repo.merge_base(from.id(), to.id())?;
+            println!("{}", base);
+        }
+
+        println!("^{}", from.id());
+    } else {
+        return Err(git2::Error::from_str("invalid results from revparse"));
+    }
+    Ok(())
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/status.rs b/git2/examples/status.rs
new file mode 100644 (file)
index 0000000..0ed6171
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ * libgit2 "status" example - shows how to use the status APIs
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Error, ErrorCode, Repository, StatusOptions, SubmoduleIgnore};
+use std::str;
+use std::time::Duration;
+
+#[derive(Parser)]
+struct Args {
+    arg_spec: Vec<String>,
+    #[structopt(name = "long", long)]
+    /// show longer statuses (default)
+    _flag_long: bool,
+    /// show short statuses
+    #[structopt(name = "short", long)]
+    flag_short: bool,
+    #[structopt(name = "porcelain", long)]
+    /// ??
+    flag_porcelain: bool,
+    #[structopt(name = "branch", short, long)]
+    /// show branch information
+    flag_branch: bool,
+    #[structopt(name = "z", short)]
+    /// ??
+    flag_z: bool,
+    #[structopt(name = "ignored", long)]
+    /// show ignored files as well
+    flag_ignored: bool,
+    #[structopt(name = "opt-modules", long = "untracked-files")]
+    /// setting for showing untracked files [no|normal|all]
+    flag_untracked_files: Option<String>,
+    #[structopt(name = "opt-files", long = "ignore-submodules")]
+    /// setting for ignoring submodules [all]
+    flag_ignore_submodules: Option<String>,
+    #[structopt(name = "dir", long = "git-dir")]
+    /// git directory to analyze
+    flag_git_dir: Option<String>,
+    #[structopt(name = "repeat", long)]
+    /// repeatedly show status, sleeping inbetween
+    flag_repeat: bool,
+    #[structopt(name = "list-submodules", long)]
+    /// show submodules
+    flag_list_submodules: bool,
+}
+
+#[derive(Eq, PartialEq)]
+enum Format {
+    Long,
+    Short,
+    Porcelain,
+}
+
+fn run(args: &Args) -> Result<(), Error> {
+    let path = args.flag_git_dir.clone().unwrap_or_else(|| ".".to_string());
+    let repo = Repository::open(&path)?;
+    if repo.is_bare() {
+        return Err(Error::from_str("cannot report status on bare repository"));
+    }
+
+    let mut opts = StatusOptions::new();
+    opts.include_ignored(args.flag_ignored);
+    match args.flag_untracked_files.as_ref().map(|s| &s[..]) {
+        Some("no") => {
+            opts.include_untracked(false);
+        }
+        Some("normal") => {
+            opts.include_untracked(true);
+        }
+        Some("all") => {
+            opts.include_untracked(true).recurse_untracked_dirs(true);
+        }
+        Some(_) => return Err(Error::from_str("invalid untracked-files value")),
+        None => {}
+    }
+    match args.flag_ignore_submodules.as_ref().map(|s| &s[..]) {
+        Some("all") => {
+            opts.exclude_submodules(true);
+        }
+        Some(_) => return Err(Error::from_str("invalid ignore-submodules value")),
+        None => {}
+    }
+    opts.include_untracked(!args.flag_ignored);
+    for spec in &args.arg_spec {
+        opts.pathspec(spec);
+    }
+
+    loop {
+        if args.flag_repeat {
+            println!("\u{1b}[H\u{1b}[2J");
+        }
+
+        let statuses = repo.statuses(Some(&mut opts))?;
+
+        if args.flag_branch {
+            show_branch(&repo, &args.format())?;
+        }
+        if args.flag_list_submodules {
+            print_submodules(&repo)?;
+        }
+
+        if args.format() == Format::Long {
+            print_long(&statuses);
+        } else {
+            print_short(&repo, &statuses);
+        }
+
+        if args.flag_repeat {
+            std::thread::sleep(Duration::new(10, 0));
+        } else {
+            return Ok(());
+        }
+    }
+}
+
+fn show_branch(repo: &Repository, format: &Format) -> Result<(), Error> {
+    let head = match repo.head() {
+        Ok(head) => Some(head),
+        Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => {
+            None
+        }
+        Err(e) => return Err(e),
+    };
+    let head = head.as_ref().and_then(|h| h.shorthand());
+
+    if format == &Format::Long {
+        println!(
+            "# On branch {}",
+            head.unwrap_or("Not currently on any branch")
+        );
+    } else {
+        println!("## {}", head.unwrap_or("HEAD (no branch)"));
+    }
+    Ok(())
+}
+
+fn print_submodules(repo: &Repository) -> Result<(), Error> {
+    let modules = repo.submodules()?;
+    println!("# Submodules");
+    for sm in &modules {
+        println!(
+            "# - submodule '{}' at {}",
+            sm.name().unwrap(),
+            sm.path().display()
+        );
+    }
+    Ok(())
+}
+
+// This function print out an output similar to git's status command in long
+// form, including the command-line hints.
+fn print_long(statuses: &git2::Statuses) {
+    let mut header = false;
+    let mut rm_in_workdir = false;
+    let mut changes_in_index = false;
+    let mut changed_in_workdir = false;
+
+    // Print index changes
+    for entry in statuses
+        .iter()
+        .filter(|e| e.status() != git2::Status::CURRENT)
+    {
+        if entry.status().contains(git2::Status::WT_DELETED) {
+            rm_in_workdir = true;
+        }
+        let istatus = match entry.status() {
+            s if s.contains(git2::Status::INDEX_NEW) => "new file: ",
+            s if s.contains(git2::Status::INDEX_MODIFIED) => "modified: ",
+            s if s.contains(git2::Status::INDEX_DELETED) => "deleted: ",
+            s if s.contains(git2::Status::INDEX_RENAMED) => "renamed: ",
+            s if s.contains(git2::Status::INDEX_TYPECHANGE) => "typechange:",
+            _ => continue,
+        };
+        if !header {
+            println!(
+                "\
+# Changes to be committed:
+#   (use \"git reset HEAD <file>...\" to unstage)
+#"
+            );
+            header = true;
+        }
+
+        let old_path = entry.head_to_index().unwrap().old_file().path();
+        let new_path = entry.head_to_index().unwrap().new_file().path();
+        match (old_path, new_path) {
+            (Some(old), Some(new)) if old != new => {
+                println!("#\t{}  {} -> {}", istatus, old.display(), new.display());
+            }
+            (old, new) => {
+                println!("#\t{}  {}", istatus, old.or(new).unwrap().display());
+            }
+        }
+    }
+
+    if header {
+        changes_in_index = true;
+        println!("#");
+    }
+    header = false;
+
+    // Print workdir changes to tracked files
+    for entry in statuses.iter() {
+        // With `Status::OPT_INCLUDE_UNMODIFIED` (not used in this example)
+        // `index_to_workdir` may not be `None` even if there are no differences,
+        // in which case it will be a `Delta::Unmodified`.
+        if entry.status() == git2::Status::CURRENT || entry.index_to_workdir().is_none() {
+            continue;
+        }
+
+        let istatus = match entry.status() {
+            s if s.contains(git2::Status::WT_MODIFIED) => "modified: ",
+            s if s.contains(git2::Status::WT_DELETED) => "deleted: ",
+            s if s.contains(git2::Status::WT_RENAMED) => "renamed: ",
+            s if s.contains(git2::Status::WT_TYPECHANGE) => "typechange:",
+            _ => continue,
+        };
+
+        if !header {
+            println!(
+                "\
+# Changes not staged for commit:
+#   (use \"git add{} <file>...\" to update what will be committed)
+#   (use \"git checkout -- <file>...\" to discard changes in working directory)
+#\
+                ",
+                if rm_in_workdir { "/rm" } else { "" }
+            );
+            header = true;
+        }
+
+        let old_path = entry.index_to_workdir().unwrap().old_file().path();
+        let new_path = entry.index_to_workdir().unwrap().new_file().path();
+        match (old_path, new_path) {
+            (Some(old), Some(new)) if old != new => {
+                println!("#\t{}  {} -> {}", istatus, old.display(), new.display());
+            }
+            (old, new) => {
+                println!("#\t{}  {}", istatus, old.or(new).unwrap().display());
+            }
+        }
+    }
+
+    if header {
+        changed_in_workdir = true;
+        println!("#");
+    }
+    header = false;
+
+    // Print untracked files
+    for entry in statuses
+        .iter()
+        .filter(|e| e.status() == git2::Status::WT_NEW)
+    {
+        if !header {
+            println!(
+                "\
+# Untracked files
+#   (use \"git add <file>...\" to include in what will be committed)
+#"
+            );
+            header = true;
+        }
+        let file = entry.index_to_workdir().unwrap().old_file().path().unwrap();
+        println!("#\t{}", file.display());
+    }
+    header = false;
+
+    // Print ignored files
+    for entry in statuses
+        .iter()
+        .filter(|e| e.status() == git2::Status::IGNORED)
+    {
+        if !header {
+            println!(
+                "\
+# Ignored files
+#   (use \"git add -f <file>...\" to include in what will be committed)
+#"
+            );
+            header = true;
+        }
+        let file = entry.index_to_workdir().unwrap().old_file().path().unwrap();
+        println!("#\t{}", file.display());
+    }
+
+    if !changes_in_index && changed_in_workdir {
+        println!(
+            "no changes added to commit (use \"git add\" and/or \
+             \"git commit -a\")"
+        );
+    }
+}
+
+// This version of the output prefixes each path with two status columns and
+// shows submodule status information.
+fn print_short(repo: &Repository, statuses: &git2::Statuses) {
+    for entry in statuses
+        .iter()
+        .filter(|e| e.status() != git2::Status::CURRENT)
+    {
+        let mut istatus = match entry.status() {
+            s if s.contains(git2::Status::INDEX_NEW) => 'A',
+            s if s.contains(git2::Status::INDEX_MODIFIED) => 'M',
+            s if s.contains(git2::Status::INDEX_DELETED) => 'D',
+            s if s.contains(git2::Status::INDEX_RENAMED) => 'R',
+            s if s.contains(git2::Status::INDEX_TYPECHANGE) => 'T',
+            _ => ' ',
+        };
+        let mut wstatus = match entry.status() {
+            s if s.contains(git2::Status::WT_NEW) => {
+                if istatus == ' ' {
+                    istatus = '?';
+                }
+                '?'
+            }
+            s if s.contains(git2::Status::WT_MODIFIED) => 'M',
+            s if s.contains(git2::Status::WT_DELETED) => 'D',
+            s if s.contains(git2::Status::WT_RENAMED) => 'R',
+            s if s.contains(git2::Status::WT_TYPECHANGE) => 'T',
+            _ => ' ',
+        };
+
+        if entry.status().contains(git2::Status::IGNORED) {
+            istatus = '!';
+            wstatus = '!';
+        }
+        if istatus == '?' && wstatus == '?' {
+            continue;
+        }
+        let mut extra = "";
+
+        // A commit in a tree is how submodules are stored, so let's go take a
+        // look at its status.
+        //
+        // TODO: check for GIT_FILEMODE_COMMIT
+        let status = entry.index_to_workdir().and_then(|diff| {
+            let ignore = SubmoduleIgnore::Unspecified;
+            diff.new_file()
+                .path_bytes()
+                .and_then(|s| str::from_utf8(s).ok())
+                .and_then(|name| repo.submodule_status(name, ignore).ok())
+        });
+        if let Some(status) = status {
+            if status.contains(git2::SubmoduleStatus::WD_MODIFIED) {
+                extra = " (new commits)";
+            } else if status.contains(git2::SubmoduleStatus::WD_INDEX_MODIFIED)
+                || status.contains(git2::SubmoduleStatus::WD_WD_MODIFIED)
+            {
+                extra = " (modified content)";
+            } else if status.contains(git2::SubmoduleStatus::WD_UNTRACKED) {
+                extra = " (untracked content)";
+            }
+        }
+
+        let (mut a, mut b, mut c) = (None, None, None);
+        if let Some(diff) = entry.head_to_index() {
+            a = diff.old_file().path();
+            b = diff.new_file().path();
+        }
+        if let Some(diff) = entry.index_to_workdir() {
+            a = a.or_else(|| diff.old_file().path());
+            b = b.or_else(|| diff.old_file().path());
+            c = diff.new_file().path();
+        }
+
+        match (istatus, wstatus) {
+            ('R', 'R') => println!(
+                "RR {} {} {}{}",
+                a.unwrap().display(),
+                b.unwrap().display(),
+                c.unwrap().display(),
+                extra
+            ),
+            ('R', w) => println!(
+                "R{} {} {}{}",
+                w,
+                a.unwrap().display(),
+                b.unwrap().display(),
+                extra
+            ),
+            (i, 'R') => println!(
+                "{}R {} {}{}",
+                i,
+                a.unwrap().display(),
+                c.unwrap().display(),
+                extra
+            ),
+            (i, w) => println!("{}{} {}{}", i, w, a.unwrap().display(), extra),
+        }
+    }
+
+    for entry in statuses
+        .iter()
+        .filter(|e| e.status() == git2::Status::WT_NEW)
+    {
+        println!(
+            "?? {}",
+            entry
+                .index_to_workdir()
+                .unwrap()
+                .old_file()
+                .path()
+                .unwrap()
+                .display()
+        );
+    }
+}
+
+impl Args {
+    fn format(&self) -> Format {
+        if self.flag_short {
+            Format::Short
+        } else if self.flag_porcelain || self.flag_z {
+            Format::Porcelain
+        } else {
+            Format::Long
+        }
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/examples/tag.rs b/git2/examples/tag.rs
new file mode 100644 (file)
index 0000000..5d2af02
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * libgit2 "tag" example - shows how to list, create and delete tags
+ *
+ * Written by the libgit2 contributors
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty.
+ *
+ * You should have received a copy of the CC0 Public Domain Dedication along
+ * with this software. If not, see
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#![deny(warnings)]
+
+use clap::Parser;
+use git2::{Commit, Error, Repository, Tag};
+use std::str;
+
+#[derive(Parser)]
+struct Args {
+    arg_tagname: Option<String>,
+    arg_object: Option<String>,
+    arg_pattern: Option<String>,
+    #[structopt(name = "n", short)]
+    /// specify number of lines from the annotation to print
+    flag_n: Option<u32>,
+    #[structopt(name = "force", short, long)]
+    /// replace an existing tag with the given name
+    flag_force: bool,
+    #[structopt(name = "list", short, long)]
+    /// list tags with names matching the pattern given
+    flag_list: bool,
+    #[structopt(name = "tag", short, long = "delete")]
+    /// delete the tag specified
+    flag_delete: Option<String>,
+    #[structopt(name = "msg", short, long = "message")]
+    /// message for a new tag
+    flag_message: Option<String>,
+}
+
+fn run(args: &Args) -> Result<(), Error> {
+    let repo = Repository::open(".")?;
+
+    if let Some(ref name) = args.arg_tagname {
+        let target = args.arg_object.as_ref().map(|s| &s[..]).unwrap_or("HEAD");
+        let obj = repo.revparse_single(target)?;
+
+        if let Some(ref message) = args.flag_message {
+            let sig = repo.signature()?;
+            repo.tag(name, &obj, &sig, message, args.flag_force)?;
+        } else {
+            repo.tag_lightweight(name, &obj, args.flag_force)?;
+        }
+    } else if let Some(ref name) = args.flag_delete {
+        let obj = repo.revparse_single(name)?;
+        let id = obj.short_id()?;
+        repo.tag_delete(name)?;
+        println!(
+            "Deleted tag '{}' (was {})",
+            name,
+            str::from_utf8(&*id).unwrap()
+        );
+    } else if args.flag_list {
+        let pattern = args.arg_pattern.as_ref().map(|s| &s[..]).unwrap_or("*");
+        for name in repo.tag_names(Some(pattern))?.iter() {
+            let name = name.unwrap();
+            let obj = repo.revparse_single(name)?;
+
+            if let Some(tag) = obj.as_tag() {
+                print_tag(tag, args);
+            } else if let Some(commit) = obj.as_commit() {
+                print_commit(commit, name, args);
+            } else {
+                print_name(name);
+            }
+        }
+    }
+    Ok(())
+}
+
+fn print_tag(tag: &Tag, args: &Args) {
+    print!("{:<16}", tag.name().unwrap());
+    if args.flag_n.is_some() {
+        print_list_lines(tag.message(), args);
+    } else {
+        println!();
+    }
+}
+
+fn print_commit(commit: &Commit, name: &str, args: &Args) {
+    print!("{:<16}", name);
+    if args.flag_n.is_some() {
+        print_list_lines(commit.message(), args);
+    } else {
+        println!();
+    }
+}
+
+fn print_name(name: &str) {
+    println!("{}", name);
+}
+
+fn print_list_lines(message: Option<&str>, args: &Args) {
+    let message = match message {
+        Some(s) => s,
+        None => return,
+    };
+    let mut lines = message.lines().filter(|l| !l.trim().is_empty());
+    if let Some(first) = lines.next() {
+        print!("{}", first);
+    }
+    println!();
+
+    for line in lines.take(args.flag_n.unwrap_or(0) as usize) {
+        print!("    {}", line);
+    }
+}
+
+fn main() {
+    let args = Args::parse();
+    match run(&args) {
+        Ok(()) => {}
+        Err(e) => println!("error: {}", e),
+    }
+}
diff --git a/git2/src/apply.rs b/git2/src/apply.rs
new file mode 100644 (file)
index 0000000..34dc811
--- /dev/null
@@ -0,0 +1,208 @@
+//! git_apply support
+//! see original: <https://github.com/libgit2/libgit2/blob/master/include/git2/apply.h>
+
+use crate::{panic, raw, util::Binding, DiffDelta, DiffHunk};
+use libc::c_int;
+use std::{ffi::c_void, mem};
+
+/// Possible application locations for git_apply
+/// see <https://libgit2.org/libgit2/#HEAD/type/git_apply_options>
+#[derive(Copy, Clone, Debug)]
+pub enum ApplyLocation {
+    /// Apply the patch to the workdir
+    WorkDir,
+    /// Apply the patch to the index
+    Index,
+    /// Apply the patch to both the working directory and the index
+    Both,
+}
+
+impl Binding for ApplyLocation {
+    type Raw = raw::git_apply_location_t;
+    unsafe fn from_raw(raw: raw::git_apply_location_t) -> Self {
+        match raw {
+            raw::GIT_APPLY_LOCATION_WORKDIR => Self::WorkDir,
+            raw::GIT_APPLY_LOCATION_INDEX => Self::Index,
+            raw::GIT_APPLY_LOCATION_BOTH => Self::Both,
+            _ => panic!("Unknown git diff binary kind"),
+        }
+    }
+    fn raw(&self) -> raw::git_apply_location_t {
+        match *self {
+            Self::WorkDir => raw::GIT_APPLY_LOCATION_WORKDIR,
+            Self::Index => raw::GIT_APPLY_LOCATION_INDEX,
+            Self::Both => raw::GIT_APPLY_LOCATION_BOTH,
+        }
+    }
+}
+
+/// Options to specify when applying a diff
+pub struct ApplyOptions<'cb> {
+    raw: raw::git_apply_options,
+    hunk_cb: Option<Box<HunkCB<'cb>>>,
+    delta_cb: Option<Box<DeltaCB<'cb>>>,
+}
+
+type HunkCB<'a> = dyn FnMut(Option<DiffHunk<'_>>) -> bool + 'a;
+type DeltaCB<'a> = dyn FnMut(Option<DiffDelta<'_>>) -> bool + 'a;
+
+extern "C" fn delta_cb_c(delta: *const raw::git_diff_delta, data: *mut c_void) -> c_int {
+    panic::wrap(|| unsafe {
+        let delta = Binding::from_raw_opt(delta as *mut _);
+
+        let payload = &mut *(data as *mut ApplyOptions<'_>);
+        let callback = match payload.delta_cb {
+            Some(ref mut c) => c,
+            None => return -1,
+        };
+
+        let apply = callback(delta);
+        if apply {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(-1)
+}
+
+extern "C" fn hunk_cb_c(hunk: *const raw::git_diff_hunk, data: *mut c_void) -> c_int {
+    panic::wrap(|| unsafe {
+        let hunk = Binding::from_raw_opt(hunk);
+
+        let payload = &mut *(data as *mut ApplyOptions<'_>);
+        let callback = match payload.hunk_cb {
+            Some(ref mut c) => c,
+            None => return -1,
+        };
+
+        let apply = callback(hunk);
+        if apply {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(-1)
+}
+
+impl<'cb> ApplyOptions<'cb> {
+    /// Creates a new set of empty options (zeroed).
+    pub fn new() -> Self {
+        let mut opts = Self {
+            raw: unsafe { mem::zeroed() },
+            hunk_cb: None,
+            delta_cb: None,
+        };
+        assert_eq!(
+            unsafe { raw::git_apply_options_init(&mut opts.raw, raw::GIT_APPLY_OPTIONS_VERSION) },
+            0
+        );
+        opts
+    }
+
+    fn flag(&mut self, opt: raw::git_apply_flags_t, val: bool) -> &mut Self {
+        let opt = opt as u32;
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Don't actually make changes, just test that the patch applies.
+    pub fn check(&mut self, check: bool) -> &mut Self {
+        self.flag(raw::GIT_APPLY_CHECK, check)
+    }
+
+    /// When applying a patch, callback that will be made per hunk.
+    pub fn hunk_callback<F>(&mut self, cb: F) -> &mut Self
+    where
+        F: FnMut(Option<DiffHunk<'_>>) -> bool + 'cb,
+    {
+        self.hunk_cb = Some(Box::new(cb) as Box<HunkCB<'cb>>);
+
+        self.raw.hunk_cb = Some(hunk_cb_c);
+        self.raw.payload = self as *mut _ as *mut _;
+
+        self
+    }
+
+    /// When applying a patch, callback that will be made per delta (file).
+    pub fn delta_callback<F>(&mut self, cb: F) -> &mut Self
+    where
+        F: FnMut(Option<DiffDelta<'_>>) -> bool + 'cb,
+    {
+        self.delta_cb = Some(Box::new(cb) as Box<DeltaCB<'cb>>);
+
+        self.raw.delta_cb = Some(delta_cb_c);
+        self.raw.payload = self as *mut _ as *mut _;
+
+        self
+    }
+
+    /// Pointer to a raw git_stash_apply_options
+    pub unsafe fn raw(&mut self) -> *const raw::git_apply_options {
+        &self.raw as *const _
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::{fs::File, io::Write, path::Path};
+
+    #[test]
+    fn smoke_test() {
+        let (_td, repo) = crate::test::repo_init();
+        let diff = t!(repo.diff_tree_to_workdir(None, None));
+        let mut count_hunks = 0;
+        let mut count_delta = 0;
+        {
+            let mut opts = ApplyOptions::new();
+            opts.hunk_callback(|_hunk| {
+                count_hunks += 1;
+                true
+            });
+            opts.delta_callback(|_delta| {
+                count_delta += 1;
+                true
+            });
+            t!(repo.apply(&diff, ApplyLocation::Both, Some(&mut opts)));
+        }
+        assert_eq!(count_hunks, 0);
+        assert_eq!(count_delta, 0);
+    }
+
+    #[test]
+    fn apply_hunks_and_delta() {
+        let file_path = Path::new("foo.txt");
+        let (td, repo) = crate::test::repo_init();
+        // create new file
+        t!(t!(File::create(&td.path().join(file_path))).write_all(b"bar"));
+        // stage the new file
+        t!(t!(repo.index()).add_path(file_path));
+        // now change workdir version
+        t!(t!(File::create(&td.path().join(file_path))).write_all(b"foo\nbar"));
+
+        let diff = t!(repo.diff_index_to_workdir(None, None));
+        assert_eq!(diff.deltas().len(), 1);
+        let mut count_hunks = 0;
+        let mut count_delta = 0;
+        {
+            let mut opts = ApplyOptions::new();
+            opts.hunk_callback(|_hunk| {
+                count_hunks += 1;
+                true
+            });
+            opts.delta_callback(|_delta| {
+                count_delta += 1;
+                true
+            });
+            t!(repo.apply(&diff, ApplyLocation::Index, Some(&mut opts)));
+        }
+        assert_eq!(count_delta, 1);
+        assert_eq!(count_hunks, 1);
+    }
+}
diff --git a/git2/src/attr.rs b/git2/src/attr.rs
new file mode 100644 (file)
index 0000000..33b1d2d
--- /dev/null
@@ -0,0 +1,175 @@
+use crate::raw;
+use std::ptr;
+use std::str;
+
+/// All possible states of an attribute.
+///
+/// This enum is used to interpret the value returned by
+/// [`Repository::get_attr`](crate::Repository::get_attr) and
+/// [`Repository::get_attr_bytes`](crate::Repository::get_attr_bytes).
+#[derive(Debug, Clone, Copy, Eq)]
+pub enum AttrValue<'string> {
+    /// The attribute is set to true.
+    True,
+    /// The attribute is unset (set to false).
+    False,
+    /// The attribute is set to a [valid UTF-8 string](prim@str).
+    String(&'string str),
+    /// The attribute is set to a string that might not be [valid UTF-8](prim@str).
+    Bytes(&'string [u8]),
+    /// The attribute is not specified.
+    Unspecified,
+}
+
+macro_rules! from_value {
+    ($value:expr => $string:expr) => {
+        match unsafe { raw::git_attr_value($value.map_or(ptr::null(), |v| v.as_ptr().cast())) } {
+            raw::GIT_ATTR_VALUE_TRUE => Self::True,
+            raw::GIT_ATTR_VALUE_FALSE => Self::False,
+            raw::GIT_ATTR_VALUE_STRING => $string,
+            raw::GIT_ATTR_VALUE_UNSPECIFIED => Self::Unspecified,
+            _ => unreachable!(),
+        }
+    };
+}
+
+impl<'string> AttrValue<'string> {
+    /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr)
+    /// by a [string](prim@str).
+    ///
+    /// This function always returns [`AttrValue::String`] and never returns [`AttrValue::Bytes`]
+    /// when the attribute is set to a string.
+    pub fn from_string(value: Option<&'string str>) -> Self {
+        from_value!(value => Self::String(value.unwrap()))
+    }
+
+    /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr_bytes)
+    /// by a [byte](u8) [slice].
+    ///
+    /// This function will perform UTF-8 validation when the attribute is set to a string, returns
+    /// [`AttrValue::String`] if it's valid UTF-8 and [`AttrValue::Bytes`] otherwise.
+    pub fn from_bytes(value: Option<&'string [u8]>) -> Self {
+        let mut value = Self::always_bytes(value);
+        if let Self::Bytes(bytes) = value {
+            if let Ok(string) = str::from_utf8(bytes) {
+                value = Self::String(string);
+            }
+        }
+        value
+    }
+
+    /// Returns the state of an attribute just like [`AttrValue::from_bytes`], but skips UTF-8
+    /// validation and always returns [`AttrValue::Bytes`] when it's set to a string.
+    pub fn always_bytes(value: Option<&'string [u8]>) -> Self {
+        from_value!(value => Self::Bytes(value.unwrap()))
+    }
+}
+
+/// Compare two [`AttrValue`]s.
+///
+/// Note that this implementation does not differentiate between [`AttrValue::String`] and
+/// [`AttrValue::Bytes`].
+impl PartialEq for AttrValue<'_> {
+    fn eq(&self, other: &AttrValue<'_>) -> bool {
+        match (self, other) {
+            (Self::True, AttrValue::True)
+            | (Self::False, AttrValue::False)
+            | (Self::Unspecified, AttrValue::Unspecified) => true,
+            (AttrValue::String(string), AttrValue::Bytes(bytes))
+            | (AttrValue::Bytes(bytes), AttrValue::String(string)) => string.as_bytes() == *bytes,
+            (AttrValue::String(left), AttrValue::String(right)) => left == right,
+            (AttrValue::Bytes(left), AttrValue::Bytes(right)) => left == right,
+            _ => false,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::AttrValue;
+
+    macro_rules! test_attr_value {
+        ($function:ident, $variant:ident) => {
+            const ATTR_TRUE: &str = "[internal]__TRUE__";
+            const ATTR_FALSE: &str = "[internal]__FALSE__";
+            const ATTR_UNSET: &str = "[internal]__UNSET__";
+            let as_bytes = AsRef::<[u8]>::as_ref;
+            // Use `matches!` here since the `PartialEq` implementation does not differentiate
+            // between `String` and `Bytes`.
+            assert!(matches!(
+                AttrValue::$function(Some(ATTR_TRUE.as_ref())),
+                AttrValue::$variant(s) if as_bytes(s) == ATTR_TRUE.as_bytes()
+            ));
+            assert!(matches!(
+                AttrValue::$function(Some(ATTR_FALSE.as_ref())),
+                AttrValue::$variant(s) if as_bytes(s) == ATTR_FALSE.as_bytes()
+            ));
+            assert!(matches!(
+                AttrValue::$function(Some(ATTR_UNSET.as_ref())),
+                AttrValue::$variant(s) if as_bytes(s) == ATTR_UNSET.as_bytes()
+            ));
+            assert!(matches!(
+                AttrValue::$function(Some("foo".as_ref())),
+                AttrValue::$variant(s) if as_bytes(s) == b"foo"
+            ));
+            assert!(matches!(
+                AttrValue::$function(Some("bar".as_ref())),
+                AttrValue::$variant(s) if as_bytes(s) == b"bar"
+            ));
+            assert_eq!(AttrValue::$function(None), AttrValue::Unspecified);
+        };
+    }
+
+    #[test]
+    fn attr_value_from_string() {
+        test_attr_value!(from_string, String);
+    }
+
+    #[test]
+    fn attr_value_from_bytes() {
+        test_attr_value!(from_bytes, String);
+        assert!(matches!(
+            AttrValue::from_bytes(Some(&[0xff])),
+            AttrValue::Bytes(&[0xff])
+        ));
+        assert!(matches!(
+            AttrValue::from_bytes(Some(b"\xffoobar")),
+            AttrValue::Bytes(b"\xffoobar")
+        ));
+    }
+
+    #[test]
+    fn attr_value_always_bytes() {
+        test_attr_value!(always_bytes, Bytes);
+        assert!(matches!(
+            AttrValue::always_bytes(Some(&[0xff; 2])),
+            AttrValue::Bytes(&[0xff, 0xff])
+        ));
+        assert!(matches!(
+            AttrValue::always_bytes(Some(b"\xffoo")),
+            AttrValue::Bytes(b"\xffoo")
+        ));
+    }
+
+    #[test]
+    fn attr_value_partial_eq() {
+        assert_eq!(AttrValue::True, AttrValue::True);
+        assert_eq!(AttrValue::False, AttrValue::False);
+        assert_eq!(AttrValue::String("foo"), AttrValue::String("foo"));
+        assert_eq!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"foo"));
+        assert_eq!(AttrValue::String("bar"), AttrValue::Bytes(b"bar"));
+        assert_eq!(AttrValue::Bytes(b"bar"), AttrValue::String("bar"));
+        assert_eq!(AttrValue::Unspecified, AttrValue::Unspecified);
+        assert_ne!(AttrValue::True, AttrValue::False);
+        assert_ne!(AttrValue::False, AttrValue::Unspecified);
+        assert_ne!(AttrValue::Unspecified, AttrValue::True);
+        assert_ne!(AttrValue::True, AttrValue::String("true"));
+        assert_ne!(AttrValue::Unspecified, AttrValue::Bytes(b"unspecified"));
+        assert_ne!(AttrValue::Bytes(b"false"), AttrValue::False);
+        assert_ne!(AttrValue::String("unspecified"), AttrValue::Unspecified);
+        assert_ne!(AttrValue::String("foo"), AttrValue::String("bar"));
+        assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"bar"));
+        assert_ne!(AttrValue::String("foo"), AttrValue::Bytes(b"bar"));
+        assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::String("bar"));
+    }
+}
diff --git a/git2/src/blame.rs b/git2/src/blame.rs
new file mode 100644 (file)
index 0000000..4bf41fe
--- /dev/null
@@ -0,0 +1,379 @@
+use crate::util::{self, Binding};
+use crate::{raw, signature, Error, Oid, Repository, Signature};
+use libc::c_char;
+use std::iter::FusedIterator;
+use std::mem;
+use std::ops::Range;
+use std::path::Path;
+use std::{marker, ptr};
+
+/// Opaque structure to hold blame results.
+pub struct Blame<'repo> {
+    raw: *mut raw::git_blame,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// Structure that represents a blame hunk.
+pub struct BlameHunk<'blame> {
+    raw: *mut raw::git_blame_hunk,
+    _marker: marker::PhantomData<&'blame raw::git_blame>,
+}
+
+/// Blame options
+pub struct BlameOptions {
+    raw: raw::git_blame_options,
+}
+
+/// An iterator over the hunks in a blame.
+pub struct BlameIter<'blame> {
+    range: Range<usize>,
+    blame: &'blame Blame<'blame>,
+}
+
+impl<'repo> Blame<'repo> {
+    /// Get blame data for a file that has been modified in memory.
+    ///
+    /// Lines that differ between the buffer and the committed version are
+    /// marked as having a zero OID for their final_commit_id.
+    pub fn blame_buffer(&self, buffer: &[u8]) -> Result<Blame<'_>, Error> {
+        let mut raw = ptr::null_mut();
+
+        unsafe {
+            try_call!(raw::git_blame_buffer(
+                &mut raw,
+                self.raw,
+                buffer.as_ptr() as *const c_char,
+                buffer.len()
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Gets the number of hunks that exist in the blame structure.
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_blame_get_hunk_count(self.raw) as usize }
+    }
+
+    /// Return `true` is there is no hunk in the blame structure.
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Gets the blame hunk at the given index.
+    pub fn get_index(&self, index: usize) -> Option<BlameHunk<'_>> {
+        unsafe {
+            let ptr = raw::git_blame_get_hunk_byindex(self.raw(), index as u32);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(BlameHunk::from_raw_const(ptr))
+            }
+        }
+    }
+
+    /// Gets the hunk that relates to the given line number in the newest
+    /// commit.
+    pub fn get_line(&self, lineno: usize) -> Option<BlameHunk<'_>> {
+        unsafe {
+            let ptr = raw::git_blame_get_hunk_byline(self.raw(), lineno);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(BlameHunk::from_raw_const(ptr))
+            }
+        }
+    }
+
+    /// Returns an iterator over the hunks in this blame.
+    pub fn iter(&self) -> BlameIter<'_> {
+        BlameIter {
+            range: 0..self.len(),
+            blame: self,
+        }
+    }
+}
+
+impl<'blame> BlameHunk<'blame> {
+    unsafe fn from_raw_const(raw: *const raw::git_blame_hunk) -> BlameHunk<'blame> {
+        BlameHunk {
+            raw: raw as *mut raw::git_blame_hunk,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    /// Returns OID of the commit where this line was last changed
+    pub fn final_commit_id(&self) -> Oid {
+        unsafe { Oid::from_raw(&(*self.raw).final_commit_id) }
+    }
+
+    /// Returns signature of the commit.
+    pub fn final_signature(&self) -> Signature<'_> {
+        unsafe { signature::from_raw_const(self, (*self.raw).final_signature) }
+    }
+
+    /// Returns line number where this hunk begins.
+    ///
+    /// Note that the start line is counting from 1.
+    pub fn final_start_line(&self) -> usize {
+        unsafe { (*self.raw).final_start_line_number }
+    }
+
+    /// Returns the OID of the commit where this hunk was found.
+    ///
+    /// This will usually be the same as `final_commit_id`,
+    /// except when `BlameOptions::track_copies_any_commit_copies` has been
+    /// turned on
+    pub fn orig_commit_id(&self) -> Oid {
+        unsafe { Oid::from_raw(&(*self.raw).orig_commit_id) }
+    }
+
+    /// Returns signature of the commit.
+    pub fn orig_signature(&self) -> Signature<'_> {
+        unsafe { signature::from_raw_const(self, (*self.raw).orig_signature) }
+    }
+
+    /// Returns line number where this hunk begins.
+    ///
+    /// Note that the start line is counting from 1.
+    pub fn orig_start_line(&self) -> usize {
+        unsafe { (*self.raw).orig_start_line_number }
+    }
+
+    /// Returns path to the file where this hunk originated.
+    ///
+    /// Note: `None` could be returned for non-unicode paths on Windows.
+    pub fn path(&self) -> Option<&Path> {
+        unsafe {
+            if let Some(bytes) = crate::opt_bytes(self, (*self.raw).orig_path) {
+                Some(util::bytes2path(bytes))
+            } else {
+                None
+            }
+        }
+    }
+
+    /// Tests whether this hunk has been tracked to a boundary commit
+    /// (the root, or the commit specified in git_blame_options.oldest_commit).
+    pub fn is_boundary(&self) -> bool {
+        unsafe { (*self.raw).boundary == 1 }
+    }
+
+    /// Returns number of lines in this hunk.
+    pub fn lines_in_hunk(&self) -> usize {
+        unsafe { (*self.raw).lines_in_hunk as usize }
+    }
+}
+
+impl Default for BlameOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl BlameOptions {
+    /// Initialize options
+    pub fn new() -> BlameOptions {
+        unsafe {
+            let mut raw: raw::git_blame_options = mem::zeroed();
+            assert_eq!(
+                raw::git_blame_init_options(&mut raw, raw::GIT_BLAME_OPTIONS_VERSION),
+                0
+            );
+
+            Binding::from_raw(&raw as *const _ as *mut _)
+        }
+    }
+
+    fn flag(&mut self, opt: u32, val: bool) -> &mut BlameOptions {
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Track lines that have moved within a file.
+    pub fn track_copies_same_file(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_FILE, opt)
+    }
+
+    /// Track lines that have moved across files in the same commit.
+    pub fn track_copies_same_commit_moves(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES, opt)
+    }
+
+    /// Track lines that have been copied from another file that exists
+    /// in the same commit.
+    pub fn track_copies_same_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES, opt)
+    }
+
+    /// Track lines that have been copied from another file that exists
+    /// in any commit.
+    pub fn track_copies_any_commit_copies(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES, opt)
+    }
+
+    /// Restrict the search of commits to those reachable following only
+    /// the first parents.
+    pub fn first_parent(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_FIRST_PARENT, opt)
+    }
+
+    /// Use mailmap file to map author and committer names and email addresses
+    /// to canonical real names and email addresses. The mailmap will be read
+    /// from the working directory, or HEAD in a bare repository.
+    pub fn use_mailmap(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_USE_MAILMAP, opt)
+    }
+
+    /// Ignore whitespace differences.
+    pub fn ignore_whitespace(&mut self, opt: bool) -> &mut BlameOptions {
+        self.flag(raw::GIT_BLAME_IGNORE_WHITESPACE, opt)
+    }
+
+    /// Setter for the id of the newest commit to consider.
+    pub fn newest_commit(&mut self, id: Oid) -> &mut BlameOptions {
+        unsafe {
+            self.raw.newest_commit = *id.raw();
+        }
+        self
+    }
+
+    /// Setter for the id of the oldest commit to consider.
+    pub fn oldest_commit(&mut self, id: Oid) -> &mut BlameOptions {
+        unsafe {
+            self.raw.oldest_commit = *id.raw();
+        }
+        self
+    }
+
+    /// The first line in the file to blame.
+    pub fn min_line(&mut self, lineno: usize) -> &mut BlameOptions {
+        self.raw.min_line = lineno;
+        self
+    }
+
+    /// The last line in the file to blame.
+    pub fn max_line(&mut self, lineno: usize) -> &mut BlameOptions {
+        self.raw.max_line = lineno;
+        self
+    }
+}
+
+impl<'repo> Binding for Blame<'repo> {
+    type Raw = *mut raw::git_blame;
+
+    unsafe fn from_raw(raw: *mut raw::git_blame) -> Blame<'repo> {
+        Blame {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *mut raw::git_blame {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Blame<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_blame_free(self.raw) }
+    }
+}
+
+impl<'blame> Binding for BlameHunk<'blame> {
+    type Raw = *mut raw::git_blame_hunk;
+
+    unsafe fn from_raw(raw: *mut raw::git_blame_hunk) -> BlameHunk<'blame> {
+        BlameHunk {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *mut raw::git_blame_hunk {
+        self.raw
+    }
+}
+
+impl Binding for BlameOptions {
+    type Raw = *mut raw::git_blame_options;
+
+    unsafe fn from_raw(opts: *mut raw::git_blame_options) -> BlameOptions {
+        BlameOptions { raw: *opts }
+    }
+
+    fn raw(&self) -> *mut raw::git_blame_options {
+        &self.raw as *const _ as *mut _
+    }
+}
+
+impl<'blame> Iterator for BlameIter<'blame> {
+    type Item = BlameHunk<'blame>;
+    fn next(&mut self) -> Option<BlameHunk<'blame>> {
+        self.range.next().and_then(|i| self.blame.get_index(i))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+
+impl<'blame> DoubleEndedIterator for BlameIter<'blame> {
+    fn next_back(&mut self) -> Option<BlameHunk<'blame>> {
+        self.range.next_back().and_then(|i| self.blame.get_index(i))
+    }
+}
+
+impl<'blame> FusedIterator for BlameIter<'blame> {}
+
+impl<'blame> ExactSizeIterator for BlameIter<'blame> {}
+
+#[cfg(test)]
+mod tests {
+    use std::fs::{self, File};
+    use std::path::Path;
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut index = repo.index().unwrap();
+
+        let root = repo.workdir().unwrap();
+        fs::create_dir(&root.join("foo")).unwrap();
+        File::create(&root.join("foo/bar")).unwrap();
+        index.add_path(Path::new("foo/bar")).unwrap();
+
+        let id = index.write_tree().unwrap();
+        let tree = repo.find_tree(id).unwrap();
+        let sig = repo.signature().unwrap();
+        let id = repo.refname_to_id("HEAD").unwrap();
+        let parent = repo.find_commit(id).unwrap();
+        let commit = repo
+            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
+            .unwrap();
+
+        let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap();
+
+        assert_eq!(blame.len(), 1);
+        assert_eq!(blame.iter().count(), 1);
+
+        let hunk = blame.get_index(0).unwrap();
+        assert_eq!(hunk.final_commit_id(), commit);
+        assert_eq!(hunk.final_signature().name(), sig.name());
+        assert_eq!(hunk.final_signature().email(), sig.email());
+        assert_eq!(hunk.final_start_line(), 1);
+        assert_eq!(hunk.path(), Some(Path::new("foo/bar")));
+        assert_eq!(hunk.lines_in_hunk(), 0);
+        assert!(!hunk.is_boundary());
+
+        let blame_buffer = blame.blame_buffer("\n".as_bytes()).unwrap();
+        let line = blame_buffer.get_line(1).unwrap();
+
+        assert_eq!(blame_buffer.len(), 2);
+        assert_eq!(blame_buffer.iter().count(), 2);
+        assert!(line.final_commit_id().is_zero());
+    }
+}
diff --git a/git2/src/blob.rs b/git2/src/blob.rs
new file mode 100644 (file)
index 0000000..5c4a6ce
--- /dev/null
@@ -0,0 +1,208 @@
+use std::io;
+use std::marker;
+use std::mem;
+use std::slice;
+
+use crate::util::Binding;
+use crate::{raw, Error, Object, Oid};
+
+/// A structure to represent a git [blob][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Blob<'repo> {
+    raw: *mut raw::git_blob,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+impl<'repo> Blob<'repo> {
+    /// Get the id (SHA1) of a repository blob
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_blob_id(&*self.raw)) }
+    }
+
+    /// Determine if the blob content is most certainly binary or not.
+    pub fn is_binary(&self) -> bool {
+        unsafe { raw::git_blob_is_binary(&*self.raw) == 1 }
+    }
+
+    /// Get the content of this blob.
+    pub fn content(&self) -> &[u8] {
+        unsafe {
+            let data = raw::git_blob_rawcontent(&*self.raw) as *const u8;
+            let len = raw::git_blob_rawsize(&*self.raw) as usize;
+            slice::from_raw_parts(data, len)
+        }
+    }
+
+    /// Get the size in bytes of the contents of this blob.
+    pub fn size(&self) -> usize {
+        unsafe { raw::git_blob_rawsize(&*self.raw) as usize }
+    }
+
+    /// Casts this Blob to be usable as an `Object`
+    pub fn as_object(&self) -> &Object<'repo> {
+        unsafe { &*(self as *const _ as *const Object<'repo>) }
+    }
+
+    /// Consumes Blob to be returned as an `Object`
+    pub fn into_object(self) -> Object<'repo> {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
+        unsafe { mem::transmute(self) }
+    }
+}
+
+impl<'repo> Binding for Blob<'repo> {
+    type Raw = *mut raw::git_blob;
+
+    unsafe fn from_raw(raw: *mut raw::git_blob) -> Blob<'repo> {
+        Blob {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_blob {
+        self.raw
+    }
+}
+
+impl<'repo> std::fmt::Debug for Blob<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("Blob").field("id", &self.id()).finish()
+    }
+}
+
+impl<'repo> Clone for Blob<'repo> {
+    fn clone(&self) -> Self {
+        self.as_object().clone().into_blob().ok().unwrap()
+    }
+}
+
+impl<'repo> Drop for Blob<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_blob_free(self.raw) }
+    }
+}
+
+/// A structure to represent a git writestream for blobs
+pub struct BlobWriter<'repo> {
+    raw: *mut raw::git_writestream,
+    need_cleanup: bool,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+impl<'repo> BlobWriter<'repo> {
+    /// Finalize blob writing stream and write the blob to the object db
+    pub fn commit(mut self) -> Result<Oid, Error> {
+        // After commit we already doesn't need cleanup on drop
+        self.need_cleanup = false;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_blob_create_fromstream_commit(&mut raw, self.raw));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+}
+
+impl<'repo> Binding for BlobWriter<'repo> {
+    type Raw = *mut raw::git_writestream;
+
+    unsafe fn from_raw(raw: *mut raw::git_writestream) -> BlobWriter<'repo> {
+        BlobWriter {
+            raw,
+            need_cleanup: true,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_writestream {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for BlobWriter<'repo> {
+    fn drop(&mut self) {
+        // We need cleanup in case the stream has not been committed
+        if self.need_cleanup {
+            unsafe {
+                if let Some(f) = (*self.raw).free {
+                    f(self.raw)
+                }
+            }
+        }
+    }
+}
+
+impl<'repo> io::Write for BlobWriter<'repo> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe {
+            if let Some(f) = (*self.raw).write {
+                let res = f(self.raw, buf.as_ptr() as *const _, buf.len());
+                if res < 0 {
+                    Err(io::Error::new(io::ErrorKind::Other, "Write error"))
+                } else {
+                    Ok(buf.len())
+                }
+            } else {
+                Err(io::Error::new(io::ErrorKind::Other, "no write callback"))
+            }
+        }
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::Repository;
+    use std::fs::File;
+    use std::io::prelude::*;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    #[test]
+    fn buffer() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let id = repo.blob(&[5, 4, 6]).unwrap();
+        let blob = repo.find_blob(id).unwrap();
+
+        assert_eq!(blob.id(), id);
+        assert_eq!(blob.size(), 3);
+        assert_eq!(blob.content(), [5, 4, 6]);
+        assert!(blob.is_binary());
+
+        repo.find_object(id, None).unwrap().as_blob().unwrap();
+        repo.find_object(id, None)
+            .unwrap()
+            .into_blob()
+            .ok()
+            .unwrap();
+    }
+
+    #[test]
+    fn path() {
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("foo");
+        File::create(&path).unwrap().write_all(&[7, 8, 9]).unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let id = repo.blob_path(&path).unwrap();
+        let blob = repo.find_blob(id).unwrap();
+        assert_eq!(blob.content(), [7, 8, 9]);
+        blob.into_object();
+    }
+
+    #[test]
+    fn stream() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let mut ws = repo.blob_writer(Some(Path::new("foo"))).unwrap();
+        let wl = ws.write(&[10, 11, 12]).unwrap();
+        assert_eq!(wl, 3);
+        let id = ws.commit().unwrap();
+        let blob = repo.find_blob(id).unwrap();
+        assert_eq!(blob.content(), [10, 11, 12]);
+        blob.into_object();
+    }
+}
diff --git a/git2/src/branch.rs b/git2/src/branch.rs
new file mode 100644 (file)
index 0000000..e1eba99
--- /dev/null
@@ -0,0 +1,197 @@
+use std::ffi::CString;
+use std::marker;
+use std::ptr;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, BranchType, Error, Reference, References};
+
+/// A structure to represent a git [branch][1]
+///
+/// A branch is currently just a wrapper to an underlying `Reference`. The
+/// reference can be accessed through the `get` and `into_reference` methods.
+///
+/// [1]: http://git-scm.com/book/en/Git-Branching-What-a-Branch-Is
+pub struct Branch<'repo> {
+    inner: Reference<'repo>,
+}
+
+/// An iterator over the branches inside of a repository.
+pub struct Branches<'repo> {
+    raw: *mut raw::git_branch_iterator,
+    _marker: marker::PhantomData<References<'repo>>,
+}
+
+impl<'repo> Branch<'repo> {
+    /// Creates Branch type from a Reference
+    pub fn wrap(reference: Reference<'_>) -> Branch<'_> {
+        Branch { inner: reference }
+    }
+
+    /// Ensure the branch name is well-formed.
+    pub fn name_is_valid(name: &str) -> Result<bool, Error> {
+        crate::init();
+        let name = CString::new(name)?;
+        let mut valid: libc::c_int = 0;
+        unsafe {
+            try_call!(raw::git_branch_name_is_valid(&mut valid, name.as_ptr()));
+        }
+        Ok(valid == 1)
+    }
+
+    /// Gain access to the reference that is this branch
+    pub fn get(&self) -> &Reference<'repo> {
+        &self.inner
+    }
+
+    /// Gain mutable access to the reference that is this branch
+    pub fn get_mut(&mut self) -> &mut Reference<'repo> {
+        &mut self.inner
+    }
+
+    /// Take ownership of the underlying reference.
+    pub fn into_reference(self) -> Reference<'repo> {
+        self.inner
+    }
+
+    /// Delete an existing branch reference.
+    pub fn delete(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_branch_delete(self.get().raw()));
+        }
+        Ok(())
+    }
+
+    /// Determine if the current local branch is pointed at by HEAD.
+    pub fn is_head(&self) -> bool {
+        unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 }
+    }
+
+    /// Move/rename an existing local branch reference.
+    pub fn rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error> {
+        let mut ret = ptr::null_mut();
+        let new_branch_name = CString::new(new_branch_name)?;
+        unsafe {
+            try_call!(raw::git_branch_move(
+                &mut ret,
+                self.get().raw(),
+                new_branch_name,
+                force
+            ));
+            Ok(Branch::wrap(Binding::from_raw(ret)))
+        }
+    }
+
+    /// Return the name of the given local or remote branch.
+    ///
+    /// May return `Ok(None)` if the name is not valid utf-8.
+    pub fn name(&self) -> Result<Option<&str>, Error> {
+        self.name_bytes().map(|s| str::from_utf8(s).ok())
+    }
+
+    /// Return the name of the given local or remote branch.
+    pub fn name_bytes(&self) -> Result<&[u8], Error> {
+        let mut ret = ptr::null();
+        unsafe {
+            try_call!(raw::git_branch_name(&mut ret, &*self.get().raw()));
+            Ok(crate::opt_bytes(self, ret).unwrap())
+        }
+    }
+
+    /// Return the reference supporting the remote tracking branch, given a
+    /// local branch reference.
+    pub fn upstream(&self) -> Result<Branch<'repo>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_branch_upstream(&mut ret, &*self.get().raw()));
+            Ok(Branch::wrap(Binding::from_raw(ret)))
+        }
+    }
+
+    /// Set the upstream configuration for a given local branch.
+    ///
+    /// If `None` is specified, then the upstream branch is unset. The name
+    /// provided is the name of the branch to set as upstream.
+    pub fn set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error> {
+        let upstream_name = crate::opt_cstr(upstream_name)?;
+        unsafe {
+            try_call!(raw::git_branch_set_upstream(
+                self.get().raw(),
+                upstream_name
+            ));
+            Ok(())
+        }
+    }
+}
+
+impl<'repo> Branches<'repo> {
+    /// Creates a new iterator from the raw pointer given.
+    ///
+    /// This function is unsafe as it is not guaranteed that `raw` is a valid
+    /// pointer.
+    pub unsafe fn from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo> {
+        Branches {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+}
+
+impl<'repo> Iterator for Branches<'repo> {
+    type Item = Result<(Branch<'repo>, BranchType), Error>;
+    fn next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>> {
+        let mut ret = ptr::null_mut();
+        let mut typ = raw::GIT_BRANCH_LOCAL;
+        unsafe {
+            try_call_iter!(raw::git_branch_next(&mut ret, &mut typ, self.raw));
+            let typ = match typ {
+                raw::GIT_BRANCH_LOCAL => BranchType::Local,
+                raw::GIT_BRANCH_REMOTE => BranchType::Remote,
+                n => panic!("unexected branch type: {}", n),
+            };
+            Some(Ok((Branch::wrap(Binding::from_raw(ret)), typ)))
+        }
+    }
+}
+
+impl<'repo> Drop for Branches<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_branch_iterator_free(self.raw) }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Branch, BranchType};
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+
+        let mut b1 = repo.branch("foo", &commit, false).unwrap();
+        assert!(!b1.is_head());
+        repo.branch("foo2", &commit, false).unwrap();
+
+        assert_eq!(repo.branches(None).unwrap().count(), 3);
+        repo.find_branch("foo", BranchType::Local).unwrap();
+        let mut b1 = b1.rename("bar", false).unwrap();
+        assert_eq!(b1.name().unwrap(), Some("bar"));
+        assert!(b1.upstream().is_err());
+        b1.set_upstream(Some("main")).unwrap();
+        b1.upstream().unwrap();
+        b1.set_upstream(None).unwrap();
+
+        b1.delete().unwrap();
+    }
+
+    #[test]
+    fn name_is_valid() {
+        assert!(Branch::name_is_valid("foo").unwrap());
+        assert!(!Branch::name_is_valid("").unwrap());
+        assert!(!Branch::name_is_valid("with spaces").unwrap());
+        assert!(!Branch::name_is_valid("~tilde").unwrap());
+    }
+}
diff --git a/git2/src/buf.rs b/git2/src/buf.rs
new file mode 100644 (file)
index 0000000..fd2bcbf
--- /dev/null
@@ -0,0 +1,71 @@
+use std::ops::{Deref, DerefMut};
+use std::ptr;
+use std::slice;
+use std::str;
+
+use crate::raw;
+use crate::util::Binding;
+
+/// A structure to wrap an intermediate buffer used by libgit2.
+///
+/// A buffer can be thought of a `Vec<u8>`, but the `Vec` type is not used to
+/// avoid copying data back and forth.
+pub struct Buf {
+    raw: raw::git_buf,
+}
+
+impl Default for Buf {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Buf {
+    /// Creates a new empty buffer.
+    pub fn new() -> Buf {
+        crate::init();
+        unsafe {
+            Binding::from_raw(&mut raw::git_buf {
+                ptr: ptr::null_mut(),
+                size: 0,
+                reserved: 0,
+            } as *mut _)
+        }
+    }
+
+    /// Attempt to view this buffer as a string slice.
+    ///
+    /// Returns `None` if the buffer is not valid utf-8.
+    pub fn as_str(&self) -> Option<&str> {
+        str::from_utf8(&**self).ok()
+    }
+}
+
+impl Deref for Buf {
+    type Target = [u8];
+    fn deref(&self) -> &[u8] {
+        unsafe { slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.size as usize) }
+    }
+}
+
+impl DerefMut for Buf {
+    fn deref_mut(&mut self) -> &mut [u8] {
+        unsafe { slice::from_raw_parts_mut(self.raw.ptr as *mut u8, self.raw.size as usize) }
+    }
+}
+
+impl Binding for Buf {
+    type Raw = *mut raw::git_buf;
+    unsafe fn from_raw(raw: *mut raw::git_buf) -> Buf {
+        Buf { raw: *raw }
+    }
+    fn raw(&self) -> *mut raw::git_buf {
+        &self.raw as *const _ as *mut _
+    }
+}
+
+impl Drop for Buf {
+    fn drop(&mut self) {
+        unsafe { raw::git_buf_dispose(&mut self.raw) }
+    }
+}
diff --git a/git2/src/build.rs b/git2/src/build.rs
new file mode 100644 (file)
index 0000000..4ac6243
--- /dev/null
@@ -0,0 +1,872 @@
+//! Builder-pattern objects for configuration various git operations.
+
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::ffi::{CStr, CString};
+use std::mem;
+use std::path::Path;
+use std::ptr;
+
+use crate::util::{self, Binding};
+use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree};
+use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote};
+
+/// A builder struct which is used to build configuration for cloning a new git
+/// repository.
+///
+/// # Example
+///
+/// Cloning using SSH:
+///
+/// ```no_run
+/// use git2::{Cred, Error, RemoteCallbacks};
+/// use std::env;
+/// use std::path::Path;
+///
+///   // Prepare callbacks.
+///   let mut callbacks = RemoteCallbacks::new();
+///   callbacks.credentials(|_url, username_from_url, _allowed_types| {
+///     Cred::ssh_key(
+///       username_from_url.unwrap(),
+///       None,
+///       Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
+///       None,
+///     )
+///   });
+///
+///   // Prepare fetch options.
+///   let mut fo = git2::FetchOptions::new();
+///   fo.remote_callbacks(callbacks);
+///
+///   // Prepare builder.
+///   let mut builder = git2::build::RepoBuilder::new();
+///   builder.fetch_options(fo);
+///
+///   // Clone the project.
+///   builder.clone(
+///     "git@github.com:rust-lang/git2-rs.git",
+///     Path::new("/tmp/git2-rs"),
+///   );
+/// ```
+pub struct RepoBuilder<'cb> {
+    bare: bool,
+    branch: Option<CString>,
+    local: bool,
+    hardlinks: bool,
+    checkout: Option<CheckoutBuilder<'cb>>,
+    fetch_opts: Option<FetchOptions<'cb>>,
+    clone_local: Option<CloneLocal>,
+    remote_create: Option<Box<RemoteCreate<'cb>>>,
+}
+
+/// Type of callback passed to `RepoBuilder::remote_create`.
+///
+/// The second and third arguments are the remote's name and the remote's URL.
+pub type RemoteCreate<'cb> =
+    dyn for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb;
+
+/// A builder struct for git tree updates.
+///
+/// Paths passed to `remove` and `upsert` can be multi-component paths, i.e. they
+/// may contain slashes.
+///
+/// This is a higher-level tree update facility.  There is also [`TreeBuilder`]
+/// which is lower-level (and operates only on one level of the tree at a time).
+///
+/// [`TreeBuilder`]: crate::TreeBuilder
+pub struct TreeUpdateBuilder {
+    updates: Vec<raw::git_tree_update>,
+    paths: Vec<CString>,
+}
+
+/// A builder struct for configuring checkouts of a repository.
+pub struct CheckoutBuilder<'cb> {
+    their_label: Option<CString>,
+    our_label: Option<CString>,
+    ancestor_label: Option<CString>,
+    target_dir: Option<CString>,
+    paths: Vec<CString>,
+    path_ptrs: Vec<*const c_char>,
+    file_perm: Option<i32>,
+    dir_perm: Option<i32>,
+    disable_filters: bool,
+    checkout_opts: u32,
+    progress: Option<Box<Progress<'cb>>>,
+    notify: Option<Box<Notify<'cb>>>,
+    notify_flags: CheckoutNotificationType,
+}
+
+/// Checkout progress notification callback.
+///
+/// The first argument is the path for the notification, the next is the number
+/// of completed steps so far, and the final is the total number of steps.
+pub type Progress<'a> = dyn FnMut(Option<&Path>, usize, usize) + 'a;
+
+/// Checkout notifications callback.
+///
+/// The first argument is the notification type, the next is the path for the
+/// the notification, followed by the baseline diff, target diff, and workdir diff.
+///
+/// The callback must return a bool specifying whether the checkout should
+/// continue.
+pub type Notify<'a> = dyn FnMut(
+        CheckoutNotificationType,
+        Option<&Path>,
+        Option<DiffFile<'_>>,
+        Option<DiffFile<'_>>,
+        Option<DiffFile<'_>>,
+    ) -> bool
+    + 'a;
+
+impl<'cb> Default for RepoBuilder<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// Options that can be passed to `RepoBuilder::clone_local`.
+#[derive(Clone, Copy)]
+pub enum CloneLocal {
+    /// Auto-detect (default)
+    ///
+    /// Here libgit2 will bypass the git-aware transport for local paths, but
+    /// use a normal fetch for `file://` URLs.
+    Auto = raw::GIT_CLONE_LOCAL_AUTO as isize,
+
+    /// Bypass the git-aware transport even for `file://` URLs.
+    Local = raw::GIT_CLONE_LOCAL as isize,
+
+    /// Never bypass the git-aware transport
+    None = raw::GIT_CLONE_NO_LOCAL as isize,
+
+    /// Bypass the git-aware transport, but don't try to use hardlinks.
+    NoLinks = raw::GIT_CLONE_LOCAL_NO_LINKS as isize,
+
+    #[doc(hidden)]
+    __Nonexhaustive = 0xff,
+}
+
+impl<'cb> RepoBuilder<'cb> {
+    /// Creates a new repository builder with all of the default configuration.
+    ///
+    /// When ready, the `clone()` method can be used to clone a new repository
+    /// using this configuration.
+    pub fn new() -> RepoBuilder<'cb> {
+        crate::init();
+        RepoBuilder {
+            bare: false,
+            branch: None,
+            local: true,
+            clone_local: None,
+            hardlinks: true,
+            checkout: None,
+            fetch_opts: None,
+            remote_create: None,
+        }
+    }
+
+    /// Indicate whether the repository will be cloned as a bare repository or
+    /// not.
+    pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> {
+        self.bare = bare;
+        self
+    }
+
+    /// Specify the name of the branch to check out after the clone.
+    ///
+    /// If not specified, the remote's default branch will be used.
+    pub fn branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb> {
+        self.branch = Some(CString::new(branch).unwrap());
+        self
+    }
+
+    /// Configures options for bypassing the git-aware transport on clone.
+    ///
+    /// Bypassing it means that instead of a fetch libgit2 will copy the object
+    /// database directory instead of figuring out what it needs, which is
+    /// faster. If possible, it will hardlink the files to save space.
+    pub fn clone_local(&mut self, clone_local: CloneLocal) -> &mut RepoBuilder<'cb> {
+        self.clone_local = Some(clone_local);
+        self
+    }
+
+    /// Set the flag for bypassing the git aware transport mechanism for local
+    /// paths.
+    ///
+    /// If `true`, the git-aware transport will be bypassed for local paths. If
+    /// `false`, the git-aware transport will not be bypassed.
+    #[deprecated(note = "use `clone_local` instead")]
+    #[doc(hidden)]
+    pub fn local(&mut self, local: bool) -> &mut RepoBuilder<'cb> {
+        self.local = local;
+        self
+    }
+
+    /// Set the flag for whether hardlinks are used when using a local git-aware
+    /// transport mechanism.
+    #[deprecated(note = "use `clone_local` instead")]
+    #[doc(hidden)]
+    pub fn hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb> {
+        self.hardlinks = links;
+        self
+    }
+
+    /// Configure the checkout which will be performed by consuming a checkout
+    /// builder.
+    pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>) -> &mut RepoBuilder<'cb> {
+        self.checkout = Some(checkout);
+        self
+    }
+
+    /// Options which control the fetch, including callbacks.
+    ///
+    /// The callbacks are used for reporting fetch progress, and for acquiring
+    /// credentials in the event they are needed.
+    pub fn fetch_options(&mut self, fetch_opts: FetchOptions<'cb>) -> &mut RepoBuilder<'cb> {
+        self.fetch_opts = Some(fetch_opts);
+        self
+    }
+
+    /// Configures a callback used to create the git remote, prior to its being
+    /// used to perform the clone operation.
+    pub fn remote_create<F>(&mut self, f: F) -> &mut RepoBuilder<'cb>
+    where
+        F: for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb,
+    {
+        self.remote_create = Some(Box::new(f));
+        self
+    }
+
+    /// Clone a remote repository.
+    ///
+    /// This will use the options configured so far to clone the specified URL
+    /// into the specified local path.
+    pub fn clone(&mut self, url: &str, into: &Path) -> Result<Repository, Error> {
+        let mut opts: raw::git_clone_options = unsafe { mem::zeroed() };
+        unsafe {
+            try_call!(raw::git_clone_init_options(
+                &mut opts,
+                raw::GIT_CLONE_OPTIONS_VERSION
+            ));
+        }
+        opts.bare = self.bare as c_int;
+        opts.checkout_branch = self
+            .branch
+            .as_ref()
+            .map(|s| s.as_ptr())
+            .unwrap_or(ptr::null());
+
+        if let Some(ref local) = self.clone_local {
+            opts.local = *local as raw::git_clone_local_t;
+        } else {
+            opts.local = match (self.local, self.hardlinks) {
+                (true, false) => raw::GIT_CLONE_LOCAL_NO_LINKS,
+                (false, _) => raw::GIT_CLONE_NO_LOCAL,
+                (true, _) => raw::GIT_CLONE_LOCAL_AUTO,
+            };
+        }
+
+        if let Some(ref mut cbs) = self.fetch_opts {
+            opts.fetch_opts = cbs.raw();
+        }
+
+        if let Some(ref mut c) = self.checkout {
+            unsafe {
+                c.configure(&mut opts.checkout_opts);
+            }
+        }
+
+        if let Some(ref mut callback) = self.remote_create {
+            opts.remote_cb = Some(remote_create_cb);
+            opts.remote_cb_payload = callback as *mut _ as *mut _;
+        }
+
+        let url = CString::new(url)?;
+        // Normal file path OK (does not need Windows conversion).
+        let into = into.into_c_string()?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_clone(&mut raw, url, into, &opts));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+}
+
+extern "C" fn remote_create_cb(
+    out: *mut *mut raw::git_remote,
+    repo: *mut raw::git_repository,
+    name: *const c_char,
+    url: *const c_char,
+    payload: *mut c_void,
+) -> c_int {
+    unsafe {
+        let repo = Repository::from_raw(repo);
+        let code = panic::wrap(|| {
+            let name = CStr::from_ptr(name).to_str().unwrap();
+            let url = CStr::from_ptr(url).to_str().unwrap();
+            let f = payload as *mut Box<RemoteCreate<'_>>;
+            match (*f)(&repo, name, url) {
+                Ok(remote) => {
+                    *out = crate::remote::remote_into_raw(remote);
+                    0
+                }
+                Err(e) => e.raw_code(),
+            }
+        });
+        mem::forget(repo);
+        code.unwrap_or(-1)
+    }
+}
+
+impl<'cb> Default for CheckoutBuilder<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'cb> CheckoutBuilder<'cb> {
+    /// Creates a new builder for checkouts with all of its default
+    /// configuration.
+    pub fn new() -> CheckoutBuilder<'cb> {
+        crate::init();
+        CheckoutBuilder {
+            disable_filters: false,
+            dir_perm: None,
+            file_perm: None,
+            path_ptrs: Vec::new(),
+            paths: Vec::new(),
+            target_dir: None,
+            ancestor_label: None,
+            our_label: None,
+            their_label: None,
+            checkout_opts: raw::GIT_CHECKOUT_SAFE as u32,
+            progress: None,
+            notify: None,
+            notify_flags: CheckoutNotificationType::empty(),
+        }
+    }
+
+    /// Indicate that this checkout should perform a dry run by checking for
+    /// conflicts but not make any actual changes.
+    pub fn dry_run(&mut self) -> &mut CheckoutBuilder<'cb> {
+        self.checkout_opts &= !((1 << 4) - 1);
+        self.checkout_opts |= raw::GIT_CHECKOUT_NONE as u32;
+        self
+    }
+
+    /// Take any action necessary to get the working directory to match the
+    /// target including potentially discarding modified files.
+    pub fn force(&mut self) -> &mut CheckoutBuilder<'cb> {
+        self.checkout_opts &= !((1 << 4) - 1);
+        self.checkout_opts |= raw::GIT_CHECKOUT_FORCE as u32;
+        self
+    }
+
+    /// Indicate that the checkout should be performed safely, allowing new
+    /// files to be created but not overwriting existing files or changes.
+    ///
+    /// This is the default.
+    pub fn safe(&mut self) -> &mut CheckoutBuilder<'cb> {
+        self.checkout_opts &= !((1 << 4) - 1);
+        self.checkout_opts |= raw::GIT_CHECKOUT_SAFE as u32;
+        self
+    }
+
+    fn flag(&mut self, bit: raw::git_checkout_strategy_t, on: bool) -> &mut CheckoutBuilder<'cb> {
+        if on {
+            self.checkout_opts |= bit as u32;
+        } else {
+            self.checkout_opts &= !(bit as u32);
+        }
+        self
+    }
+
+    /// In safe mode, create files that don't exist.
+    ///
+    /// Defaults to false.
+    pub fn recreate_missing(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_RECREATE_MISSING, allow)
+    }
+
+    /// In safe mode, apply safe file updates even when there are conflicts
+    /// instead of canceling the checkout.
+    ///
+    /// Defaults to false.
+    pub fn allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_ALLOW_CONFLICTS, allow)
+    }
+
+    /// Remove untracked files from the working dir.
+    ///
+    /// Defaults to false.
+    pub fn remove_untracked(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_REMOVE_UNTRACKED, remove)
+    }
+
+    /// Remove ignored files from the working dir.
+    ///
+    /// Defaults to false.
+    pub fn remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_REMOVE_IGNORED, remove)
+    }
+
+    /// Only update the contents of files that already exist.
+    ///
+    /// If set, files will not be created or deleted.
+    ///
+    /// Defaults to false.
+    pub fn update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_UPDATE_ONLY, update)
+    }
+
+    /// Prevents checkout from writing the updated files' information to the
+    /// index.
+    ///
+    /// Defaults to true.
+    pub fn update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_DONT_UPDATE_INDEX, !update)
+    }
+
+    /// Indicate whether the index and git attributes should be refreshed from
+    /// disk before any operations.
+    ///
+    /// Defaults to true,
+    pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh)
+    }
+
+    /// Skip files with unmerged index entries.
+    ///
+    /// Defaults to false.
+    pub fn skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_SKIP_UNMERGED, skip)
+    }
+
+    /// Indicate whether the checkout should proceed on conflicts by using the
+    /// stage 2 version of the file ("ours").
+    ///
+    /// Defaults to false.
+    pub fn use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_USE_OURS, ours)
+    }
+
+    /// Indicate whether the checkout should proceed on conflicts by using the
+    /// stage 3 version of the file ("theirs").
+    ///
+    /// Defaults to false.
+    pub fn use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_USE_THEIRS, theirs)
+    }
+
+    /// Indicate whether ignored files should be overwritten during the checkout.
+    ///
+    /// Defaults to true.
+    pub fn overwrite_ignored(&mut self, overwrite: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, !overwrite)
+    }
+
+    /// Indicate whether a normal merge file should be written for conflicts.
+    ///
+    /// Defaults to false.
+    pub fn conflict_style_merge(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_MERGE, on)
+    }
+
+    /// Specify for which notification types to invoke the notification
+    /// callback.
+    ///
+    /// Defaults to none.
+    pub fn notify_on(
+        &mut self,
+        notification_types: CheckoutNotificationType,
+    ) -> &mut CheckoutBuilder<'cb> {
+        self.notify_flags = notification_types;
+        self
+    }
+
+    /// Indicates whether to include common ancestor data in diff3 format files
+    /// for conflicts.
+    ///
+    /// Defaults to false.
+    pub fn conflict_style_diff3(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, on)
+    }
+
+    /// Treat paths specified in [`CheckoutBuilder::path`] as exact file paths
+    /// instead of as pathspecs.
+    pub fn disable_pathspec_match(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> {
+        self.flag(raw::GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH, on)
+    }
+
+    /// Indicate whether to apply filters like CRLF conversion.
+    pub fn disable_filters(&mut self, disable: bool) -> &mut CheckoutBuilder<'cb> {
+        self.disable_filters = disable;
+        self
+    }
+
+    /// Set the mode with which new directories are created.
+    ///
+    /// Default is 0755
+    pub fn dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> {
+        self.dir_perm = Some(perm);
+        self
+    }
+
+    /// Set the mode with which new files are created.
+    ///
+    /// The default is 0644 or 0755 as dictated by the blob.
+    pub fn file_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> {
+        self.file_perm = Some(perm);
+        self
+    }
+
+    /// Add a path to be checked out.
+    ///
+    /// The path is a [pathspec] pattern, unless
+    /// [`CheckoutBuilder::disable_pathspec_match`] is set.
+    ///
+    /// If no paths are specified, then all files are checked out. Otherwise
+    /// only these specified paths are checked out.
+    ///
+    /// [pathspec]: https://git-scm.com/docs/gitglossary.html#Documentation/gitglossary.txt-aiddefpathspecapathspec
+    pub fn path<T: IntoCString>(&mut self, path: T) -> &mut CheckoutBuilder<'cb> {
+        let path = util::cstring_to_repo_path(path).unwrap();
+        self.path_ptrs.push(path.as_ptr());
+        self.paths.push(path);
+        self
+    }
+
+    /// Set the directory to check out to
+    pub fn target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb> {
+        // Normal file path OK (does not need Windows conversion).
+        self.target_dir = Some(dst.into_c_string().unwrap());
+        self
+    }
+
+    /// The name of the common ancestor side of conflicts
+    pub fn ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
+        self.ancestor_label = Some(CString::new(label).unwrap());
+        self
+    }
+
+    /// The name of the common our side of conflicts
+    pub fn our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
+        self.our_label = Some(CString::new(label).unwrap());
+        self
+    }
+
+    /// The name of the common their side of conflicts
+    pub fn their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
+        self.their_label = Some(CString::new(label).unwrap());
+        self
+    }
+
+    /// Set a callback to receive notifications of checkout progress.
+    pub fn progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb>
+    where
+        F: FnMut(Option<&Path>, usize, usize) + 'cb,
+    {
+        self.progress = Some(Box::new(cb) as Box<Progress<'cb>>);
+        self
+    }
+
+    /// Set a callback to receive checkout notifications.
+    ///
+    /// Callbacks are invoked prior to modifying any files on disk.
+    /// Returning `false` from the callback will cancel the checkout.
+    pub fn notify<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb>
+    where
+        F: FnMut(
+                CheckoutNotificationType,
+                Option<&Path>,
+                Option<DiffFile<'_>>,
+                Option<DiffFile<'_>>,
+                Option<DiffFile<'_>>,
+            ) -> bool
+            + 'cb,
+    {
+        self.notify = Some(Box::new(cb) as Box<Notify<'cb>>);
+        self
+    }
+
+    /// Configure a raw checkout options based on this configuration.
+    ///
+    /// This method is unsafe as there is no guarantee that this structure will
+    /// outlive the provided checkout options.
+    pub unsafe fn configure(&mut self, opts: &mut raw::git_checkout_options) {
+        opts.version = raw::GIT_CHECKOUT_OPTIONS_VERSION;
+        opts.disable_filters = self.disable_filters as c_int;
+        opts.dir_mode = self.dir_perm.unwrap_or(0) as c_uint;
+        opts.file_mode = self.file_perm.unwrap_or(0) as c_uint;
+
+        if !self.path_ptrs.is_empty() {
+            opts.paths.strings = self.path_ptrs.as_ptr() as *mut _;
+            opts.paths.count = self.path_ptrs.len() as size_t;
+        }
+
+        if let Some(ref c) = self.target_dir {
+            opts.target_directory = c.as_ptr();
+        }
+        if let Some(ref c) = self.ancestor_label {
+            opts.ancestor_label = c.as_ptr();
+        }
+        if let Some(ref c) = self.our_label {
+            opts.our_label = c.as_ptr();
+        }
+        if let Some(ref c) = self.their_label {
+            opts.their_label = c.as_ptr();
+        }
+        if self.progress.is_some() {
+            opts.progress_cb = Some(progress_cb);
+            opts.progress_payload = self as *mut _ as *mut _;
+        }
+        if self.notify.is_some() {
+            opts.notify_cb = Some(notify_cb);
+            opts.notify_payload = self as *mut _ as *mut _;
+            opts.notify_flags = self.notify_flags.bits() as c_uint;
+        }
+        opts.checkout_strategy = self.checkout_opts as c_uint;
+    }
+}
+
+extern "C" fn progress_cb(
+    path: *const c_char,
+    completed: size_t,
+    total: size_t,
+    data: *mut c_void,
+) {
+    panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut CheckoutBuilder<'_>);
+        let callback = match payload.progress {
+            Some(ref mut c) => c,
+            None => return,
+        };
+        let path = if path.is_null() {
+            None
+        } else {
+            Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
+        };
+        callback(path, completed as usize, total as usize)
+    });
+}
+
+extern "C" fn notify_cb(
+    why: raw::git_checkout_notify_t,
+    path: *const c_char,
+    baseline: *const raw::git_diff_file,
+    target: *const raw::git_diff_file,
+    workdir: *const raw::git_diff_file,
+    data: *mut c_void,
+) -> c_int {
+    // pack callback etc
+    panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut CheckoutBuilder<'_>);
+        let callback = match payload.notify {
+            Some(ref mut c) => c,
+            None => return 0,
+        };
+        let path = if path.is_null() {
+            None
+        } else {
+            Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
+        };
+
+        let baseline = if baseline.is_null() {
+            None
+        } else {
+            Some(DiffFile::from_raw(baseline))
+        };
+
+        let target = if target.is_null() {
+            None
+        } else {
+            Some(DiffFile::from_raw(target))
+        };
+
+        let workdir = if workdir.is_null() {
+            None
+        } else {
+            Some(DiffFile::from_raw(workdir))
+        };
+
+        let why = CheckoutNotificationType::from_bits_truncate(why as u32);
+        let keep_going = callback(why, path, baseline, target, workdir);
+        if keep_going {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(2)
+}
+
+unsafe impl Send for TreeUpdateBuilder {}
+
+impl Default for TreeUpdateBuilder {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl TreeUpdateBuilder {
+    /// Create a new empty series of updates.
+    pub fn new() -> Self {
+        Self {
+            updates: Vec::new(),
+            paths: Vec::new(),
+        }
+    }
+
+    /// Add an update removing the specified `path` from a tree.
+    pub fn remove<T: IntoCString>(&mut self, path: T) -> &mut Self {
+        let path = util::cstring_to_repo_path(path).unwrap();
+        let path_ptr = path.as_ptr();
+        self.paths.push(path);
+        self.updates.push(raw::git_tree_update {
+            action: raw::GIT_TREE_UPDATE_REMOVE,
+            id: raw::git_oid {
+                id: [0; raw::GIT_OID_RAWSZ],
+            },
+            filemode: raw::GIT_FILEMODE_UNREADABLE,
+            path: path_ptr,
+        });
+        self
+    }
+
+    /// Add an update setting the specified `path` to a specific Oid, whether it currently exists
+    /// or not.
+    ///
+    /// Note that libgit2 does not support an upsert of a previously removed path, or an upsert
+    /// that changes the type of an object (such as from tree to blob or vice versa).
+    pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: FileMode) -> &mut Self {
+        let path = util::cstring_to_repo_path(path).unwrap();
+        let path_ptr = path.as_ptr();
+        self.paths.push(path);
+        self.updates.push(raw::git_tree_update {
+            action: raw::GIT_TREE_UPDATE_UPSERT,
+            id: unsafe { *id.raw() },
+            filemode: u32::from(filemode) as raw::git_filemode_t,
+            path: path_ptr,
+        });
+        self
+    }
+
+    /// Create a new tree from the specified baseline and this series of updates.
+    ///
+    /// The baseline tree must exist in the specified repository.
+    pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> {
+        let mut ret = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_tree_create_updated(
+                &mut ret,
+                repo.raw(),
+                baseline.raw(),
+                self.updates.len(),
+                self.updates.as_ptr()
+            ));
+            Ok(Binding::from_raw(&ret as *const _))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder};
+    use crate::{CheckoutNotificationType, FileMode, Repository};
+    use std::fs;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    #[test]
+    fn smoke() {
+        let r = RepoBuilder::new().clone("/path/to/nowhere", Path::new("foo"));
+        assert!(r.is_err());
+    }
+
+    #[test]
+    fn smoke2() {
+        let td = TempDir::new().unwrap();
+        Repository::init_bare(&td.path().join("bare")).unwrap();
+        let url = if cfg!(unix) {
+            format!("file://{}/bare", td.path().display())
+        } else {
+            format!(
+                "file:///{}/bare",
+                td.path().display().to_string().replace("\\", "/")
+            )
+        };
+
+        let dst = td.path().join("foo");
+        RepoBuilder::new().clone(&url, &dst).unwrap();
+        fs::remove_dir_all(&dst).unwrap();
+        assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err());
+    }
+
+    #[test]
+    fn smoke_tree_create_updated() {
+        let (_tempdir, repo) = crate::test::repo_init();
+        let (_, tree_id) = crate::test::commit(&repo);
+        let tree = t!(repo.find_tree(tree_id));
+        assert!(tree.get_name("bar").is_none());
+        let foo_id = tree.get_name("foo").unwrap().id();
+        let tree2_id = t!(TreeUpdateBuilder::new()
+            .remove("foo")
+            .upsert("bar/baz", foo_id, FileMode::Blob)
+            .create_updated(&repo, &tree));
+        let tree2 = t!(repo.find_tree(tree2_id));
+        assert!(tree2.get_name("foo").is_none());
+        let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id();
+        assert_eq!(foo_id, baz_id);
+    }
+
+    /// Issue regression test #365
+    #[test]
+    fn notify_callback() {
+        let td = TempDir::new().unwrap();
+        let cd = TempDir::new().unwrap();
+
+        {
+            let mut opts = crate::RepositoryInitOptions::new();
+            opts.initial_head("main");
+            let repo = Repository::init_opts(&td.path(), &opts).unwrap();
+
+            let mut config = repo.config().unwrap();
+            config.set_str("user.name", "name").unwrap();
+            config.set_str("user.email", "email").unwrap();
+
+            let mut index = repo.index().unwrap();
+            let p = Path::new(td.path()).join("file");
+            println!("using path {:?}", p);
+            fs::File::create(&p).unwrap();
+            index.add_path(&Path::new("file")).unwrap();
+            let id = index.write_tree().unwrap();
+
+            let tree = repo.find_tree(id).unwrap();
+            let sig = repo.signature().unwrap();
+            repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
+                .unwrap();
+        }
+
+        let repo = Repository::open_bare(&td.path().join(".git")).unwrap();
+        let tree = repo
+            .revparse_single(&"main")
+            .unwrap()
+            .peel_to_tree()
+            .unwrap();
+        let mut index = repo.index().unwrap();
+        index.read_tree(&tree).unwrap();
+
+        let mut checkout_opts = CheckoutBuilder::new();
+        checkout_opts.target_dir(&cd.path());
+        checkout_opts.notify_on(CheckoutNotificationType::all());
+        checkout_opts.notify(|_notif, _path, baseline, target, workdir| {
+            assert!(baseline.is_none());
+            assert_eq!(target.unwrap().path(), Some(Path::new("file")));
+            assert!(workdir.is_none());
+            true
+        });
+        repo.checkout_index(Some(&mut index), Some(&mut checkout_opts))
+            .unwrap();
+    }
+}
diff --git a/git2/src/call.rs b/git2/src/call.rs
new file mode 100644 (file)
index 0000000..9aa3ae6
--- /dev/null
@@ -0,0 +1,242 @@
+#![macro_use]
+
+use crate::Error;
+
+macro_rules! call {
+    (raw::$p:ident ($($e:expr),*)) => (
+        raw::$p($(crate::call::convert(&$e)),*)
+    )
+}
+
+macro_rules! try_call {
+    (raw::$p:ident ($($e:expr),*)) => ({
+        match crate::call::c_try(raw::$p($(crate::call::convert(&$e)),*)) {
+            Ok(o) => o,
+            Err(e) => { crate::panic::check(); return Err(e) }
+        }
+    })
+}
+
+macro_rules! try_call_iter {
+    ($($f:tt)*) => {
+        match call!($($f)*) {
+            0 => {}
+            raw::GIT_ITEROVER => return None,
+            e => return Some(Err(crate::call::last_error(e)))
+        }
+    }
+}
+
+#[doc(hidden)]
+pub trait Convert<T> {
+    fn convert(&self) -> T;
+}
+
+pub fn convert<T, U: Convert<T>>(u: &U) -> T {
+    u.convert()
+}
+
+pub fn c_try(ret: libc::c_int) -> Result<libc::c_int, Error> {
+    match ret {
+        n if n < 0 => Err(last_error(n)),
+        n => Ok(n),
+    }
+}
+
+pub fn last_error(code: libc::c_int) -> Error {
+    Error::last_error(code)
+}
+
+mod impls {
+    use std::ffi::CString;
+    use std::ptr;
+
+    use crate::call::Convert;
+    use crate::{raw, BranchType, ConfigLevel, Direction, ObjectType, ResetType};
+    use crate::{
+        AutotagOption, DiffFormat, FetchPrune, FileFavor, SubmoduleIgnore, SubmoduleUpdate,
+    };
+
+    impl<T: Copy> Convert<T> for T {
+        fn convert(&self) -> T {
+            *self
+        }
+    }
+
+    impl Convert<libc::c_int> for bool {
+        fn convert(&self) -> libc::c_int {
+            *self as libc::c_int
+        }
+    }
+    impl<'a, T> Convert<*const T> for &'a T {
+        fn convert(&self) -> *const T {
+            *self as *const T
+        }
+    }
+    impl<'a, T> Convert<*mut T> for &'a mut T {
+        fn convert(&self) -> *mut T {
+            &**self as *const T as *mut T
+        }
+    }
+    impl<T> Convert<*const T> for *mut T {
+        fn convert(&self) -> *const T {
+            *self as *const T
+        }
+    }
+
+    impl Convert<*const libc::c_char> for CString {
+        fn convert(&self) -> *const libc::c_char {
+            self.as_ptr()
+        }
+    }
+
+    impl<T, U: Convert<*const T>> Convert<*const T> for Option<U> {
+        fn convert(&self) -> *const T {
+            self.as_ref().map(|s| s.convert()).unwrap_or(ptr::null())
+        }
+    }
+
+    impl<T, U: Convert<*mut T>> Convert<*mut T> for Option<U> {
+        fn convert(&self) -> *mut T {
+            self.as_ref()
+                .map(|s| s.convert())
+                .unwrap_or(ptr::null_mut())
+        }
+    }
+
+    impl Convert<raw::git_reset_t> for ResetType {
+        fn convert(&self) -> raw::git_reset_t {
+            match *self {
+                ResetType::Soft => raw::GIT_RESET_SOFT,
+                ResetType::Hard => raw::GIT_RESET_HARD,
+                ResetType::Mixed => raw::GIT_RESET_MIXED,
+            }
+        }
+    }
+
+    impl Convert<raw::git_direction> for Direction {
+        fn convert(&self) -> raw::git_direction {
+            match *self {
+                Direction::Push => raw::GIT_DIRECTION_PUSH,
+                Direction::Fetch => raw::GIT_DIRECTION_FETCH,
+            }
+        }
+    }
+
+    impl Convert<raw::git_object_t> for ObjectType {
+        fn convert(&self) -> raw::git_object_t {
+            match *self {
+                ObjectType::Any => raw::GIT_OBJECT_ANY,
+                ObjectType::Commit => raw::GIT_OBJECT_COMMIT,
+                ObjectType::Tree => raw::GIT_OBJECT_TREE,
+                ObjectType::Blob => raw::GIT_OBJECT_BLOB,
+                ObjectType::Tag => raw::GIT_OBJECT_TAG,
+            }
+        }
+    }
+
+    impl Convert<raw::git_object_t> for Option<ObjectType> {
+        fn convert(&self) -> raw::git_object_t {
+            self.unwrap_or(ObjectType::Any).convert()
+        }
+    }
+
+    impl Convert<raw::git_branch_t> for BranchType {
+        fn convert(&self) -> raw::git_branch_t {
+            match *self {
+                BranchType::Remote => raw::GIT_BRANCH_REMOTE,
+                BranchType::Local => raw::GIT_BRANCH_LOCAL,
+            }
+        }
+    }
+
+    impl Convert<raw::git_branch_t> for Option<BranchType> {
+        fn convert(&self) -> raw::git_branch_t {
+            self.map(|s| s.convert()).unwrap_or(raw::GIT_BRANCH_ALL)
+        }
+    }
+
+    impl Convert<raw::git_config_level_t> for ConfigLevel {
+        fn convert(&self) -> raw::git_config_level_t {
+            match *self {
+                ConfigLevel::ProgramData => raw::GIT_CONFIG_LEVEL_PROGRAMDATA,
+                ConfigLevel::System => raw::GIT_CONFIG_LEVEL_SYSTEM,
+                ConfigLevel::XDG => raw::GIT_CONFIG_LEVEL_XDG,
+                ConfigLevel::Global => raw::GIT_CONFIG_LEVEL_GLOBAL,
+                ConfigLevel::Local => raw::GIT_CONFIG_LEVEL_LOCAL,
+                ConfigLevel::Worktree => raw::GIT_CONFIG_LEVEL_WORKTREE,
+                ConfigLevel::App => raw::GIT_CONFIG_LEVEL_APP,
+                ConfigLevel::Highest => raw::GIT_CONFIG_HIGHEST_LEVEL,
+            }
+        }
+    }
+
+    impl Convert<raw::git_diff_format_t> for DiffFormat {
+        fn convert(&self) -> raw::git_diff_format_t {
+            match *self {
+                DiffFormat::Patch => raw::GIT_DIFF_FORMAT_PATCH,
+                DiffFormat::PatchHeader => raw::GIT_DIFF_FORMAT_PATCH_HEADER,
+                DiffFormat::Raw => raw::GIT_DIFF_FORMAT_RAW,
+                DiffFormat::NameOnly => raw::GIT_DIFF_FORMAT_NAME_ONLY,
+                DiffFormat::NameStatus => raw::GIT_DIFF_FORMAT_NAME_STATUS,
+                DiffFormat::PatchId => raw::GIT_DIFF_FORMAT_PATCH_ID,
+            }
+        }
+    }
+
+    impl Convert<raw::git_merge_file_favor_t> for FileFavor {
+        fn convert(&self) -> raw::git_merge_file_favor_t {
+            match *self {
+                FileFavor::Normal => raw::GIT_MERGE_FILE_FAVOR_NORMAL,
+                FileFavor::Ours => raw::GIT_MERGE_FILE_FAVOR_OURS,
+                FileFavor::Theirs => raw::GIT_MERGE_FILE_FAVOR_THEIRS,
+                FileFavor::Union => raw::GIT_MERGE_FILE_FAVOR_UNION,
+            }
+        }
+    }
+
+    impl Convert<raw::git_submodule_ignore_t> for SubmoduleIgnore {
+        fn convert(&self) -> raw::git_submodule_ignore_t {
+            match *self {
+                SubmoduleIgnore::Unspecified => raw::GIT_SUBMODULE_IGNORE_UNSPECIFIED,
+                SubmoduleIgnore::None => raw::GIT_SUBMODULE_IGNORE_NONE,
+                SubmoduleIgnore::Untracked => raw::GIT_SUBMODULE_IGNORE_UNTRACKED,
+                SubmoduleIgnore::Dirty => raw::GIT_SUBMODULE_IGNORE_DIRTY,
+                SubmoduleIgnore::All => raw::GIT_SUBMODULE_IGNORE_ALL,
+            }
+        }
+    }
+
+    impl Convert<raw::git_submodule_update_t> for SubmoduleUpdate {
+        fn convert(&self) -> raw::git_submodule_update_t {
+            match *self {
+                SubmoduleUpdate::Checkout => raw::GIT_SUBMODULE_UPDATE_CHECKOUT,
+                SubmoduleUpdate::Rebase => raw::GIT_SUBMODULE_UPDATE_REBASE,
+                SubmoduleUpdate::Merge => raw::GIT_SUBMODULE_UPDATE_MERGE,
+                SubmoduleUpdate::None => raw::GIT_SUBMODULE_UPDATE_NONE,
+                SubmoduleUpdate::Default => raw::GIT_SUBMODULE_UPDATE_DEFAULT,
+            }
+        }
+    }
+
+    impl Convert<raw::git_remote_autotag_option_t> for AutotagOption {
+        fn convert(&self) -> raw::git_remote_autotag_option_t {
+            match *self {
+                AutotagOption::Unspecified => raw::GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED,
+                AutotagOption::None => raw::GIT_REMOTE_DOWNLOAD_TAGS_NONE,
+                AutotagOption::Auto => raw::GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
+                AutotagOption::All => raw::GIT_REMOTE_DOWNLOAD_TAGS_ALL,
+            }
+        }
+    }
+
+    impl Convert<raw::git_fetch_prune_t> for FetchPrune {
+        fn convert(&self) -> raw::git_fetch_prune_t {
+            match *self {
+                FetchPrune::Unspecified => raw::GIT_FETCH_PRUNE_UNSPECIFIED,
+                FetchPrune::On => raw::GIT_FETCH_PRUNE,
+                FetchPrune::Off => raw::GIT_FETCH_NO_PRUNE,
+            }
+        }
+    }
+}
diff --git a/git2/src/cert.rs b/git2/src/cert.rs
new file mode 100644 (file)
index 0000000..b232cc3
--- /dev/null
@@ -0,0 +1,191 @@
+//! Certificate types which are passed to `CertificateCheck` in
+//! `RemoteCallbacks`.
+
+use std::marker;
+use std::mem;
+use std::slice;
+
+use crate::raw;
+use crate::util::Binding;
+
+/// A certificate for a remote connection, viewable as one of `CertHostkey` or
+/// `CertX509` currently.
+pub struct Cert<'a> {
+    raw: *mut raw::git_cert,
+    _marker: marker::PhantomData<&'a raw::git_cert>,
+}
+
+/// Hostkey information taken from libssh2
+pub struct CertHostkey<'a> {
+    raw: *mut raw::git_cert_hostkey,
+    _marker: marker::PhantomData<&'a raw::git_cert>,
+}
+
+/// X.509 certificate information
+pub struct CertX509<'a> {
+    raw: *mut raw::git_cert_x509,
+    _marker: marker::PhantomData<&'a raw::git_cert>,
+}
+
+/// The SSH host key type.
+#[derive(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub enum SshHostKeyType {
+    /// Unknown key type
+    Unknown = raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN as isize,
+    /// RSA key type
+    Rsa = raw::GIT_CERT_SSH_RAW_TYPE_RSA as isize,
+    /// DSS key type
+    Dss = raw::GIT_CERT_SSH_RAW_TYPE_DSS as isize,
+    /// ECDSA 256 key type
+    Ecdsa256 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 as isize,
+    /// ECDSA 384 key type
+    Ecdsa384 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 as isize,
+    /// ECDSA 521 key type
+    Ecdsa521 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 as isize,
+    /// ED25519 key type
+    Ed255219 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 as isize,
+}
+
+impl SshHostKeyType {
+    /// The name of the key type as encoded in the known_hosts file.
+    pub fn name(&self) -> &'static str {
+        match self {
+            SshHostKeyType::Unknown => "unknown",
+            SshHostKeyType::Rsa => "ssh-rsa",
+            SshHostKeyType::Dss => "ssh-dss",
+            SshHostKeyType::Ecdsa256 => "ecdsa-sha2-nistp256",
+            SshHostKeyType::Ecdsa384 => "ecdsa-sha2-nistp384",
+            SshHostKeyType::Ecdsa521 => "ecdsa-sha2-nistp521",
+            SshHostKeyType::Ed255219 => "ssh-ed25519",
+        }
+    }
+
+    /// A short name of the key type, the colloquial form used as a human-readable description.
+    pub fn short_name(&self) -> &'static str {
+        match self {
+            SshHostKeyType::Unknown => "Unknown",
+            SshHostKeyType::Rsa => "RSA",
+            SshHostKeyType::Dss => "DSA",
+            SshHostKeyType::Ecdsa256 => "ECDSA",
+            SshHostKeyType::Ecdsa384 => "ECDSA",
+            SshHostKeyType::Ecdsa521 => "ECDSA",
+            SshHostKeyType::Ed255219 => "ED25519",
+        }
+    }
+}
+
+impl<'a> Cert<'a> {
+    /// Attempt to view this certificate as an SSH hostkey.
+    ///
+    /// Returns `None` if this is not actually an SSH hostkey.
+    pub fn as_hostkey(&self) -> Option<&CertHostkey<'a>> {
+        self.cast(raw::GIT_CERT_HOSTKEY_LIBSSH2)
+    }
+
+    /// Attempt to view this certificate as an X.509 certificate.
+    ///
+    /// Returns `None` if this is not actually an X.509 certificate.
+    pub fn as_x509(&self) -> Option<&CertX509<'a>> {
+        self.cast(raw::GIT_CERT_X509)
+    }
+
+    fn cast<T>(&self, kind: raw::git_cert_t) -> Option<&T> {
+        assert_eq!(mem::size_of::<Cert<'a>>(), mem::size_of::<T>());
+        unsafe {
+            if kind == (*self.raw).cert_type {
+                Some(&*(self as *const Cert<'a> as *const T))
+            } else {
+                None
+            }
+        }
+    }
+}
+
+impl<'a> CertHostkey<'a> {
+    /// Returns the md5 hash of the hostkey, if available.
+    pub fn hash_md5(&self) -> Option<&[u8; 16]> {
+        unsafe {
+            if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_MD5 as u32 == 0 {
+                None
+            } else {
+                Some(&(*self.raw).hash_md5)
+            }
+        }
+    }
+
+    /// Returns the SHA-1 hash of the hostkey, if available.
+    pub fn hash_sha1(&self) -> Option<&[u8; 20]> {
+        unsafe {
+            if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA1 as u32 == 0 {
+                None
+            } else {
+                Some(&(*self.raw).hash_sha1)
+            }
+        }
+    }
+
+    /// Returns the SHA-256 hash of the hostkey, if available.
+    pub fn hash_sha256(&self) -> Option<&[u8; 32]> {
+        unsafe {
+            if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA256 as u32 == 0 {
+                None
+            } else {
+                Some(&(*self.raw).hash_sha256)
+            }
+        }
+    }
+
+    /// Returns the raw host key.
+    pub fn hostkey(&self) -> Option<&[u8]> {
+        unsafe {
+            if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
+                return None;
+            }
+            Some(slice::from_raw_parts(
+                (*self.raw).hostkey as *const u8,
+                (*self.raw).hostkey_len as usize,
+            ))
+        }
+    }
+
+    /// Returns the type of the host key.
+    pub fn hostkey_type(&self) -> Option<SshHostKeyType> {
+        unsafe {
+            if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 {
+                return None;
+            }
+            let t = match (*self.raw).raw_type {
+                raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown,
+                raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa,
+                raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss,
+                raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256,
+                raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384,
+                raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521,
+                raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219,
+                t => panic!("unexpected host key type {:?}", t),
+            };
+            Some(t)
+        }
+    }
+}
+
+impl<'a> CertX509<'a> {
+    /// Return the X.509 certificate data as a byte slice
+    pub fn data(&self) -> &[u8] {
+        unsafe { slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).len as usize) }
+    }
+}
+
+impl<'a> Binding for Cert<'a> {
+    type Raw = *mut raw::git_cert;
+    unsafe fn from_raw(raw: *mut raw::git_cert) -> Cert<'a> {
+        Cert {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_cert {
+        self.raw
+    }
+}
diff --git a/git2/src/cherrypick.rs b/git2/src/cherrypick.rs
new file mode 100644 (file)
index 0000000..659b730
--- /dev/null
@@ -0,0 +1,72 @@
+use std::mem;
+
+use crate::build::CheckoutBuilder;
+use crate::merge::MergeOptions;
+use crate::raw;
+use std::ptr;
+
+/// Options to specify when cherry picking
+pub struct CherrypickOptions<'cb> {
+    mainline: u32,
+    checkout_builder: Option<CheckoutBuilder<'cb>>,
+    merge_opts: Option<MergeOptions>,
+}
+
+impl<'cb> CherrypickOptions<'cb> {
+    /// Creates a default set of cherrypick options
+    pub fn new() -> CherrypickOptions<'cb> {
+        CherrypickOptions {
+            mainline: 0,
+            checkout_builder: None,
+            merge_opts: None,
+        }
+    }
+
+    /// Set the mainline value
+    ///
+    /// For merge commits, the "mainline" is treated as the parent.
+    pub fn mainline(&mut self, mainline: u32) -> &mut Self {
+        self.mainline = mainline;
+        self
+    }
+
+    /// Set the checkout builder
+    pub fn checkout_builder(&mut self, cb: CheckoutBuilder<'cb>) -> &mut Self {
+        self.checkout_builder = Some(cb);
+        self
+    }
+
+    /// Set the merge options
+    pub fn merge_opts(&mut self, merge_opts: MergeOptions) -> &mut Self {
+        self.merge_opts = Some(merge_opts);
+        self
+    }
+
+    /// Obtain the raw struct
+    pub fn raw(&mut self) -> raw::git_cherrypick_options {
+        unsafe {
+            let mut checkout_opts: raw::git_checkout_options = mem::zeroed();
+            raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION);
+            if let Some(ref mut cb) = self.checkout_builder {
+                cb.configure(&mut checkout_opts);
+            }
+
+            let mut merge_opts: raw::git_merge_options = mem::zeroed();
+            raw::git_merge_init_options(&mut merge_opts, raw::GIT_MERGE_OPTIONS_VERSION);
+            if let Some(ref opts) = self.merge_opts {
+                ptr::copy(opts.raw(), &mut merge_opts, 1);
+            }
+
+            let mut cherrypick_opts: raw::git_cherrypick_options = mem::zeroed();
+            raw::git_cherrypick_init_options(
+                &mut cherrypick_opts,
+                raw::GIT_CHERRYPICK_OPTIONS_VERSION,
+            );
+            cherrypick_opts.mainline = self.mainline;
+            cherrypick_opts.checkout_opts = checkout_opts;
+            cherrypick_opts.merge_opts = merge_opts;
+
+            cherrypick_opts
+        }
+    }
+}
diff --git a/git2/src/commit.rs b/git2/src/commit.rs
new file mode 100644 (file)
index 0000000..7fef508
--- /dev/null
@@ -0,0 +1,472 @@
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::ptr;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, signature, Buf, Error, IntoCString, Mailmap, Object, Oid, Signature, Time, Tree};
+
+/// A structure to represent a git [commit][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Commit<'repo> {
+    raw: *mut raw::git_commit,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+/// An iterator over the parent commits of a commit.
+///
+/// Aborts iteration when a commit cannot be found
+pub struct Parents<'commit, 'repo> {
+    range: Range<usize>,
+    commit: &'commit Commit<'repo>,
+}
+
+/// An iterator over the parent commits' ids of a commit.
+///
+/// Aborts iteration when a commit cannot be found
+pub struct ParentIds<'commit> {
+    range: Range<usize>,
+    commit: &'commit Commit<'commit>,
+}
+
+impl<'repo> Commit<'repo> {
+    /// Get the id (SHA1) of a repository commit
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_commit_id(&*self.raw)) }
+    }
+
+    /// Get the id of the tree pointed to by this commit.
+    ///
+    /// No attempts are made to fetch an object from the ODB.
+    pub fn tree_id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_commit_tree_id(&*self.raw)) }
+    }
+
+    /// Get the tree pointed to by a commit.
+    pub fn tree(&self) -> Result<Tree<'repo>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_commit_tree(&mut ret, &*self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get access to the underlying raw pointer.
+    pub fn raw(&self) -> *mut raw::git_commit {
+        self.raw
+    }
+
+    /// Get the full message of a commit.
+    ///
+    /// The returned message will be slightly prettified by removing any
+    /// potential leading newlines.
+    ///
+    /// `None` will be returned if the message is not valid utf-8
+    pub fn message(&self) -> Option<&str> {
+        str::from_utf8(self.message_bytes()).ok()
+    }
+
+    /// Get the full message of a commit as a byte slice.
+    ///
+    /// The returned message will be slightly prettified by removing any
+    /// potential leading newlines.
+    pub fn message_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_commit_message(&*self.raw)).unwrap() }
+    }
+
+    /// Get the encoding for the message of a commit, as a string representing a
+    /// standard encoding name.
+    ///
+    /// `None` will be returned if the encoding is not known
+    pub fn message_encoding(&self) -> Option<&str> {
+        let bytes = unsafe { crate::opt_bytes(self, raw::git_commit_message_encoding(&*self.raw)) };
+        bytes.and_then(|b| str::from_utf8(b).ok())
+    }
+
+    /// Get the full raw message of a commit.
+    ///
+    /// `None` will be returned if the message is not valid utf-8
+    pub fn message_raw(&self) -> Option<&str> {
+        str::from_utf8(self.message_raw_bytes()).ok()
+    }
+
+    /// Get the full raw message of a commit.
+    pub fn message_raw_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_commit_message_raw(&*self.raw)).unwrap() }
+    }
+
+    /// Get the full raw text of the commit header.
+    ///
+    /// `None` will be returned if the message is not valid utf-8
+    pub fn raw_header(&self) -> Option<&str> {
+        str::from_utf8(self.raw_header_bytes()).ok()
+    }
+
+    /// Get an arbitrary header field.
+    pub fn header_field_bytes<T: IntoCString>(&self, field: T) -> Result<Buf, Error> {
+        let buf = Buf::new();
+        let raw_field = field.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_commit_header_field(
+                buf.raw(),
+                &*self.raw,
+                raw_field
+            ));
+        }
+        Ok(buf)
+    }
+
+    /// Get the full raw text of the commit header.
+    pub fn raw_header_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap() }
+    }
+
+    /// Get the short "summary" of the git commit message.
+    ///
+    /// The returned message is the summary of the commit, comprising the first
+    /// paragraph of the message with whitespace trimmed and squashed.
+    ///
+    /// `None` may be returned if an error occurs or if the summary is not valid
+    /// utf-8.
+    pub fn summary(&self) -> Option<&str> {
+        self.summary_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the short "summary" of the git commit message.
+    ///
+    /// The returned message is the summary of the commit, comprising the first
+    /// paragraph of the message with whitespace trimmed and squashed.
+    ///
+    /// `None` may be returned if an error occurs
+    pub fn summary_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_commit_summary(self.raw)) }
+    }
+
+    /// Get the long "body" of the git commit message.
+    ///
+    /// The returned message is the body of the commit, comprising everything
+    /// but the first paragraph of the message. Leading and trailing whitespaces
+    /// are trimmed.
+    ///
+    /// `None` may be returned if an error occurs or if the summary is not valid
+    /// utf-8.
+    pub fn body(&self) -> Option<&str> {
+        self.body_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the long "body" of the git commit message.
+    ///
+    /// The returned message is the body of the commit, comprising everything
+    /// but the first paragraph of the message. Leading and trailing whitespaces
+    /// are trimmed.
+    ///
+    /// `None` may be returned if an error occurs.
+    pub fn body_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_commit_body(self.raw)) }
+    }
+
+    /// Get the commit time (i.e. committer time) of a commit.
+    ///
+    /// The first element of the tuple is the time, in seconds, since the epoch.
+    /// The second element is the offset, in minutes, of the time zone of the
+    /// committer's preferred time zone.
+    pub fn time(&self) -> Time {
+        unsafe {
+            Time::new(
+                raw::git_commit_time(&*self.raw) as i64,
+                raw::git_commit_time_offset(&*self.raw) as i32,
+            )
+        }
+    }
+
+    /// Creates a new iterator over the parents of this commit.
+    pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> {
+        Parents {
+            range: 0..self.parent_count(),
+            commit: self,
+        }
+    }
+
+    /// Creates a new iterator over the parents of this commit.
+    pub fn parent_ids(&self) -> ParentIds<'_> {
+        ParentIds {
+            range: 0..self.parent_count(),
+            commit: self,
+        }
+    }
+
+    /// Get the author of this commit.
+    pub fn author(&self) -> Signature<'_> {
+        unsafe {
+            let ptr = raw::git_commit_author(&*self.raw);
+            signature::from_raw_const(self, ptr)
+        }
+    }
+
+    /// Get the author of this commit, using the mailmap to map names and email
+    /// addresses to canonical real names and email addresses.
+    pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_commit_author_with_mailmap(
+                &mut ret,
+                &*self.raw,
+                &*mailmap.raw()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the committer of this commit.
+    pub fn committer(&self) -> Signature<'_> {
+        unsafe {
+            let ptr = raw::git_commit_committer(&*self.raw);
+            signature::from_raw_const(self, ptr)
+        }
+    }
+
+    /// Get the committer of this commit, using the mailmap to map names and email
+    /// addresses to canonical real names and email addresses.
+    pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_commit_committer_with_mailmap(
+                &mut ret,
+                &*self.raw,
+                &*mailmap.raw()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Amend this existing commit with all non-`None` values
+    ///
+    /// This creates a new commit that is exactly the same as the old commit,
+    /// except that any non-`None` values will be updated. The new commit has
+    /// the same parents as the old commit.
+    ///
+    /// For information about `update_ref`, see [`Repository::commit`].
+    ///
+    /// [`Repository::commit`]: struct.Repository.html#method.commit
+    pub fn amend(
+        &self,
+        update_ref: Option<&str>,
+        author: Option<&Signature<'_>>,
+        committer: Option<&Signature<'_>>,
+        message_encoding: Option<&str>,
+        message: Option<&str>,
+        tree: Option<&Tree<'repo>>,
+    ) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        let update_ref = crate::opt_cstr(update_ref)?;
+        let encoding = crate::opt_cstr(message_encoding)?;
+        let message = crate::opt_cstr(message)?;
+        unsafe {
+            try_call!(raw::git_commit_amend(
+                &mut raw,
+                self.raw(),
+                update_ref,
+                author.map(|s| s.raw()),
+                committer.map(|s| s.raw()),
+                encoding,
+                message,
+                tree.map(|t| t.raw())
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Get the number of parents of this commit.
+    ///
+    /// Use the `parents` iterator to return an iterator over all parents.
+    pub fn parent_count(&self) -> usize {
+        unsafe { raw::git_commit_parentcount(&*self.raw) as usize }
+    }
+
+    /// Get the specified parent of the commit.
+    ///
+    /// Use the `parents` iterator to return an iterator over all parents.
+    pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> {
+        unsafe {
+            let mut raw = ptr::null_mut();
+            try_call!(raw::git_commit_parent(
+                &mut raw,
+                &*self.raw,
+                i as libc::c_uint
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get the specified parent id of the commit.
+    ///
+    /// This is different from `parent`, which will attempt to load the
+    /// parent commit from the ODB.
+    ///
+    /// Use the `parent_ids` iterator to return an iterator over all parents.
+    pub fn parent_id(&self, i: usize) -> Result<Oid, Error> {
+        unsafe {
+            let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint);
+            if id.is_null() {
+                Err(Error::from_str("parent index out of bounds"))
+            } else {
+                Ok(Binding::from_raw(id))
+            }
+        }
+    }
+
+    /// Casts this Commit to be usable as an `Object`
+    pub fn as_object(&self) -> &Object<'repo> {
+        unsafe { &*(self as *const _ as *const Object<'repo>) }
+    }
+
+    /// Consumes Commit to be returned as an `Object`
+    pub fn into_object(self) -> Object<'repo> {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
+        unsafe { mem::transmute(self) }
+    }
+}
+
+impl<'repo> Binding for Commit<'repo> {
+    type Raw = *mut raw::git_commit;
+    unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> {
+        Commit {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_commit {
+        self.raw
+    }
+}
+
+impl<'repo> std::fmt::Debug for Commit<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("Commit");
+        ds.field("id", &self.id());
+        if let Some(summary) = self.summary() {
+            ds.field("summary", &summary);
+        }
+        ds.finish()
+    }
+}
+
+/// Aborts iteration when a commit cannot be found
+impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> {
+    type Item = Commit<'repo>;
+    fn next(&mut self) -> Option<Commit<'repo>> {
+        self.range.next().and_then(|i| self.commit.parent(i).ok())
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+
+/// Aborts iteration when a commit cannot be found
+impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> {
+    fn next_back(&mut self) -> Option<Commit<'repo>> {
+        self.range
+            .next_back()
+            .and_then(|i| self.commit.parent(i).ok())
+    }
+}
+
+impl<'repo, 'commit> FusedIterator for Parents<'commit, 'repo> {}
+
+impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {}
+
+/// Aborts iteration when a commit cannot be found
+impl<'commit> Iterator for ParentIds<'commit> {
+    type Item = Oid;
+    fn next(&mut self) -> Option<Oid> {
+        self.range
+            .next()
+            .and_then(|i| self.commit.parent_id(i).ok())
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+
+/// Aborts iteration when a commit cannot be found
+impl<'commit> DoubleEndedIterator for ParentIds<'commit> {
+    fn next_back(&mut self) -> Option<Oid> {
+        self.range
+            .next_back()
+            .and_then(|i| self.commit.parent_id(i).ok())
+    }
+}
+
+impl<'commit> FusedIterator for ParentIds<'commit> {}
+
+impl<'commit> ExactSizeIterator for ParentIds<'commit> {}
+
+impl<'repo> Clone for Commit<'repo> {
+    fn clone(&self) -> Self {
+        self.as_object().clone().into_commit().ok().unwrap()
+    }
+}
+
+impl<'repo> Drop for Commit<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_commit_free(self.raw) }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+        assert_eq!(commit.message(), Some("initial\n\nbody"));
+        assert_eq!(commit.body(), Some("body"));
+        assert_eq!(commit.id(), target);
+        commit.message_raw().unwrap();
+        commit.raw_header().unwrap();
+        commit.message_encoding();
+        commit.summary().unwrap();
+        commit.body().unwrap();
+        commit.tree_id();
+        commit.tree().unwrap();
+        assert_eq!(commit.parents().count(), 0);
+
+        let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
+        assert_eq!(
+            crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(),
+            commit.tree_id()
+        );
+        assert_eq!(commit.author().name(), Some("name"));
+        assert_eq!(commit.author().email(), Some("email"));
+        assert_eq!(commit.committer().name(), Some("name"));
+        assert_eq!(commit.committer().email(), Some("email"));
+
+        let sig = repo.signature().unwrap();
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+        let id = repo
+            .commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
+            .unwrap();
+        let head = repo.find_commit(id).unwrap();
+
+        let new_head = head
+            .amend(Some("HEAD"), None, None, None, Some("new message"), None)
+            .unwrap();
+        let new_head = repo.find_commit(new_head).unwrap();
+        assert_eq!(new_head.message(), Some("new message"));
+        new_head.into_object();
+
+        repo.find_object(target, None).unwrap().as_commit().unwrap();
+        repo.find_object(target, None)
+            .unwrap()
+            .into_commit()
+            .ok()
+            .unwrap();
+    }
+}
diff --git a/git2/src/config.rs b/git2/src/config.rs
new file mode 100644 (file)
index 0000000..9ba0a6d
--- /dev/null
@@ -0,0 +1,782 @@
+use std::ffi::CString;
+use std::marker;
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::str;
+
+use crate::util::{self, Binding};
+use crate::{raw, Buf, ConfigLevel, Error, IntoCString};
+
+/// A structure representing a git configuration key/value store
+pub struct Config {
+    raw: *mut raw::git_config,
+}
+
+/// A struct representing a certain entry owned by a `Config` instance.
+///
+/// An entry has a name, a value, and a level it applies to.
+pub struct ConfigEntry<'cfg> {
+    raw: *mut raw::git_config_entry,
+    _marker: marker::PhantomData<&'cfg Config>,
+    owned: bool,
+}
+
+/// An iterator over the `ConfigEntry` values of a `Config` structure.
+///
+/// Due to lifetime restrictions, `ConfigEntries` does not implement the
+/// standard [`Iterator`] trait. It provides a [`next`] function which only
+/// allows access to one entry at a time. [`for_each`] is available as a
+/// convenience function.
+///
+/// [`next`]: ConfigEntries::next
+/// [`for_each`]: ConfigEntries::for_each
+///
+/// # Example
+///
+/// ```
+/// // Example of how to collect all entries.
+/// use git2::Config;
+///
+/// let config = Config::new()?;
+/// let iter = config.entries(None)?;
+/// let mut entries = Vec::new();
+/// iter
+///     .for_each(|entry| {
+///         let name = entry.name().unwrap().to_string();
+///         let value = entry.value().unwrap_or("").to_string();
+///         entries.push((name, value))
+///     })?;
+/// for entry in &entries {
+///     println!("{} = {}", entry.0, entry.1);
+/// }
+/// # Ok::<(), git2::Error>(())
+///
+/// ```
+pub struct ConfigEntries<'cfg> {
+    raw: *mut raw::git_config_iterator,
+    current: Option<ConfigEntry<'cfg>>,
+    _marker: marker::PhantomData<&'cfg Config>,
+}
+
+impl Config {
+    /// Allocate a new configuration object
+    ///
+    /// This object is empty, so you have to add a file to it before you can do
+    /// anything with it.
+    pub fn new() -> Result<Config, Error> {
+        crate::init();
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_config_new(&mut raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new config instance containing a single on-disk file
+    pub fn open(path: &Path) -> Result<Config, Error> {
+        crate::init();
+        let mut raw = ptr::null_mut();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_config_open_ondisk(&mut raw, path));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Open the global, XDG and system configuration files
+    ///
+    /// Utility wrapper that finds the global, XDG and system configuration
+    /// files and opens them into a single prioritized config object that can
+    /// be used when accessing default config data outside a repository.
+    pub fn open_default() -> Result<Config, Error> {
+        crate::init();
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_config_open_default(&mut raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Locate the path to the global configuration file
+    ///
+    /// The user or global configuration file is usually located in
+    /// `$HOME/.gitconfig`.
+    ///
+    /// This method will try to guess the full path to that file, if the file
+    /// exists. The returned path may be used on any method call to load
+    /// the global configuration file.
+    ///
+    /// This method will not guess the path to the XDG compatible config file
+    /// (`.config/git/config`).
+    pub fn find_global() -> Result<PathBuf, Error> {
+        crate::init();
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_config_find_global(buf.raw()));
+        }
+        Ok(util::bytes2path(&buf).to_path_buf())
+    }
+
+    /// Locate the path to the system configuration file
+    ///
+    /// If /etc/gitconfig doesn't exist, it will look for `%PROGRAMFILES%`
+    pub fn find_system() -> Result<PathBuf, Error> {
+        crate::init();
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_config_find_system(buf.raw()));
+        }
+        Ok(util::bytes2path(&buf).to_path_buf())
+    }
+
+    /// Locate the path to the global XDG compatible configuration file
+    ///
+    /// The XDG compatible configuration file is usually located in
+    /// `$HOME/.config/git/config`.
+    pub fn find_xdg() -> Result<PathBuf, Error> {
+        crate::init();
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_config_find_xdg(buf.raw()));
+        }
+        Ok(util::bytes2path(&buf).to_path_buf())
+    }
+
+    /// Add an on-disk config file instance to an existing config
+    ///
+    /// The on-disk file pointed at by path will be opened and parsed; it's
+    /// expected to be a native Git config file following the default Git config
+    /// syntax (see man git-config).
+    ///
+    /// Further queries on this config object will access each of the config
+    /// file instances in order (instances with a higher priority level will be
+    /// accessed first).
+    pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> {
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_config_add_file_ondisk(
+                self.raw,
+                path,
+                level,
+                ptr::null(),
+                force
+            ));
+            Ok(())
+        }
+    }
+
+    /// Delete a config variable from the config file with the highest level
+    /// (usually the local one).
+    pub fn remove(&mut self, name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_delete_entry(self.raw, name));
+            Ok(())
+        }
+    }
+
+    /// Remove multivar config variables in the config file with the highest level (usually the
+    /// local one).
+    ///
+    /// The regular expression is applied case-sensitively on the value.
+    pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let regexp = CString::new(regexp)?;
+        unsafe {
+            try_call!(raw::git_config_delete_multivar(self.raw, name, regexp));
+        }
+        Ok(())
+    }
+
+    /// Get the value of a boolean config variable.
+    ///
+    /// All config files will be looked into, in the order of their defined
+    /// level. A higher level means a higher priority. The first occurrence of
+    /// the variable will be returned here.
+    pub fn get_bool(&self, name: &str) -> Result<bool, Error> {
+        let mut out = 0 as libc::c_int;
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name));
+        }
+        Ok(out != 0)
+    }
+
+    /// Get the value of an integer config variable.
+    ///
+    /// All config files will be looked into, in the order of their defined
+    /// level. A higher level means a higher priority. The first occurrence of
+    /// the variable will be returned here.
+    pub fn get_i32(&self, name: &str) -> Result<i32, Error> {
+        let mut out = 0i32;
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name));
+        }
+        Ok(out)
+    }
+
+    /// Get the value of an integer config variable.
+    ///
+    /// All config files will be looked into, in the order of their defined
+    /// level. A higher level means a higher priority. The first occurrence of
+    /// the variable will be returned here.
+    pub fn get_i64(&self, name: &str) -> Result<i64, Error> {
+        let mut out = 0i64;
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name));
+        }
+        Ok(out)
+    }
+
+    /// Get the value of a string config variable.
+    ///
+    /// This is the same as `get_bytes` except that it may return `Err` if
+    /// the bytes are not valid utf-8.
+    ///
+    /// For consistency reasons, this method can only be called on a [`snapshot`].
+    /// An error will be returned otherwise.
+    ///
+    /// [`snapshot`]: `crate::Config::snapshot`
+    pub fn get_str(&self, name: &str) -> Result<&str, Error> {
+        str::from_utf8(self.get_bytes(name)?)
+            .map_err(|_| Error::from_str("configuration value is not valid utf8"))
+    }
+
+    /// Get the value of a string config variable as a byte slice.
+    ///
+    /// For consistency reasons, this method can only be called on a [`snapshot`].
+    /// An error will be returned otherwise.
+    ///
+    /// [`snapshot`]: `crate::Config::snapshot`
+    pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> {
+        let mut ret = ptr::null();
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name));
+            Ok(crate::opt_bytes(self, ret).unwrap())
+        }
+    }
+
+    /// Get the value of a string config variable as an owned string.
+    ///
+    /// All config files will be looked into, in the order of their
+    /// defined level. A higher level means a higher priority. The
+    /// first occurrence of the variable will be returned here.
+    ///
+    /// An error will be returned if the config value is not valid utf-8.
+    pub fn get_string(&self, name: &str) -> Result<String, Error> {
+        let ret = Buf::new();
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name));
+        }
+        str::from_utf8(&ret)
+            .map(|s| s.to_string())
+            .map_err(|_| Error::from_str("configuration value is not valid utf8"))
+    }
+
+    /// Get the value of a path config variable as an owned `PathBuf`.
+    ///
+    /// A leading '~' will be expanded to the global search path (which
+    /// defaults to the user's home directory but can be overridden via
+    /// [`raw::git_libgit2_opts`].
+    ///
+    /// All config files will be looked into, in the order of their
+    /// defined level. A higher level means a higher priority. The
+    /// first occurrence of the variable will be returned here.
+    pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> {
+        let ret = Buf::new();
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_path(ret.raw(), self.raw, name));
+        }
+        Ok(crate::util::bytes2path(&ret).to_path_buf())
+    }
+
+    /// Get the ConfigEntry for a config variable.
+    pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_get_entry(&mut ret, self.raw, name));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Iterate over all the config variables
+    ///
+    /// If `glob` is `Some`, then the iterator will only iterate over all
+    /// variables whose name matches the pattern.
+    ///
+    /// The regular expression is applied case-sensitively on the normalized form of
+    /// the variable name: the section and variable parts are lower-cased. The
+    /// subsection is left unchanged.
+    ///
+    /// Due to lifetime restrictions, the returned value does not implement
+    /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use git2::Config;
+    ///
+    /// let cfg = Config::new().unwrap();
+    ///
+    /// let mut entries = cfg.entries(None).unwrap();
+    /// while let Some(entry) = entries.next() {
+    ///     let entry = entry.unwrap();
+    ///     println!("{} => {}", entry.name().unwrap(), entry.value().unwrap());
+    /// }
+    /// ```
+    pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            match glob {
+                Some(s) => {
+                    let s = CString::new(s)?;
+                    try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s));
+                }
+                None => {
+                    try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw));
+                }
+            }
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Iterate over the values of a multivar
+    ///
+    /// If `regexp` is `Some`, then the iterator will only iterate over all
+    /// values which match the pattern.
+    ///
+    /// The regular expression is applied case-sensitively on the normalized form of
+    /// the variable name: the section and variable parts are lower-cased. The
+    /// subsection is left unchanged.
+    ///
+    /// Due to lifetime restrictions, the returned value does not implement
+    /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more.
+    pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        let regexp = regexp.map(CString::new).transpose()?;
+        unsafe {
+            try_call!(raw::git_config_multivar_iterator_new(
+                &mut ret, &*self.raw, name, regexp
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Open the global/XDG configuration file according to git's rules
+    ///
+    /// Git allows you to store your global configuration at `$HOME/.config` or
+    /// `$XDG_CONFIG_HOME/git/config`. For backwards compatibility, the XDG file
+    /// shouldn't be used unless the use has created it explicitly. With this
+    /// function you'll open the correct one to write to.
+    pub fn open_global(&mut self) -> Result<Config, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_config_open_global(&mut raw, self.raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Build a single-level focused config object from a multi-level one.
+    ///
+    /// The returned config object can be used to perform get/set/delete
+    /// operations on a single specific level.
+    pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Set the value of a boolean config variable in the config file with the
+    /// highest level (usually the local one).
+    pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_set_bool(self.raw, name, value));
+        }
+        Ok(())
+    }
+
+    /// Set the value of an integer config variable in the config file with the
+    /// highest level (usually the local one).
+    pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_set_int32(self.raw, name, value));
+        }
+        Ok(())
+    }
+
+    /// Set the value of an integer config variable in the config file with the
+    /// highest level (usually the local one).
+    pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_config_set_int64(self.raw, name, value));
+        }
+        Ok(())
+    }
+
+    /// Set the value of an multivar config variable in the config file with the
+    /// highest level (usually the local one).
+    ///
+    /// The regular expression is applied case-sensitively on the value.
+    pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let regexp = CString::new(regexp)?;
+        let value = CString::new(value)?;
+        unsafe {
+            try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value));
+        }
+        Ok(())
+    }
+
+    /// Set the value of a string config variable in the config file with the
+    /// highest level (usually the local one).
+    pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let value = CString::new(value)?;
+        unsafe {
+            try_call!(raw::git_config_set_string(self.raw, name, value));
+        }
+        Ok(())
+    }
+
+    /// Create a snapshot of the configuration
+    ///
+    /// Create a snapshot of the current state of a configuration, which allows
+    /// you to look into a consistent view of the configuration for looking up
+    /// complex values (e.g. a remote, submodule).
+    pub fn snapshot(&mut self) -> Result<Config, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_config_snapshot(&mut ret, self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Parse a string as a bool.
+    ///
+    /// Interprets "true", "yes", "on", 1, or any non-zero number as true.
+    /// Interprets "false", "no", "off", 0, or an empty string as false.
+    pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> {
+        let s = s.into_c_string()?;
+        let mut out = 0;
+        crate::init();
+        unsafe {
+            try_call!(raw::git_config_parse_bool(&mut out, s));
+        }
+        Ok(out != 0)
+    }
+
+    /// Parse a string as an i32; handles suffixes like k, M, or G, and
+    /// multiplies by the appropriate power of 1024.
+    pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> {
+        let s = s.into_c_string()?;
+        let mut out = 0;
+        crate::init();
+        unsafe {
+            try_call!(raw::git_config_parse_int32(&mut out, s));
+        }
+        Ok(out)
+    }
+
+    /// Parse a string as an i64; handles suffixes like k, M, or G, and
+    /// multiplies by the appropriate power of 1024.
+    pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> {
+        let s = s.into_c_string()?;
+        let mut out = 0;
+        crate::init();
+        unsafe {
+            try_call!(raw::git_config_parse_int64(&mut out, s));
+        }
+        Ok(out)
+    }
+}
+
+impl Binding for Config {
+    type Raw = *mut raw::git_config;
+    unsafe fn from_raw(raw: *mut raw::git_config) -> Config {
+        Config { raw }
+    }
+    fn raw(&self) -> *mut raw::git_config {
+        self.raw
+    }
+}
+
+impl Drop for Config {
+    fn drop(&mut self) {
+        unsafe { raw::git_config_free(self.raw) }
+    }
+}
+
+impl<'cfg> ConfigEntry<'cfg> {
+    /// Gets the name of this entry.
+    ///
+    /// May return `None` if the name is not valid utf-8
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Gets the name of this entry as a byte slice.
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
+    }
+
+    /// Gets the value of this entry.
+    ///
+    /// May return `None` if the value is not valid utf-8
+    ///
+    /// # Panics
+    ///
+    /// Panics when no value is defined.
+    pub fn value(&self) -> Option<&str> {
+        str::from_utf8(self.value_bytes()).ok()
+    }
+
+    /// Gets the value of this entry as a byte slice.
+    ///
+    /// # Panics
+    ///
+    /// Panics when no value is defined.
+    pub fn value_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() }
+    }
+
+    /// Returns `true` when a value is defined otherwise `false`.
+    ///
+    /// No value defined is a short-hand to represent a Boolean `true`.
+    pub fn has_value(&self) -> bool {
+        unsafe { !(*self.raw).value.is_null() }
+    }
+
+    /// Gets the configuration level of this entry.
+    pub fn level(&self) -> ConfigLevel {
+        unsafe { ConfigLevel::from_raw((*self.raw).level) }
+    }
+
+    /// Depth of includes where this variable was found
+    pub fn include_depth(&self) -> u32 {
+        unsafe { (*self.raw).include_depth as u32 }
+    }
+}
+
+impl<'cfg> Binding for ConfigEntry<'cfg> {
+    type Raw = *mut raw::git_config_entry;
+
+    unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> {
+        ConfigEntry {
+            raw,
+            _marker: marker::PhantomData,
+            owned: true,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_config_entry {
+        self.raw
+    }
+}
+
+impl<'cfg> Binding for ConfigEntries<'cfg> {
+    type Raw = *mut raw::git_config_iterator;
+
+    unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> {
+        ConfigEntries {
+            raw,
+            current: None,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_config_iterator {
+        self.raw
+    }
+}
+
+impl<'cfg> ConfigEntries<'cfg> {
+    /// Advances the iterator and returns the next value.
+    ///
+    /// Returns `None` when iteration is finished.
+    pub fn next(&mut self) -> Option<Result<&ConfigEntry<'cfg>, Error>> {
+        let mut raw = ptr::null_mut();
+        drop(self.current.take());
+        unsafe {
+            try_call_iter!(raw::git_config_next(&mut raw, self.raw));
+            let entry = ConfigEntry {
+                owned: false,
+                raw,
+                _marker: marker::PhantomData,
+            };
+            self.current = Some(entry);
+            Some(Ok(self.current.as_ref().unwrap()))
+        }
+    }
+
+    /// Calls the given closure for each remaining entry in the iterator.
+    pub fn for_each<F: FnMut(&ConfigEntry<'cfg>)>(mut self, mut f: F) -> Result<(), Error> {
+        while let Some(entry) = self.next() {
+            let entry = entry?;
+            f(entry);
+        }
+        Ok(())
+    }
+}
+
+impl<'cfg> Drop for ConfigEntries<'cfg> {
+    fn drop(&mut self) {
+        unsafe { raw::git_config_iterator_free(self.raw) }
+    }
+}
+
+impl<'cfg> Drop for ConfigEntry<'cfg> {
+    fn drop(&mut self) {
+        if self.owned {
+            unsafe { raw::git_config_entry_free(self.raw) }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::fs::File;
+    use tempfile::TempDir;
+
+    use crate::Config;
+
+    #[test]
+    fn smoke() {
+        let _cfg = Config::new().unwrap();
+        let _ = Config::find_global();
+        let _ = Config::find_system();
+        let _ = Config::find_xdg();
+    }
+
+    #[test]
+    fn persisted() {
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("foo");
+        File::create(&path).unwrap();
+
+        let mut cfg = Config::open(&path).unwrap();
+        assert!(cfg.get_bool("foo.bar").is_err());
+        cfg.set_bool("foo.k1", true).unwrap();
+        cfg.set_i32("foo.k2", 1).unwrap();
+        cfg.set_i64("foo.k3", 2).unwrap();
+        cfg.set_str("foo.k4", "bar").unwrap();
+        cfg.snapshot().unwrap();
+        drop(cfg);
+
+        let cfg = Config::open(&path).unwrap().snapshot().unwrap();
+        assert_eq!(cfg.get_bool("foo.k1").unwrap(), true);
+        assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1);
+        assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2);
+        assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar");
+
+        let mut entries = cfg.entries(None).unwrap();
+        while let Some(entry) = entries.next() {
+            let entry = entry.unwrap();
+            entry.name();
+            entry.value();
+            entry.level();
+        }
+    }
+
+    #[test]
+    fn multivar() {
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("foo");
+        File::create(&path).unwrap();
+
+        let mut cfg = Config::open(&path).unwrap();
+        cfg.set_multivar("foo.bar", "^$", "baz").unwrap();
+        cfg.set_multivar("foo.bar", "^$", "qux").unwrap();
+        cfg.set_multivar("foo.bar", "^$", "quux").unwrap();
+        cfg.set_multivar("foo.baz", "^$", "oki").unwrap();
+
+        // `entries` filters by name
+        let mut entries: Vec<String> = Vec::new();
+        cfg.entries(Some("foo.bar"))
+            .unwrap()
+            .for_each(|entry| entries.push(entry.value().unwrap().to_string()))
+            .unwrap();
+        entries.sort();
+        assert_eq!(entries, ["baz", "quux", "qux"]);
+
+        // which is the same as `multivar` without a regex
+        let mut multivals = Vec::new();
+        cfg.multivar("foo.bar", None)
+            .unwrap()
+            .for_each(|entry| multivals.push(entry.value().unwrap().to_string()))
+            .unwrap();
+        multivals.sort();
+        assert_eq!(multivals, entries);
+
+        // yet _with_ a regex, `multivar` filters by value
+        let mut quxish = Vec::new();
+        cfg.multivar("foo.bar", Some("qu.*x"))
+            .unwrap()
+            .for_each(|entry| quxish.push(entry.value().unwrap().to_string()))
+            .unwrap();
+        quxish.sort();
+        assert_eq!(quxish, ["quux", "qux"]);
+
+        cfg.remove_multivar("foo.bar", ".*").unwrap();
+
+        let count = |entries: super::ConfigEntries<'_>| -> usize {
+            let mut c = 0;
+            entries.for_each(|_| c += 1).unwrap();
+            c
+        };
+
+        assert_eq!(count(cfg.entries(Some("foo.bar")).unwrap()), 0);
+        assert_eq!(count(cfg.multivar("foo.bar", None).unwrap()), 0);
+    }
+
+    #[test]
+    fn parse() {
+        assert_eq!(Config::parse_bool("").unwrap(), false);
+        assert_eq!(Config::parse_bool("false").unwrap(), false);
+        assert_eq!(Config::parse_bool("no").unwrap(), false);
+        assert_eq!(Config::parse_bool("off").unwrap(), false);
+        assert_eq!(Config::parse_bool("0").unwrap(), false);
+
+        assert_eq!(Config::parse_bool("true").unwrap(), true);
+        assert_eq!(Config::parse_bool("yes").unwrap(), true);
+        assert_eq!(Config::parse_bool("on").unwrap(), true);
+        assert_eq!(Config::parse_bool("1").unwrap(), true);
+        assert_eq!(Config::parse_bool("42").unwrap(), true);
+
+        assert!(Config::parse_bool(" ").is_err());
+        assert!(Config::parse_bool("some-string").is_err());
+        assert!(Config::parse_bool("-").is_err());
+
+        assert_eq!(Config::parse_i32("0").unwrap(), 0);
+        assert_eq!(Config::parse_i32("1").unwrap(), 1);
+        assert_eq!(Config::parse_i32("100").unwrap(), 100);
+        assert_eq!(Config::parse_i32("-1").unwrap(), -1);
+        assert_eq!(Config::parse_i32("-100").unwrap(), -100);
+        assert_eq!(Config::parse_i32("1k").unwrap(), 1024);
+        assert_eq!(Config::parse_i32("4k").unwrap(), 4096);
+        assert_eq!(Config::parse_i32("1M").unwrap(), 1048576);
+        assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024);
+
+        assert_eq!(Config::parse_i64("0").unwrap(), 0);
+        assert_eq!(Config::parse_i64("1").unwrap(), 1);
+        assert_eq!(Config::parse_i64("100").unwrap(), 100);
+        assert_eq!(Config::parse_i64("-1").unwrap(), -1);
+        assert_eq!(Config::parse_i64("-100").unwrap(), -100);
+        assert_eq!(Config::parse_i64("1k").unwrap(), 1024);
+        assert_eq!(Config::parse_i64("4k").unwrap(), 4096);
+        assert_eq!(Config::parse_i64("1M").unwrap(), 1048576);
+        assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024);
+        assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024);
+    }
+}
diff --git a/git2/src/cred.rs b/git2/src/cred.rs
new file mode 100644 (file)
index 0000000..b1f15ca
--- /dev/null
@@ -0,0 +1,725 @@
+use log::{debug, trace};
+use std::ffi::CString;
+use std::io::Write;
+use std::mem;
+use std::path::Path;
+use std::process::{Command, Stdio};
+use std::ptr;
+
+use crate::util::Binding;
+use crate::{raw, Config, Error, IntoCString};
+
+/// A structure to represent git credentials in libgit2.
+pub struct Cred {
+    raw: *mut raw::git_cred,
+}
+
+/// Management of the gitcredentials(7) interface.
+pub struct CredentialHelper {
+    /// A public field representing the currently discovered username from
+    /// configuration.
+    pub username: Option<String>,
+    protocol: Option<String>,
+    host: Option<String>,
+    port: Option<u16>,
+    path: Option<String>,
+    url: String,
+    commands: Vec<String>,
+}
+
+impl Cred {
+    /// Create a "default" credential usable for Negotiate mechanisms like NTLM
+    /// or Kerberos authentication.
+    pub fn default() -> Result<Cred, Error> {
+        crate::init();
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cred_default_new(&mut out));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Create a new ssh key credential object used for querying an ssh-agent.
+    ///
+    /// The username specified is the username to authenticate.
+    pub fn ssh_key_from_agent(username: &str) -> Result<Cred, Error> {
+        crate::init();
+        let mut out = ptr::null_mut();
+        let username = CString::new(username)?;
+        unsafe {
+            try_call!(raw::git_cred_ssh_key_from_agent(&mut out, username));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Create a new passphrase-protected ssh key credential object.
+    pub fn ssh_key(
+        username: &str,
+        publickey: Option<&Path>,
+        privatekey: &Path,
+        passphrase: Option<&str>,
+    ) -> Result<Cred, Error> {
+        crate::init();
+        let username = CString::new(username)?;
+        let publickey = crate::opt_cstr(publickey)?;
+        let privatekey = privatekey.into_c_string()?;
+        let passphrase = crate::opt_cstr(passphrase)?;
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cred_ssh_key_new(
+                &mut out, username, publickey, privatekey, passphrase
+            ));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Create a new ssh key credential object reading the keys from memory.
+    pub fn ssh_key_from_memory(
+        username: &str,
+        publickey: Option<&str>,
+        privatekey: &str,
+        passphrase: Option<&str>,
+    ) -> Result<Cred, Error> {
+        crate::init();
+        let username = CString::new(username)?;
+        let publickey = crate::opt_cstr(publickey)?;
+        let privatekey = CString::new(privatekey)?;
+        let passphrase = crate::opt_cstr(passphrase)?;
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cred_ssh_key_memory_new(
+                &mut out, username, publickey, privatekey, passphrase
+            ));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Create a new plain-text username and password credential object.
+    pub fn userpass_plaintext(username: &str, password: &str) -> Result<Cred, Error> {
+        crate::init();
+        let username = CString::new(username)?;
+        let password = CString::new(password)?;
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cred_userpass_plaintext_new(
+                &mut out, username, password
+            ));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Attempt to read `credential.helper` according to gitcredentials(7) [1]
+    ///
+    /// This function will attempt to parse the user's `credential.helper`
+    /// configuration, invoke the necessary processes, and read off what the
+    /// username/password should be for a particular URL.
+    ///
+    /// The returned credential type will be a username/password credential if
+    /// successful.
+    ///
+    /// [1]: https://www.kernel.org/pub/software/scm/git/docs/gitcredentials.html
+    pub fn credential_helper(
+        config: &Config,
+        url: &str,
+        username: Option<&str>,
+    ) -> Result<Cred, Error> {
+        match CredentialHelper::new(url)
+            .config(config)
+            .username(username)
+            .execute()
+        {
+            Some((username, password)) => Cred::userpass_plaintext(&username, &password),
+            None => Err(Error::from_str(
+                "failed to acquire username/password \
+                 from local configuration",
+            )),
+        }
+    }
+
+    /// Create a credential to specify a username.
+    ///
+    /// This is used with ssh authentication to query for the username if none is
+    /// specified in the URL.
+    pub fn username(username: &str) -> Result<Cred, Error> {
+        crate::init();
+        let username = CString::new(username)?;
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cred_username_new(&mut out, username));
+            Ok(Binding::from_raw(out))
+        }
+    }
+
+    /// Check whether a credential object contains username information.
+    pub fn has_username(&self) -> bool {
+        unsafe { raw::git_cred_has_username(self.raw) == 1 }
+    }
+
+    /// Return the type of credentials that this object represents.
+    pub fn credtype(&self) -> raw::git_credtype_t {
+        unsafe { (*self.raw).credtype }
+    }
+
+    /// Unwrap access to the underlying raw pointer, canceling the destructor
+    pub unsafe fn unwrap(mut self) -> *mut raw::git_cred {
+        mem::replace(&mut self.raw, ptr::null_mut())
+    }
+}
+
+impl Binding for Cred {
+    type Raw = *mut raw::git_cred;
+
+    unsafe fn from_raw(raw: *mut raw::git_cred) -> Cred {
+        Cred { raw }
+    }
+    fn raw(&self) -> *mut raw::git_cred {
+        self.raw
+    }
+}
+
+impl Drop for Cred {
+    fn drop(&mut self) {
+        if !self.raw.is_null() {
+            unsafe {
+                if let Some(f) = (*self.raw).free {
+                    f(self.raw)
+                }
+            }
+        }
+    }
+}
+
+impl CredentialHelper {
+    /// Create a new credential helper object which will be used to probe git's
+    /// local credential configuration.
+    ///
+    /// The URL specified is the namespace on which this will query credentials.
+    /// Invalid URLs are currently ignored.
+    pub fn new(url: &str) -> CredentialHelper {
+        let mut ret = CredentialHelper {
+            protocol: None,
+            host: None,
+            port: None,
+            path: None,
+            username: None,
+            url: url.to_string(),
+            commands: Vec::new(),
+        };
+
+        // Parse out the (protocol, host) if one is available
+        if let Ok(url) = url::Url::parse(url) {
+            if let Some(url::Host::Domain(s)) = url.host() {
+                ret.host = Some(s.to_string());
+            }
+            ret.port = url.port();
+            ret.protocol = Some(url.scheme().to_string());
+        }
+        ret
+    }
+
+    /// Set the username that this credential helper will query with.
+    ///
+    /// By default the username is `None`.
+    pub fn username(&mut self, username: Option<&str>) -> &mut CredentialHelper {
+        self.username = username.map(|s| s.to_string());
+        self
+    }
+
+    /// Query the specified configuration object to discover commands to
+    /// execute, usernames to query, etc.
+    pub fn config(&mut self, config: &Config) -> &mut CredentialHelper {
+        // Figure out the configured username/helper program.
+        //
+        // see http://git-scm.com/docs/gitcredentials.html#_configuration_options
+        if self.username.is_none() {
+            self.config_username(config);
+        }
+        self.config_helper(config);
+        self.config_use_http_path(config);
+        self
+    }
+
+    // Configure the queried username from `config`
+    fn config_username(&mut self, config: &Config) {
+        let key = self.exact_key("username");
+        self.username = config
+            .get_string(&key)
+            .ok()
+            .or_else(|| {
+                self.url_key("username")
+                    .and_then(|s| config.get_string(&s).ok())
+            })
+            .or_else(|| config.get_string("credential.username").ok())
+    }
+
+    // Discover all `helper` directives from `config`
+    fn config_helper(&mut self, config: &Config) {
+        let exact = config.get_string(&self.exact_key("helper"));
+        self.add_command(exact.as_ref().ok().map(|s| &s[..]));
+        if let Some(key) = self.url_key("helper") {
+            let url = config.get_string(&key);
+            self.add_command(url.as_ref().ok().map(|s| &s[..]));
+        }
+        let global = config.get_string("credential.helper");
+        self.add_command(global.as_ref().ok().map(|s| &s[..]));
+    }
+
+    // Discover `useHttpPath` from `config`
+    fn config_use_http_path(&mut self, config: &Config) {
+        let mut use_http_path = false;
+        if let Some(value) = config.get_bool(&self.exact_key("useHttpPath")).ok() {
+            use_http_path = value;
+        } else if let Some(value) = self
+            .url_key("useHttpPath")
+            .and_then(|key| config.get_bool(&key).ok())
+        {
+            use_http_path = value;
+        } else if let Some(value) = config.get_bool("credential.useHttpPath").ok() {
+            use_http_path = value;
+        }
+
+        if use_http_path {
+            if let Ok(url) = url::Url::parse(&self.url) {
+                let path = url.path();
+                // Url::parse always includes a leading slash for rooted URLs, while git does not.
+                self.path = Some(path.strip_prefix('/').unwrap_or(path).to_string());
+            }
+        }
+    }
+
+    // Add a `helper` configured command to the list of commands to execute.
+    //
+    // see https://www.kernel.org/pub/software/scm/git/docs/technical
+    //                           /api-credentials.html#_credential_helpers
+    fn add_command(&mut self, cmd: Option<&str>) {
+        let cmd = match cmd {
+            Some("") | None => return,
+            Some(s) => s,
+        };
+
+        if cmd.starts_with('!') {
+            self.commands.push(cmd[1..].to_string());
+        } else if cmd.contains("/") || cmd.contains("\\") {
+            self.commands.push(cmd.to_string());
+        } else {
+            self.commands.push(format!("git credential-{}", cmd));
+        }
+    }
+
+    fn exact_key(&self, name: &str) -> String {
+        format!("credential.{}.{}", self.url, name)
+    }
+
+    fn url_key(&self, name: &str) -> Option<String> {
+        match (&self.host, &self.protocol) {
+            (&Some(ref host), &Some(ref protocol)) => {
+                Some(format!("credential.{}://{}.{}", protocol, host, name))
+            }
+            _ => None,
+        }
+    }
+
+    /// Execute this helper, attempting to discover a username/password pair.
+    ///
+    /// All I/O errors are ignored, (to match git behavior), and this function
+    /// only succeeds if both a username and a password were found
+    pub fn execute(&self) -> Option<(String, String)> {
+        let mut username = self.username.clone();
+        let mut password = None;
+        for cmd in &self.commands {
+            let (u, p) = self.execute_cmd(cmd, &username);
+            if u.is_some() && username.is_none() {
+                username = u;
+            }
+            if p.is_some() && password.is_none() {
+                password = p;
+            }
+            if username.is_some() && password.is_some() {
+                break;
+            }
+        }
+
+        match (username, password) {
+            (Some(u), Some(p)) => Some((u, p)),
+            _ => None,
+        }
+    }
+
+    // Execute the given `cmd`, providing the appropriate variables on stdin and
+    // then afterwards parsing the output into the username/password on stdout.
+    fn execute_cmd(
+        &self,
+        cmd: &str,
+        username: &Option<String>,
+    ) -> (Option<String>, Option<String>) {
+        macro_rules! my_try( ($e:expr) => (
+            match $e {
+                Ok(e) => e,
+                Err(e) => {
+                    debug!("{} failed with {}", stringify!($e), e);
+                    return (None, None)
+                }
+            }
+        ) );
+
+        // It looks like the `cmd` specification is typically bourne-shell-like
+        // syntax, so try that first. If that fails, though, we may be on a
+        // Windows machine for example where `sh` isn't actually available by
+        // default. Most credential helper configurations though are pretty
+        // simple (aka one or two space-separated strings) so also try to invoke
+        // the process directly.
+        //
+        // If that fails then it's up to the user to put `sh` in path and make
+        // sure it works.
+        let mut c = Command::new("sh");
+        #[cfg(windows)]
+        {
+            use std::os::windows::process::CommandExt;
+            const CREATE_NO_WINDOW: u32 = 0x08000000;
+            c.creation_flags(CREATE_NO_WINDOW);
+        }
+        c.arg("-c")
+            .arg(&format!("{} get", cmd))
+            .stdin(Stdio::piped())
+            .stdout(Stdio::piped())
+            .stderr(Stdio::piped());
+        debug!("executing credential helper {:?}", c);
+        let mut p = match c.spawn() {
+            Ok(p) => p,
+            Err(e) => {
+                debug!("`sh` failed to spawn: {}", e);
+                let mut parts = cmd.split_whitespace();
+                let mut c = Command::new(parts.next().unwrap());
+                #[cfg(windows)]
+                {
+                    use std::os::windows::process::CommandExt;
+                    const CREATE_NO_WINDOW: u32 = 0x08000000;
+                    c.creation_flags(CREATE_NO_WINDOW);
+                }
+                for arg in parts {
+                    c.arg(arg);
+                }
+                c.arg("get")
+                    .stdin(Stdio::piped())
+                    .stdout(Stdio::piped())
+                    .stderr(Stdio::piped());
+                debug!("executing credential helper {:?}", c);
+                match c.spawn() {
+                    Ok(p) => p,
+                    Err(e) => {
+                        debug!("fallback of {:?} failed with {}", cmd, e);
+                        return (None, None);
+                    }
+                }
+            }
+        };
+
+        // Ignore write errors as the command may not actually be listening for
+        // stdin
+        {
+            let stdin = p.stdin.as_mut().unwrap();
+            if let Some(ref p) = self.protocol {
+                let _ = writeln!(stdin, "protocol={}", p);
+            }
+            if let Some(ref p) = self.host {
+                if let Some(ref p2) = self.port {
+                    let _ = writeln!(stdin, "host={}:{}", p, p2);
+                } else {
+                    let _ = writeln!(stdin, "host={}", p);
+                }
+            }
+            if let Some(ref p) = self.path {
+                let _ = writeln!(stdin, "path={}", p);
+            }
+            if let Some(ref p) = *username {
+                let _ = writeln!(stdin, "username={}", p);
+            }
+        }
+        let output = my_try!(p.wait_with_output());
+        if !output.status.success() {
+            debug!(
+                "credential helper failed: {}\nstdout ---\n{}\nstderr ---\n{}",
+                output.status,
+                String::from_utf8_lossy(&output.stdout),
+                String::from_utf8_lossy(&output.stderr)
+            );
+            return (None, None);
+        }
+        trace!(
+            "credential helper stderr ---\n{}",
+            String::from_utf8_lossy(&output.stderr)
+        );
+        self.parse_output(output.stdout)
+    }
+
+    // Parse the output of a command into the username/password found
+    fn parse_output(&self, output: Vec<u8>) -> (Option<String>, Option<String>) {
+        // Parse the output of the command, looking for username/password
+        let mut username = None;
+        let mut password = None;
+        for line in output.split(|t| *t == b'\n') {
+            let mut parts = line.splitn(2, |t| *t == b'=');
+            let key = parts.next().unwrap();
+            let value = match parts.next() {
+                Some(s) => s,
+                None => {
+                    trace!("ignoring output line: {}", String::from_utf8_lossy(line));
+                    continue;
+                }
+            };
+            let value = match String::from_utf8(value.to_vec()) {
+                Ok(s) => s,
+                Err(..) => continue,
+            };
+            match key {
+                b"username" => username = Some(value),
+                b"password" => password = Some(value),
+                _ => {}
+            }
+        }
+        (username, password)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use std::env;
+    use std::fs::File;
+    use std::io::prelude::*;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    use crate::{Config, ConfigLevel, Cred, CredentialHelper};
+
+    macro_rules! test_cfg( ($($k:expr => $v:expr),*) => ({
+        let td = TempDir::new().unwrap();
+        let mut cfg = Config::new().unwrap();
+        cfg.add_file(&td.path().join("cfg"), ConfigLevel::App, false).unwrap();
+        $(cfg.set_str($k, $v).unwrap();)*
+        cfg
+    }) );
+
+    #[test]
+    fn smoke() {
+        Cred::default().unwrap();
+    }
+
+    #[test]
+    fn credential_helper1() {
+        let cfg = test_cfg! {
+            "credential.helper" => "!f() { echo username=a; echo password=b; }; f"
+        };
+        let (u, p) = CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "a");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    fn credential_helper2() {
+        let cfg = test_cfg! {};
+        assert!(CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .is_none());
+    }
+
+    #[test]
+    fn credential_helper3() {
+        let cfg = test_cfg! {
+            "credential.https://example.com.helper" =>
+                    "!f() { echo username=c; }; f",
+            "credential.helper" => "!f() { echo username=a; echo password=b; }; f"
+        };
+        let (u, p) = CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "c");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    fn credential_helper4() {
+        if cfg!(windows) {
+            return;
+        } // shell scripts don't work on Windows
+
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("script");
+        File::create(&path)
+            .unwrap()
+            .write(
+                br"\
+#!/bin/sh
+echo username=c
+",
+            )
+            .unwrap();
+        chmod(&path);
+        let cfg = test_cfg! {
+            "credential.https://example.com.helper" =>
+                    &path.display().to_string()[..],
+            "credential.helper" => "!f() { echo username=a; echo password=b; }; f"
+        };
+        let (u, p) = CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "c");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    fn credential_helper5() {
+        if cfg!(windows) {
+            return;
+        } // shell scripts don't work on Windows
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("git-credential-script");
+        File::create(&path)
+            .unwrap()
+            .write(
+                br"\
+#!/bin/sh
+echo username=c
+",
+            )
+            .unwrap();
+        chmod(&path);
+
+        let paths = env::var("PATH").unwrap();
+        let paths =
+            env::split_paths(&paths).chain(path.parent().map(|p| p.to_path_buf()).into_iter());
+        env::set_var("PATH", &env::join_paths(paths).unwrap());
+
+        let cfg = test_cfg! {
+            "credential.https://example.com.helper" => "script",
+            "credential.helper" => "!f() { echo username=a; echo password=b; }; f"
+        };
+        let (u, p) = CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "c");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    fn credential_helper6() {
+        let cfg = test_cfg! {
+            "credential.helper" => ""
+        };
+        assert!(CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .is_none());
+    }
+
+    #[test]
+    fn credential_helper7() {
+        if cfg!(windows) {
+            return;
+        } // shell scripts don't work on Windows
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("script");
+        File::create(&path)
+            .unwrap()
+            .write(
+                br"\
+#!/bin/sh
+echo username=$1
+echo password=$2
+",
+            )
+            .unwrap();
+        chmod(&path);
+        let cfg = test_cfg! {
+            "credential.helper" => &format!("{} a b", path.display())
+        };
+        let (u, p) = CredentialHelper::new("https://example.com/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "a");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    fn credential_helper8() {
+        let cfg = test_cfg! {
+            "credential.useHttpPath" => "true"
+        };
+        let mut helper = CredentialHelper::new("https://example.com/foo/bar");
+        helper.config(&cfg);
+        assert_eq!(helper.path.as_deref(), Some("foo/bar"));
+    }
+
+    #[test]
+    fn credential_helper9() {
+        let cfg = test_cfg! {
+            "credential.helper" => "!f() { while read line; do eval $line; done; if [ \"$host\" = example.com:3000 ]; then echo username=a; echo password=b; fi; }; f"
+        };
+        let (u, p) = CredentialHelper::new("https://example.com:3000/foo/bar")
+            .config(&cfg)
+            .execute()
+            .unwrap();
+        assert_eq!(u, "a");
+        assert_eq!(p, "b");
+    }
+
+    #[test]
+    #[cfg(feature = "ssh")]
+    fn ssh_key_from_memory() {
+        let cred = Cred::ssh_key_from_memory(
+            "test",
+            Some("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDByAO8uj+kXicj6C2ODMspgmUoVyl5eaw8vR6a1yEnFuJFzevabNlN6Ut+CPT3TRnYk5BW73pyXBtnSL2X95BOnbjMDXc4YIkgs3YYHWnxbqsD4Pj/RoGqhf+gwhOBtL0poh8tT8WqXZYxdJQKLQC7oBqf3ykCEYulE4oeRUmNh4IzEE+skD/zDkaJ+S1HRD8D8YCiTO01qQnSmoDFdmIZTi8MS8Cw+O/Qhym1271ThMlhD6PubSYJXfE6rVbE7A9RzH73A6MmKBlzK8VTb4SlNSrr/DOk+L0uq+wPkv+pm+D9WtxoqQ9yl6FaK1cPawa3+7yRNle3m+72KCtyMkQv"),
+            r#"
+                -----BEGIN RSA PRIVATE KEY-----
+                Proc-Type: 4,ENCRYPTED
+                DEK-Info: AES-128-CBC,818C7722D3B01F2161C2ACF6A5BBAAE8
+
+                3Cht4QB3PcoQ0I55j1B3m2ZzIC/mrh+K5nQeA1Vy2GBTMyM7yqGHqTOv7qLhJscd
+                H+cB0Pm6yCr3lYuNrcKWOCUto+91P7ikyARruHVwyIxKdNx15uNulOzQJHQWNbA4
+                RQHlhjON4atVo2FyJ6n+ujK6QiBg2PR5Vbbw/AtV6zBCFW3PhzDn+qqmHjpBFqj2
+                vZUUe+MkDQcaF5J45XMHahhSdo/uKCDhfbylExp/+ACWkvxdPpsvcARM6X434ucD
+                aPY+4i0/JyLkdbm0GFN9/q3i53qf4kCBhojFl4AYJdGI0AzAgbdTXZ7EJHbAGZHS
+                os5K0oTwDVXMI0sSE2I/qHxaZZsDP1dOKq6di6SFPUp8liYimm7rNintRX88Gl2L
+                g1ko9abp/NlgD0YY/3mad+NNAISDL/YfXq2fklH3En3/7ZrOVZFKfZXwQwas5g+p
+                VQPKi3+ae74iOjLyuPDSc1ePmhUNYeP+9rLSc0wiaiHqls+2blPPDxAGMEo63kbz
+                YPVjdmuVX4VWnyEsfTxxJdFDYGSNh6rlrrO1RFrex7kJvpg5gTX4M/FT8TfCd7Hn
+                M6adXsLMqwu5tz8FuDmAtVdq8zdSrgZeAbpJ9D3EDOmZ70xz4XBL19ImxDp+Qqs2
+                kQX7kobRzeeP2URfRoGr7XZikQWyQ2UASfPcQULY8R58QoZWWsQ4w51GZHg7TDnw
+                1DRo/0OgkK7Gqf215nFmMpB4uyi58cq3WFwWQa1IqslkObpVgBQZcNZb/hKUYPGk
+                g4zehfIgAfCdnQHwZvQ6Fdzhcs3SZeO+zVyuiZN3Gsi9HU0/1vpAKiuuOzcG02vF
+                b6Y6hwsAA9yphF3atI+ARD4ZwXdDfzuGb3yJglMT3Fr/xuLwAvdchRo1spANKA0E
+                tT5okLrK0H4wnHvf2SniVVWRhmJis0lQo9LjGGwRIdsPpVnJSDvaISIVF+fHT90r
+                HvxN8zXI93x9jcPtwp7puQ1C7ehKJK10sZ71OLIZeuUgwt+5DRunqg6evPco9Go7
+                UOGwcVhLY200KT+1k7zWzCS0yVQp2HRm6cxsZXAp4ClBSwIx15eIoLIrjZdJRjCq
+                COp6pZx1fnvJ9ERIvl5hon+Ty+renMcFKz2HmchC7egpcqIxW9Dsv6zjhHle6pxb
+                37GaEKHF2KA3RN+dSV/K8n+C9Yent5tx5Y9a/pMcgRGtgu+G+nyFmkPKn5Zt39yX
+                qDpyM0LtbRVZPs+MgiqoGIwYc/ujoCq7GL38gezsBQoHaTt79yYBqCp6UR0LMuZ5
+                f/7CtWqffgySfJ/0wjGidDAumDv8CK45AURpL/Z+tbFG3M9ar/LZz/Y6EyBcLtGY
+                Wwb4zs8zXIA0qHrjNTnPqHDvezziArYfgPjxCIHMZzms9Yn8+N02p39uIytqg434
+                BAlCqZ7GYdDFfTpWIwX+segTK9ux0KdBqcQv+9Fwwjkq9KySnRKqNl7ZJcefFZJq
+                c6PA1iinZWBjuaO1HKx3PFulrl0bcpR9Kud1ZIyfnh5rwYN8UQkkcR/wZPla04TY
+                8l5dq/LI/3G5sZXwUHKOcuQWTj7Saq7Q6gkKoMfqt0wC5bpZ1m17GHPoMz6GtX9O
+                -----END RSA PRIVATE KEY-----
+            "#,
+            Some("test123"));
+        assert!(cred.is_ok());
+    }
+
+    #[cfg(unix)]
+    fn chmod(path: &Path) {
+        use std::fs;
+        use std::os::unix::prelude::*;
+        let mut perms = fs::metadata(path).unwrap().permissions();
+        perms.set_mode(0o755);
+        fs::set_permissions(path, perms).unwrap();
+    }
+    #[cfg(windows)]
+    fn chmod(_path: &Path) {}
+}
diff --git a/git2/src/describe.rs b/git2/src/describe.rs
new file mode 100644 (file)
index 0000000..cbaa189
--- /dev/null
@@ -0,0 +1,201 @@
+use std::ffi::CString;
+use std::marker;
+use std::mem;
+use std::ptr;
+
+use libc::{c_int, c_uint};
+
+use crate::util::Binding;
+use crate::{raw, Buf, Error, Repository};
+
+/// The result of a `describe` operation on either an `Describe` or a
+/// `Repository`.
+pub struct Describe<'repo> {
+    raw: *mut raw::git_describe_result,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// Options which indicate how a `Describe` is created.
+pub struct DescribeOptions {
+    raw: raw::git_describe_options,
+    pattern: CString,
+}
+
+/// Options which can be used to customize how a description is formatted.
+pub struct DescribeFormatOptions {
+    raw: raw::git_describe_format_options,
+    dirty_suffix: CString,
+}
+
+impl<'repo> Describe<'repo> {
+    /// Prints this describe result, returning the result as a string.
+    pub fn format(&self, opts: Option<&DescribeFormatOptions>) -> Result<String, Error> {
+        let buf = Buf::new();
+        let opts = opts.map(|o| &o.raw as *const _).unwrap_or(ptr::null());
+        unsafe {
+            try_call!(raw::git_describe_format(buf.raw(), self.raw, opts));
+        }
+        Ok(String::from_utf8(buf.to_vec()).unwrap())
+    }
+}
+
+impl<'repo> Binding for Describe<'repo> {
+    type Raw = *mut raw::git_describe_result;
+
+    unsafe fn from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo> {
+        Describe {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_describe_result {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Describe<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_describe_result_free(self.raw) }
+    }
+}
+
+impl Default for DescribeFormatOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl DescribeFormatOptions {
+    /// Creates a new blank set of formatting options for a description.
+    pub fn new() -> DescribeFormatOptions {
+        let mut opts = DescribeFormatOptions {
+            raw: unsafe { mem::zeroed() },
+            dirty_suffix: CString::new(Vec::new()).unwrap(),
+        };
+        opts.raw.version = 1;
+        opts.raw.abbreviated_size = 7;
+        opts
+    }
+
+    /// Sets the size of the abbreviated commit id to use.
+    ///
+    /// The value is the lower bound for the length of the abbreviated string,
+    /// and the default is 7.
+    pub fn abbreviated_size(&mut self, size: u32) -> &mut Self {
+        self.raw.abbreviated_size = size as c_uint;
+        self
+    }
+
+    /// Sets whether or not the long format is used even when a shorter name
+    /// could be used.
+    pub fn always_use_long_format(&mut self, long: bool) -> &mut Self {
+        self.raw.always_use_long_format = long as c_int;
+        self
+    }
+
+    /// If the workdir is dirty and this is set, this string will be appended to
+    /// the description string.
+    pub fn dirty_suffix(&mut self, suffix: &str) -> &mut Self {
+        self.dirty_suffix = CString::new(suffix).unwrap();
+        self.raw.dirty_suffix = self.dirty_suffix.as_ptr();
+        self
+    }
+}
+
+impl Default for DescribeOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl DescribeOptions {
+    /// Creates a new blank set of formatting options for a description.
+    pub fn new() -> DescribeOptions {
+        let mut opts = DescribeOptions {
+            raw: unsafe { mem::zeroed() },
+            pattern: CString::new(Vec::new()).unwrap(),
+        };
+        opts.raw.version = 1;
+        opts.raw.max_candidates_tags = 10;
+        opts
+    }
+
+    #[allow(missing_docs)]
+    pub fn max_candidates_tags(&mut self, max: u32) -> &mut Self {
+        self.raw.max_candidates_tags = max as c_uint;
+        self
+    }
+
+    /// Sets the reference lookup strategy
+    ///
+    /// This behaves like the `--tags` option to git-describe.
+    pub fn describe_tags(&mut self) -> &mut Self {
+        self.raw.describe_strategy = raw::GIT_DESCRIBE_TAGS as c_uint;
+        self
+    }
+
+    /// Sets the reference lookup strategy
+    ///
+    /// This behaves like the `--all` option to git-describe.
+    pub fn describe_all(&mut self) -> &mut Self {
+        self.raw.describe_strategy = raw::GIT_DESCRIBE_ALL as c_uint;
+        self
+    }
+
+    /// Indicates when calculating the distance from the matching tag or
+    /// reference whether to only walk down the first-parent ancestry.
+    pub fn only_follow_first_parent(&mut self, follow: bool) -> &mut Self {
+        self.raw.only_follow_first_parent = follow as c_int;
+        self
+    }
+
+    /// If no matching tag or reference is found whether a describe option would
+    /// normally fail. This option indicates, however, that it will instead fall
+    /// back to showing the full id of the commit.
+    pub fn show_commit_oid_as_fallback(&mut self, show: bool) -> &mut Self {
+        self.raw.show_commit_oid_as_fallback = show as c_int;
+        self
+    }
+
+    #[allow(missing_docs)]
+    pub fn pattern(&mut self, pattern: &str) -> &mut Self {
+        self.pattern = CString::new(pattern).unwrap();
+        self.raw.pattern = self.pattern.as_ptr();
+        self
+    }
+}
+
+impl Binding for DescribeOptions {
+    type Raw = *mut raw::git_describe_options;
+
+    unsafe fn from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions {
+        panic!("unimplemened")
+    }
+    fn raw(&self) -> *mut raw::git_describe_options {
+        &self.raw as *const _ as *mut _
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::DescribeOptions;
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = t!(repo.head()).target().unwrap();
+
+        let d = t!(repo.describe(DescribeOptions::new().show_commit_oid_as_fallback(true)));
+        let id = head.to_string();
+        assert_eq!(t!(d.format(None)), &id[..7]);
+
+        let obj = t!(repo.find_object(head, None));
+        let sig = t!(repo.signature());
+        t!(repo.tag("foo", &obj, &sig, "message", true));
+        let d = t!(repo.describe(&DescribeOptions::new()));
+        assert_eq!(t!(d.format(None)), "foo");
+
+        let d = t!(obj.describe(&DescribeOptions::new()));
+        assert_eq!(t!(d.format(None)), "foo");
+    }
+}
diff --git a/git2/src/diff.rs b/git2/src/diff.rs
new file mode 100644 (file)
index 0000000..3070550
--- /dev/null
@@ -0,0 +1,1863 @@
+use libc::{c_char, c_int, c_void, size_t};
+use std::ffi::CString;
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr;
+use std::slice;
+
+use crate::util::{self, Binding};
+use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository};
+use crate::{DiffFlags, DiffStatsFormat, IntoCString};
+
+/// The diff object that contains all individual file deltas.
+///
+/// This is an opaque structure which will be allocated by one of the diff
+/// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree`
+/// or other `diff_*` functions).
+pub struct Diff<'repo> {
+    raw: *mut raw::git_diff,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+unsafe impl<'repo> Send for Diff<'repo> {}
+
+/// Description of changes to one entry.
+pub struct DiffDelta<'a> {
+    raw: *mut raw::git_diff_delta,
+    _marker: marker::PhantomData<&'a raw::git_diff_delta>,
+}
+
+/// Description of one side of a delta.
+///
+/// Although this is called a "file" it could represent a file, a symbolic
+/// link, a submodule commit id, or even a tree (although that only happens if
+/// you are tracking type changes or ignored/untracked directories).
+pub struct DiffFile<'a> {
+    raw: *const raw::git_diff_file,
+    _marker: marker::PhantomData<&'a raw::git_diff_file>,
+}
+
+/// Structure describing options about how the diff should be executed.
+pub struct DiffOptions {
+    pathspec: Vec<CString>,
+    pathspec_ptrs: Vec<*const c_char>,
+    old_prefix: Option<CString>,
+    new_prefix: Option<CString>,
+    raw: raw::git_diff_options,
+}
+
+/// Control behavior of rename and copy detection
+pub struct DiffFindOptions {
+    raw: raw::git_diff_find_options,
+}
+
+/// Control behavior of formatting emails
+pub struct DiffFormatEmailOptions {
+    raw: raw::git_diff_format_email_options,
+}
+
+/// Control behavior of formatting emails
+pub struct DiffPatchidOptions {
+    raw: raw::git_diff_patchid_options,
+}
+
+/// An iterator over the diffs in a delta
+pub struct Deltas<'diff> {
+    range: Range<usize>,
+    diff: &'diff Diff<'diff>,
+}
+
+/// Structure describing a line (or data span) of a diff.
+pub struct DiffLine<'a> {
+    raw: *const raw::git_diff_line,
+    _marker: marker::PhantomData<&'a raw::git_diff_line>,
+}
+
+/// Structure describing a hunk of a diff.
+pub struct DiffHunk<'a> {
+    raw: *const raw::git_diff_hunk,
+    _marker: marker::PhantomData<&'a raw::git_diff_hunk>,
+}
+
+/// Structure describing a hunk of a diff.
+pub struct DiffStats {
+    raw: *mut raw::git_diff_stats,
+}
+
+/// Structure describing the binary contents of a diff.
+pub struct DiffBinary<'a> {
+    raw: *const raw::git_diff_binary,
+    _marker: marker::PhantomData<&'a raw::git_diff_binary>,
+}
+
+/// The contents of one of the files in a binary diff.
+pub struct DiffBinaryFile<'a> {
+    raw: *const raw::git_diff_binary_file,
+    _marker: marker::PhantomData<&'a raw::git_diff_binary_file>,
+}
+
+/// When producing a binary diff, the binary data returned will be
+/// either the deflated full ("literal") contents of the file, or
+/// the deflated binary delta between the two sides (whichever is
+/// smaller).
+#[derive(Copy, Clone, Debug)]
+pub enum DiffBinaryKind {
+    /// There is no binary delta
+    None,
+    /// The binary data is the literal contents of the file
+    Literal,
+    /// The binary data is the delta from one side to the other
+    Delta,
+}
+
+type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
+
+pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a;
+pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a;
+pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a;
+pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a;
+
+pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> {
+    pub file: Option<&'a mut FileCb<'b>>,
+    pub binary: Option<&'c mut BinaryCb<'d>>,
+    pub hunk: Option<&'e mut HunkCb<'f>>,
+    pub line: Option<&'g mut LineCb<'h>>,
+}
+
+impl<'repo> Diff<'repo> {
+    /// Merge one diff into another.
+    ///
+    /// This merges items from the "from" list into the "self" list.  The
+    /// resulting diff will have all items that appear in either list.
+    /// If an item appears in both lists, then it will be "merged" to appear
+    /// as if the old version was from the "onto" list and the new version
+    /// is from the "from" list (with the exception that if the item has a
+    /// pending DELETE in the middle, then it will show as deleted).
+    pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_diff_merge(self.raw, &*from.raw));
+        }
+        Ok(())
+    }
+
+    /// Returns an iterator over the deltas in this diff.
+    pub fn deltas(&self) -> Deltas<'_> {
+        let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) };
+        Deltas {
+            range: 0..(num_deltas as usize),
+            diff: self,
+        }
+    }
+
+    /// Return the diff delta for an entry in the diff list.
+    pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> {
+        unsafe {
+            let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t);
+            Binding::from_raw_opt(ptr as *mut _)
+        }
+    }
+
+    /// Check if deltas are sorted case sensitively or insensitively.
+    pub fn is_sorted_icase(&self) -> bool {
+        unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 }
+    }
+
+    /// Iterate over a diff generating formatted text output.
+    ///
+    /// Returning `false` from the callback will terminate the iteration and
+    /// return an error from this function.
+    pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error>
+    where
+        F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool,
+    {
+        let mut cb: &mut PrintCb<'_> = &mut cb;
+        let ptr = &mut cb as *mut _;
+        let print: raw::git_diff_line_cb = Some(print_cb);
+        unsafe {
+            try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _));
+            Ok(())
+        }
+    }
+
+    /// Loop over all deltas in a diff issuing callbacks.
+    ///
+    /// Returning `false` from any callback will terminate the iteration and
+    /// return an error from this function.
+    pub fn foreach(
+        &self,
+        file_cb: &mut FileCb<'_>,
+        binary_cb: Option<&mut BinaryCb<'_>>,
+        hunk_cb: Option<&mut HunkCb<'_>>,
+        line_cb: Option<&mut LineCb<'_>>,
+    ) -> Result<(), Error> {
+        let mut cbs = DiffCallbacks {
+            file: Some(file_cb),
+            binary: binary_cb,
+            hunk: hunk_cb,
+            line: line_cb,
+        };
+        let ptr = &mut cbs as *mut _;
+        unsafe {
+            let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
+                Some(binary_cb_c)
+            } else {
+                None
+            };
+            let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
+                Some(hunk_cb_c)
+            } else {
+                None
+            };
+            let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
+                Some(line_cb_c)
+            } else {
+                None
+            };
+            let file_cb: raw::git_diff_file_cb = Some(file_cb_c);
+            try_call!(raw::git_diff_foreach(
+                self.raw,
+                file_cb,
+                binary_cb_c,
+                hunk_cb_c,
+                line_cb_c,
+                ptr as *mut _
+            ));
+            Ok(())
+        }
+    }
+
+    /// Accumulate diff statistics for all patches.
+    pub fn stats(&self) -> Result<DiffStats, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_get_stats(&mut ret, self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Transform a diff marking file renames, copies, etc.
+    ///
+    /// This modifies a diff in place, replacing old entries that look like
+    /// renames or copies with new entries reflecting those changes. This also
+    /// will, if requested, break modified files into add/remove pairs if the
+    /// amount of change is above a threshold.
+    pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> {
+        let opts = opts.map(|opts| &opts.raw);
+        unsafe {
+            try_call!(raw::git_diff_find_similar(self.raw, opts));
+        }
+        Ok(())
+    }
+
+    /// Create an e-mail ready patch from a diff.
+    ///
+    /// Matches the format created by `git format-patch`
+    #[doc(hidden)]
+    #[deprecated(note = "refactored to `Email::from_diff` to match upstream")]
+    pub fn format_email(
+        &mut self,
+        patch_no: usize,
+        total_patches: usize,
+        commit: &crate::Commit<'repo>,
+        opts: Option<&mut DiffFormatEmailOptions>,
+    ) -> Result<Buf, Error> {
+        assert!(patch_no > 0);
+        assert!(patch_no <= total_patches);
+        let mut default = DiffFormatEmailOptions::default();
+        let raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw);
+        let summary = commit.summary_bytes().unwrap();
+        let mut message = commit.message_bytes();
+        assert!(message.starts_with(summary));
+        message = &message[summary.len()..];
+        raw_opts.patch_no = patch_no;
+        raw_opts.total_patches = total_patches;
+        let id = commit.id();
+        raw_opts.id = id.raw();
+        raw_opts.summary = summary.as_ptr() as *const _;
+        raw_opts.body = message.as_ptr() as *const _;
+        raw_opts.author = commit.author().raw();
+        let buf = Buf::new();
+        #[allow(deprecated)]
+        unsafe {
+            try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts));
+        }
+        Ok(buf)
+    }
+
+    /// Create a patch ID from a diff.
+    pub fn patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_diff_patchid(
+                &mut raw,
+                self.raw,
+                opts.map(|o| &mut o.raw)
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    // TODO: num_deltas_of_type, find_similar
+}
+impl Diff<'static> {
+    /// Read the contents of a git patch file into a `git_diff` object.
+    ///
+    /// The diff object produced is similar to the one that would be
+    /// produced if you actually produced it computationally by comparing
+    /// two trees, however there may be subtle differences. For example,
+    /// a patch file likely contains abbreviated object IDs, so the
+    /// object IDs parsed by this function will also be abbreviated.
+    pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> {
+        crate::init();
+        let mut diff: *mut raw::git_diff = std::ptr::null_mut();
+        unsafe {
+            // NOTE: Doesn't depend on repo, so lifetime can be 'static
+            try_call!(raw::git_diff_from_buffer(
+                &mut diff,
+                buffer.as_ptr() as *const c_char,
+                buffer.len()
+            ));
+            Ok(Diff::from_raw(diff))
+        }
+    }
+}
+
+pub extern "C" fn print_cb(
+    delta: *const raw::git_diff_delta,
+    hunk: *const raw::git_diff_hunk,
+    line: *const raw::git_diff_line,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let delta = Binding::from_raw(delta as *mut _);
+        let hunk = Binding::from_raw_opt(hunk);
+        let line = Binding::from_raw(line);
+
+        let r = panic::wrap(|| {
+            let data = data as *mut &mut PrintCb<'_>;
+            (*data)(delta, hunk, line)
+        });
+        if r == Some(true) {
+            raw::GIT_OK
+        } else {
+            raw::GIT_EUSER
+        }
+    }
+}
+
+pub extern "C" fn file_cb_c(
+    delta: *const raw::git_diff_delta,
+    progress: f32,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let delta = Binding::from_raw(delta as *mut _);
+
+        let r = panic::wrap(|| {
+            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
+            match (*cbs).file {
+                Some(ref mut cb) => cb(delta, progress),
+                None => false,
+            }
+        });
+        if r == Some(true) {
+            raw::GIT_OK
+        } else {
+            raw::GIT_EUSER
+        }
+    }
+}
+
+pub extern "C" fn binary_cb_c(
+    delta: *const raw::git_diff_delta,
+    binary: *const raw::git_diff_binary,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let delta = Binding::from_raw(delta as *mut _);
+        let binary = Binding::from_raw(binary);
+
+        let r = panic::wrap(|| {
+            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
+            match (*cbs).binary {
+                Some(ref mut cb) => cb(delta, binary),
+                None => false,
+            }
+        });
+        if r == Some(true) {
+            raw::GIT_OK
+        } else {
+            raw::GIT_EUSER
+        }
+    }
+}
+
+pub extern "C" fn hunk_cb_c(
+    delta: *const raw::git_diff_delta,
+    hunk: *const raw::git_diff_hunk,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let delta = Binding::from_raw(delta as *mut _);
+        let hunk = Binding::from_raw(hunk);
+
+        let r = panic::wrap(|| {
+            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
+            match (*cbs).hunk {
+                Some(ref mut cb) => cb(delta, hunk),
+                None => false,
+            }
+        });
+        if r == Some(true) {
+            raw::GIT_OK
+        } else {
+            raw::GIT_EUSER
+        }
+    }
+}
+
+pub extern "C" fn line_cb_c(
+    delta: *const raw::git_diff_delta,
+    hunk: *const raw::git_diff_hunk,
+    line: *const raw::git_diff_line,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let delta = Binding::from_raw(delta as *mut _);
+        let hunk = Binding::from_raw_opt(hunk);
+        let line = Binding::from_raw(line);
+
+        let r = panic::wrap(|| {
+            let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>;
+            match (*cbs).line {
+                Some(ref mut cb) => cb(delta, hunk, line),
+                None => false,
+            }
+        });
+        if r == Some(true) {
+            raw::GIT_OK
+        } else {
+            raw::GIT_EUSER
+        }
+    }
+}
+
+impl<'repo> Binding for Diff<'repo> {
+    type Raw = *mut raw::git_diff;
+    unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> {
+        Diff {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_diff {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Diff<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_diff_free(self.raw) }
+    }
+}
+
+impl<'a> DiffDelta<'a> {
+    /// Returns the flags on the delta.
+    ///
+    /// For more information, see `DiffFlags`'s documentation.
+    pub fn flags(&self) -> DiffFlags {
+        let flags = unsafe { (*self.raw).flags };
+        let mut result = DiffFlags::empty();
+
+        #[cfg(target_env = "msvc")]
+        fn as_u32(flag: i32) -> u32 {
+            flag as u32
+        }
+        #[cfg(not(target_env = "msvc"))]
+        fn as_u32(flag: u32) -> u32 {
+            flag
+        }
+
+        if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 {
+            result |= DiffFlags::BINARY;
+        }
+        if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 {
+            result |= DiffFlags::NOT_BINARY;
+        }
+        if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 {
+            result |= DiffFlags::VALID_ID;
+        }
+        if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 {
+            result |= DiffFlags::EXISTS;
+        }
+        result
+    }
+
+    // TODO: expose when diffs are more exposed
+    // pub fn similarity(&self) -> u16 {
+    //     unsafe { (*self.raw).similarity }
+    // }
+
+    /// Returns the number of files in this delta.
+    pub fn nfiles(&self) -> u16 {
+        unsafe { (*self.raw).nfiles }
+    }
+
+    /// Returns the status of this entry
+    ///
+    /// For more information, see `Delta`'s documentation
+    pub fn status(&self) -> Delta {
+        match unsafe { (*self.raw).status } {
+            raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified,
+            raw::GIT_DELTA_ADDED => Delta::Added,
+            raw::GIT_DELTA_DELETED => Delta::Deleted,
+            raw::GIT_DELTA_MODIFIED => Delta::Modified,
+            raw::GIT_DELTA_RENAMED => Delta::Renamed,
+            raw::GIT_DELTA_COPIED => Delta::Copied,
+            raw::GIT_DELTA_IGNORED => Delta::Ignored,
+            raw::GIT_DELTA_UNTRACKED => Delta::Untracked,
+            raw::GIT_DELTA_TYPECHANGE => Delta::Typechange,
+            raw::GIT_DELTA_UNREADABLE => Delta::Unreadable,
+            raw::GIT_DELTA_CONFLICTED => Delta::Conflicted,
+            n => panic!("unknown diff status: {}", n),
+        }
+    }
+
+    /// Return the file which represents the "from" side of the diff.
+    ///
+    /// What side this means depends on the function that was used to generate
+    /// the diff and will be documented on the function itself.
+    pub fn old_file(&self) -> DiffFile<'a> {
+        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
+    }
+
+    /// Return the file which represents the "to" side of the diff.
+    ///
+    /// What side this means depends on the function that was used to generate
+    /// the diff and will be documented on the function itself.
+    pub fn new_file(&self) -> DiffFile<'a> {
+        unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
+    }
+}
+
+impl<'a> Binding for DiffDelta<'a> {
+    type Raw = *mut raw::git_diff_delta;
+    unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> {
+        DiffDelta {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_diff_delta {
+        self.raw
+    }
+}
+
+impl<'a> std::fmt::Debug for DiffDelta<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("DiffDelta")
+            .field("nfiles", &self.nfiles())
+            .field("status", &self.status())
+            .field("old_file", &self.old_file())
+            .field("new_file", &self.new_file())
+            .finish()
+    }
+}
+
+impl<'a> DiffFile<'a> {
+    /// Returns the Oid of this item.
+    ///
+    /// If this entry represents an absent side of a diff (e.g. the `old_file`
+    /// of a `Added` delta), then the oid returned will be zeroes.
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
+    }
+
+    /// Returns the path, in bytes, of the entry relative to the working
+    /// directory of the repository.
+    pub fn path_bytes(&self) -> Option<&'a [u8]> {
+        static FOO: () = ();
+        unsafe { crate::opt_bytes(&FOO, (*self.raw).path) }
+    }
+
+    /// Returns the path of the entry relative to the working directory of the
+    /// repository.
+    pub fn path(&self) -> Option<&'a Path> {
+        self.path_bytes().map(util::bytes2path)
+    }
+
+    /// Returns the size of this entry, in bytes
+    pub fn size(&self) -> u64 {
+        unsafe { (*self.raw).size as u64 }
+    }
+
+    /// Returns `true` if file(s) are treated as binary data.
+    pub fn is_binary(&self) -> bool {
+        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 }
+    }
+
+    /// Returns `true` if file(s) are treated as text data.
+    pub fn is_not_binary(&self) -> bool {
+        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 }
+    }
+
+    /// Returns `true` if `id` value is known correct.
+    pub fn is_valid_id(&self) -> bool {
+        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 }
+    }
+
+    /// Returns `true` if file exists at this side of the delta.
+    pub fn exists(&self) -> bool {
+        unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 }
+    }
+
+    /// Returns file mode.
+    pub fn mode(&self) -> FileMode {
+        match unsafe { (*self.raw).mode.into() } {
+            raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable,
+            raw::GIT_FILEMODE_TREE => FileMode::Tree,
+            raw::GIT_FILEMODE_BLOB => FileMode::Blob,
+            raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE => FileMode::BlobGroupWritable,
+            raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable,
+            raw::GIT_FILEMODE_LINK => FileMode::Link,
+            raw::GIT_FILEMODE_COMMIT => FileMode::Commit,
+            mode => panic!("unknown mode: {}", mode),
+        }
+    }
+}
+
+impl<'a> Binding for DiffFile<'a> {
+    type Raw = *const raw::git_diff_file;
+    unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> {
+        DiffFile {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_diff_file {
+        self.raw
+    }
+}
+
+impl<'a> std::fmt::Debug for DiffFile<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("DiffFile");
+        ds.field("id", &self.id());
+        if let Some(path_bytes) = &self.path_bytes() {
+            ds.field("path_bytes", path_bytes);
+        }
+        if let Some(path) = &self.path() {
+            ds.field("path", path);
+        }
+        ds.field("size", &self.size()).finish()
+    }
+}
+
+impl Default for DiffOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl DiffOptions {
+    /// Creates a new set of empty diff options.
+    ///
+    /// All flags and other options are defaulted to false or their otherwise
+    /// zero equivalents.
+    pub fn new() -> DiffOptions {
+        let mut opts = DiffOptions {
+            pathspec: Vec::new(),
+            pathspec_ptrs: Vec::new(),
+            raw: unsafe { mem::zeroed() },
+            old_prefix: None,
+            new_prefix: None,
+        };
+        assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0);
+        opts
+    }
+
+    fn flag(&mut self, opt: raw::git_diff_option_t, val: bool) -> &mut DiffOptions {
+        let opt = opt as u32;
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Flag indicating whether the sides of the diff will be reversed.
+    pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_REVERSE, reverse)
+    }
+
+    /// Flag indicating whether ignored files are included.
+    pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include)
+    }
+
+    /// Flag indicating whether ignored directories are traversed deeply or not.
+    pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse)
+    }
+
+    /// Flag indicating whether untracked files are in the diff
+    pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include)
+    }
+
+    /// Flag indicating whether untracked directories are traversed deeply or
+    /// not.
+    pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse)
+    }
+
+    /// Flag indicating whether unmodified files are in the diff.
+    pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include)
+    }
+
+    /// If enabled, then Typechange delta records are generated.
+    pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include)
+    }
+
+    /// Event with `include_typechange`, the tree returned generally shows a
+    /// deleted blob. This flag correctly labels the tree transitions as a
+    /// typechange record with the `new_file`'s mode set to tree.
+    ///
+    /// Note that the tree SHA will not be available.
+    pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include)
+    }
+
+    /// Flag indicating whether file mode changes are ignored.
+    pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore)
+    }
+
+    /// Flag indicating whether all submodules should be treated as unmodified.
+    pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore)
+    }
+
+    /// Flag indicating whether case insensitive filenames should be used.
+    pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore)
+    }
+
+    /// If pathspecs are specified, this flag means that they should be applied
+    /// as an exact match instead of a fnmatch pattern.
+    pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable)
+    }
+
+    /// Disable updating the `binary` flag in delta records. This is useful when
+    /// iterating over a diff if you don't need hunk and data callbacks and want
+    /// to avoid having to load a file completely.
+    pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip)
+    }
+
+    /// When diff finds an untracked directory, to match the behavior of core
+    /// Git, it scans the contents for ignored and untracked files. If all
+    /// contents are ignored, then the directory is ignored; if any contents are
+    /// not ignored, then the directory is untracked. This is extra work that
+    /// may not matter in many cases.
+    ///
+    /// This flag turns off that scan and immediately labels an untracked
+    /// directory as untracked (changing the behavior to not match core git).
+    pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable)
+    }
+
+    /// When diff finds a file in the working directory with stat information
+    /// different from the index, but the OID ends up being the same, write the
+    /// correct stat information into the index. Note: without this flag, diff
+    /// will always leave the index untouched.
+    pub fn update_index(&mut self, update: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_UPDATE_INDEX, update)
+    }
+
+    /// Include unreadable files in the diff
+    pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include)
+    }
+
+    /// Include unreadable files in the diff as untracked files
+    pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
+    }
+
+    /// Treat all files as text, disabling binary attributes and detection.
+    pub fn force_text(&mut self, force: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_FORCE_TEXT, force)
+    }
+
+    /// Treat all files as binary, disabling text diffs
+    pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_FORCE_BINARY, force)
+    }
+
+    /// Ignore all whitespace
+    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore)
+    }
+
+    /// Ignore changes in the amount of whitespace
+    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore)
+    }
+
+    /// Ignore whitespace at the end of line
+    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore)
+    }
+
+    /// Ignore blank lines
+    pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore)
+    }
+
+    /// When generating patch text, include the content of untracked files.
+    ///
+    /// This automatically turns on `include_untracked` but it does not turn on
+    /// `recurse_untracked_dirs`. Add that flag if you want the content of every
+    /// single untracked file.
+    pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show)
+    }
+
+    /// When generating output, include the names of unmodified files if they
+    /// are included in the `Diff`. Normally these are skipped in the formats
+    /// that list files (e.g. name-only, name-status, raw). Even with this these
+    /// will not be included in the patch format.
+    pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show)
+    }
+
+    /// Use the "patience diff" algorithm
+    pub fn patience(&mut self, patience: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_PATIENCE, patience)
+    }
+
+    /// Take extra time to find the minimal diff
+    pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_MINIMAL, minimal)
+    }
+
+    /// Include the necessary deflate/delta information so that `git-apply` can
+    /// apply given diff information to binary files.
+    pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_SHOW_BINARY, show)
+    }
+
+    /// Use a heuristic that takes indentation and whitespace into account
+    /// which generally can produce better diffs when dealing with ambiguous
+    /// diff hunks.
+    pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions {
+        self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic)
+    }
+
+    /// Set the number of unchanged lines that define the boundary of a hunk
+    /// (and to display before and after).
+    ///
+    /// The default value for this is 3.
+    pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions {
+        self.raw.context_lines = lines;
+        self
+    }
+
+    /// Set the maximum number of unchanged lines between hunk boundaries before
+    /// the hunks will be merged into one.
+    ///
+    /// The default value for this is 0.
+    pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions {
+        self.raw.interhunk_lines = lines;
+        self
+    }
+
+    /// The default value for this is `core.abbrev` or 7 if unset.
+    pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions {
+        self.raw.id_abbrev = abbrev;
+        self
+    }
+
+    /// Maximum size (in bytes) above which a blob will be marked as binary
+    /// automatically.
+    ///
+    /// A negative value will disable this entirely.
+    ///
+    /// The default value for this is 512MB.
+    pub fn max_size(&mut self, size: i64) -> &mut DiffOptions {
+        self.raw.max_size = size as raw::git_off_t;
+        self
+    }
+
+    /// The virtual "directory" to prefix old file names with in hunk headers.
+    ///
+    /// The default value for this is "a".
+    pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
+        self.old_prefix = Some(t.into_c_string().unwrap());
+        self
+    }
+
+    /// The virtual "directory" to prefix new file names with in hunk headers.
+    ///
+    /// The default value for this is "b".
+    pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions {
+        self.new_prefix = Some(t.into_c_string().unwrap());
+        self
+    }
+
+    /// Add to the array of paths/fnmatch patterns to constrain the diff.
+    pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions {
+        let s = util::cstring_to_repo_path(pathspec).unwrap();
+        self.pathspec_ptrs.push(s.as_ptr());
+        self.pathspec.push(s);
+        self
+    }
+
+    /// Acquire a pointer to the underlying raw options.
+    ///
+    /// This function is unsafe as the pointer is only valid so long as this
+    /// structure is not moved, modified, or used elsewhere.
+    pub unsafe fn raw(&mut self) -> *const raw::git_diff_options {
+        self.raw.old_prefix = self
+            .old_prefix
+            .as_ref()
+            .map(|s| s.as_ptr())
+            .unwrap_or(ptr::null());
+        self.raw.new_prefix = self
+            .new_prefix
+            .as_ref()
+            .map(|s| s.as_ptr())
+            .unwrap_or(ptr::null());
+        self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t;
+        self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _;
+        &self.raw as *const _
+    }
+
+    // TODO: expose ignore_submodules, notify_cb/notify_payload
+}
+
+impl<'diff> Iterator for Deltas<'diff> {
+    type Item = DiffDelta<'diff>;
+    fn next(&mut self) -> Option<DiffDelta<'diff>> {
+        self.range.next().and_then(|i| self.diff.get_delta(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'diff> DoubleEndedIterator for Deltas<'diff> {
+    fn next_back(&mut self) -> Option<DiffDelta<'diff>> {
+        self.range.next_back().and_then(|i| self.diff.get_delta(i))
+    }
+}
+impl<'diff> FusedIterator for Deltas<'diff> {}
+
+impl<'diff> ExactSizeIterator for Deltas<'diff> {}
+
+/// Line origin constants.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum DiffLineType {
+    /// These values will be sent to `git_diff_line_cb` along with the line
+    Context,
+    ///
+    Addition,
+    ///
+    Deletion,
+    /// Both files have no LF at end
+    ContextEOFNL,
+    /// Old has no LF at end, new does
+    AddEOFNL,
+    /// Old has LF at end, new does not
+    DeleteEOFNL,
+    /// The following values will only be sent to a `git_diff_line_cb` when
+    /// the content of a diff is being formatted through `git_diff_print`.
+    FileHeader,
+    ///
+    HunkHeader,
+    /// For "Binary files x and y differ"
+    Binary,
+}
+
+impl Binding for DiffLineType {
+    type Raw = raw::git_diff_line_t;
+    unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self {
+        match raw {
+            raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context,
+            raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition,
+            raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion,
+            raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL,
+            raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL,
+            raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL,
+            raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader,
+            raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader,
+            raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary,
+            _ => panic!("Unknown git diff line type"),
+        }
+    }
+    fn raw(&self) -> raw::git_diff_line_t {
+        match *self {
+            DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT,
+            DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION,
+            DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION,
+            DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL,
+            DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL,
+            DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL,
+            DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR,
+            DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR,
+            DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY,
+        }
+    }
+}
+
+impl<'a> DiffLine<'a> {
+    /// Line number in old file or `None` for added line
+    pub fn old_lineno(&self) -> Option<u32> {
+        match unsafe { (*self.raw).old_lineno } {
+            n if n < 0 => None,
+            n => Some(n as u32),
+        }
+    }
+
+    /// Line number in new file or `None` for deleted line
+    pub fn new_lineno(&self) -> Option<u32> {
+        match unsafe { (*self.raw).new_lineno } {
+            n if n < 0 => None,
+            n => Some(n as u32),
+        }
+    }
+
+    /// Number of newline characters in content
+    pub fn num_lines(&self) -> u32 {
+        unsafe { (*self.raw).num_lines as u32 }
+    }
+
+    /// Offset in the original file to the content
+    pub fn content_offset(&self) -> i64 {
+        unsafe { (*self.raw).content_offset as i64 }
+    }
+
+    /// Content of this line as bytes.
+    pub fn content(&self) -> &'a [u8] {
+        unsafe {
+            slice::from_raw_parts(
+                (*self.raw).content as *const u8,
+                (*self.raw).content_len as usize,
+            )
+        }
+    }
+
+    /// origin of this `DiffLine`.
+    ///
+    pub fn origin_value(&self) -> DiffLineType {
+        unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) }
+    }
+
+    /// Sigil showing the origin of this `DiffLine`.
+    ///
+    ///  * ` ` - Line context
+    ///  * `+` - Line addition
+    ///  * `-` - Line deletion
+    ///  * `=` - Context (End of file)
+    ///  * `>` - Add (End of file)
+    ///  * `<` - Remove (End of file)
+    ///  * `F` - File header
+    ///  * `H` - Hunk header
+    ///  * `B` - Line binary
+    pub fn origin(&self) -> char {
+        match unsafe { (*self.raw).origin as raw::git_diff_line_t } {
+            raw::GIT_DIFF_LINE_CONTEXT => ' ',
+            raw::GIT_DIFF_LINE_ADDITION => '+',
+            raw::GIT_DIFF_LINE_DELETION => '-',
+            raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=',
+            raw::GIT_DIFF_LINE_ADD_EOFNL => '>',
+            raw::GIT_DIFF_LINE_DEL_EOFNL => '<',
+            raw::GIT_DIFF_LINE_FILE_HDR => 'F',
+            raw::GIT_DIFF_LINE_HUNK_HDR => 'H',
+            raw::GIT_DIFF_LINE_BINARY => 'B',
+            _ => ' ',
+        }
+    }
+}
+
+impl<'a> Binding for DiffLine<'a> {
+    type Raw = *const raw::git_diff_line;
+    unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> {
+        DiffLine {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_diff_line {
+        self.raw
+    }
+}
+
+impl<'a> std::fmt::Debug for DiffLine<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("DiffLine");
+        if let Some(old_lineno) = &self.old_lineno() {
+            ds.field("old_lineno", old_lineno);
+        }
+        if let Some(new_lineno) = &self.new_lineno() {
+            ds.field("new_lineno", new_lineno);
+        }
+        ds.field("num_lines", &self.num_lines())
+            .field("content_offset", &self.content_offset())
+            .field("content", &self.content())
+            .field("origin", &self.origin())
+            .finish()
+    }
+}
+
+impl<'a> DiffHunk<'a> {
+    /// Starting line number in old_file
+    pub fn old_start(&self) -> u32 {
+        unsafe { (*self.raw).old_start as u32 }
+    }
+
+    /// Number of lines in old_file
+    pub fn old_lines(&self) -> u32 {
+        unsafe { (*self.raw).old_lines as u32 }
+    }
+
+    /// Starting line number in new_file
+    pub fn new_start(&self) -> u32 {
+        unsafe { (*self.raw).new_start as u32 }
+    }
+
+    /// Number of lines in new_file
+    pub fn new_lines(&self) -> u32 {
+        unsafe { (*self.raw).new_lines as u32 }
+    }
+
+    /// Header text
+    pub fn header(&self) -> &'a [u8] {
+        unsafe {
+            slice::from_raw_parts(
+                (*self.raw).header.as_ptr() as *const u8,
+                (*self.raw).header_len as usize,
+            )
+        }
+    }
+}
+
+impl<'a> Binding for DiffHunk<'a> {
+    type Raw = *const raw::git_diff_hunk;
+    unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> {
+        DiffHunk {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_diff_hunk {
+        self.raw
+    }
+}
+
+impl<'a> std::fmt::Debug for DiffHunk<'a> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("DiffHunk")
+            .field("old_start", &self.old_start())
+            .field("old_lines", &self.old_lines())
+            .field("new_start", &self.new_start())
+            .field("new_lines", &self.new_lines())
+            .field("header", &self.header())
+            .finish()
+    }
+}
+
+impl DiffStats {
+    /// Get the total number of files changed in a diff.
+    pub fn files_changed(&self) -> usize {
+        unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize }
+    }
+
+    /// Get the total number of insertions in a diff
+    pub fn insertions(&self) -> usize {
+        unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize }
+    }
+
+    /// Get the total number of deletions in a diff
+    pub fn deletions(&self) -> usize {
+        unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize }
+    }
+
+    /// Print diff statistics to a Buf
+    pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> {
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_diff_stats_to_buf(
+                buf.raw(),
+                self.raw,
+                format.bits(),
+                width as size_t
+            ));
+        }
+        Ok(buf)
+    }
+}
+
+impl Binding for DiffStats {
+    type Raw = *mut raw::git_diff_stats;
+
+    unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats {
+        DiffStats { raw }
+    }
+    fn raw(&self) -> *mut raw::git_diff_stats {
+        self.raw
+    }
+}
+
+impl Drop for DiffStats {
+    fn drop(&mut self) {
+        unsafe { raw::git_diff_stats_free(self.raw) }
+    }
+}
+
+impl std::fmt::Debug for DiffStats {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("DiffStats")
+            .field("files_changed", &self.files_changed())
+            .field("insertions", &self.insertions())
+            .field("deletions", &self.deletions())
+            .finish()
+    }
+}
+
+impl<'a> DiffBinary<'a> {
+    /// Returns whether there is data in this binary structure or not.
+    ///
+    /// If this is `true`, then this was produced and included binary content.
+    /// If this is `false` then this was generated knowing only that a binary
+    /// file changed but without providing the data, probably from a patch that
+    /// said `Binary files a/file.txt and b/file.txt differ`.
+    pub fn contains_data(&self) -> bool {
+        unsafe { (*self.raw).contains_data == 1 }
+    }
+
+    /// The contents of the old file.
+    pub fn old_file(&self) -> DiffBinaryFile<'a> {
+        unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) }
+    }
+
+    /// The contents of the new file.
+    pub fn new_file(&self) -> DiffBinaryFile<'a> {
+        unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) }
+    }
+}
+
+impl<'a> Binding for DiffBinary<'a> {
+    type Raw = *const raw::git_diff_binary;
+    unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> {
+        DiffBinary {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_diff_binary {
+        self.raw
+    }
+}
+
+impl<'a> DiffBinaryFile<'a> {
+    /// The type of binary data for this file
+    pub fn kind(&self) -> DiffBinaryKind {
+        unsafe { Binding::from_raw((*self.raw).kind) }
+    }
+
+    /// The binary data, deflated
+    pub fn data(&self) -> &[u8] {
+        unsafe {
+            slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize)
+        }
+    }
+
+    /// The length of the binary data after inflation
+    pub fn inflated_len(&self) -> usize {
+        unsafe { (*self.raw).inflatedlen as usize }
+    }
+}
+
+impl<'a> Binding for DiffBinaryFile<'a> {
+    type Raw = *const raw::git_diff_binary_file;
+    unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> {
+        DiffBinaryFile {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_diff_binary_file {
+        self.raw
+    }
+}
+
+impl Binding for DiffBinaryKind {
+    type Raw = raw::git_diff_binary_t;
+    unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind {
+        match raw {
+            raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None,
+            raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal,
+            raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta,
+            _ => panic!("Unknown git diff binary kind"),
+        }
+    }
+    fn raw(&self) -> raw::git_diff_binary_t {
+        match *self {
+            DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE,
+            DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL,
+            DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA,
+        }
+    }
+}
+
+impl Default for DiffFindOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl DiffFindOptions {
+    /// Creates a new set of empty diff find options.
+    ///
+    /// All flags and other options are defaulted to false or their otherwise
+    /// zero equivalents.
+    pub fn new() -> DiffFindOptions {
+        let mut opts = DiffFindOptions {
+            raw: unsafe { mem::zeroed() },
+        };
+        assert_eq!(
+            unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) },
+            0
+        );
+        opts
+    }
+
+    fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions {
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Reset all flags back to their unset state, indicating that
+    /// `diff.renames` should be used instead. This is overridden once any flag
+    /// is set.
+    pub fn by_config(&mut self) -> &mut DiffFindOptions {
+        self.flag(0xffffffff, false)
+    }
+
+    /// Look for renames?
+    pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_RENAMES, find)
+    }
+
+    /// Consider old side of modified for renames?
+    pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find)
+    }
+
+    /// Look for copies?
+    pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_COPIES, find)
+    }
+
+    /// Consider unmodified as copy sources?
+    ///
+    /// For this to work correctly, use `include_unmodified` when the initial
+    /// diff is being generated.
+    pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find)
+    }
+
+    /// Mark significant rewrites for split.
+    pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_REWRITES, find)
+    }
+
+    /// Actually split large rewrites into delete/add pairs
+    pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_BREAK_REWRITES, find)
+    }
+
+    #[doc(hidden)]
+    pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.break_rewrites(find)
+    }
+
+    /// Find renames/copies for untracked items in working directory.
+    ///
+    /// For this to work correctly use the `include_untracked` option when the
+    /// initial diff is being generated.
+    pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find)
+    }
+
+    /// Turn on all finding features.
+    pub fn all(&mut self, find: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_ALL, find)
+    }
+
+    /// Measure similarity ignoring leading whitespace (default)
+    pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore)
+    }
+
+    /// Measure similarity ignoring all whitespace
+    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore)
+    }
+
+    /// Measure similarity including all data
+    pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont)
+    }
+
+    /// Measure similarity only by comparing SHAs (fast and cheap)
+    pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact)
+    }
+
+    /// Do not break rewrites unless they contribute to a rename.
+    ///
+    /// Normally, `break_rewrites` and `rewrites` will measure the
+    /// self-similarity of modified files and split the ones that have changed a
+    /// lot into a delete/add pair.  Then the sides of that pair will be
+    /// considered candidates for rename and copy detection
+    ///
+    /// If you add this flag in and the split pair is not used for an actual
+    /// rename or copy, then the modified record will be restored to a regular
+    /// modified record instead of being split.
+    pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b)
+    }
+
+    /// Remove any unmodified deltas after find_similar is done.
+    ///
+    /// Using `copies_from_unmodified` to emulate the `--find-copies-harder`
+    /// behavior requires building a diff with the `include_unmodified` flag. If
+    /// you do not want unmodified records in the final result, pas this flag to
+    /// have them removed.
+    pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions {
+        self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove)
+    }
+
+    /// Similarity to consider a file renamed (default 50)
+    pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
+        self.raw.rename_threshold = thresh;
+        self
+    }
+
+    /// Similarity of modified to be eligible rename source (default 50)
+    pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
+        self.raw.rename_from_rewrite_threshold = thresh;
+        self
+    }
+
+    /// Similarity to consider a file copy (default 50)
+    pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
+        self.raw.copy_threshold = thresh;
+        self
+    }
+
+    /// Similarity to split modify into delete/add pair (default 60)
+    pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions {
+        self.raw.break_rewrite_threshold = thresh;
+        self
+    }
+
+    /// Maximum similarity sources to examine for a file (somewhat like
+    /// git-diff's `-l` option or `diff.renameLimit` config)
+    ///
+    /// Defaults to 200
+    pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions {
+        self.raw.rename_limit = limit as size_t;
+        self
+    }
+
+    // TODO: expose git_diff_similarity_metric
+
+    /// Acquire a pointer to the underlying raw options.
+    pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options {
+        &self.raw
+    }
+}
+
+impl Default for DiffFormatEmailOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl DiffFormatEmailOptions {
+    /// Creates a new set of email options,
+    /// initialized to the default values
+    pub fn new() -> Self {
+        let mut opts = DiffFormatEmailOptions {
+            raw: unsafe { mem::zeroed() },
+        };
+        assert_eq!(
+            unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) },
+            0
+        );
+        opts
+    }
+
+    fn flag(&mut self, opt: u32, val: bool) -> &mut Self {
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Exclude `[PATCH]` from the subject header
+    pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self {
+        self.flag(
+            raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER,
+            should_exclude,
+        )
+    }
+}
+
+impl DiffPatchidOptions {
+    /// Creates a new set of patchid options,
+    /// initialized to the default values
+    pub fn new() -> Self {
+        let mut opts = DiffPatchidOptions {
+            raw: unsafe { mem::zeroed() },
+        };
+        assert_eq!(
+            unsafe {
+                raw::git_diff_patchid_options_init(
+                    &mut opts.raw,
+                    raw::GIT_DIFF_PATCHID_OPTIONS_VERSION,
+                )
+            },
+            0
+        );
+        opts
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{DiffLineType, DiffOptions, Oid, Signature, Time};
+    use std::borrow::Borrow;
+    use std::fs::File;
+    use std::io::Write;
+    use std::path::Path;
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let diff = repo.diff_tree_to_workdir(None, None).unwrap();
+        assert_eq!(diff.deltas().len(), 0);
+        let stats = diff.stats().unwrap();
+        assert_eq!(stats.insertions(), 0);
+        assert_eq!(stats.deletions(), 0);
+        assert_eq!(stats.files_changed(), 0);
+        let patchid = diff.patchid(None).unwrap();
+        assert_ne!(patchid, Oid::zero());
+    }
+
+    #[test]
+    fn foreach_smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let diff = t!(repo.diff_tree_to_workdir(None, None));
+        let mut count = 0;
+        t!(diff.foreach(
+            &mut |_file, _progress| {
+                count = count + 1;
+                true
+            },
+            None,
+            None,
+            None
+        ));
+        assert_eq!(count, 0);
+    }
+
+    #[test]
+    fn foreach_file_only() {
+        let path = Path::new("foo");
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
+        let mut opts = DiffOptions::new();
+        opts.include_untracked(true);
+        let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts)));
+        let mut count = 0;
+        let mut result = None;
+        t!(diff.foreach(
+            &mut |file, _progress| {
+                count = count + 1;
+                result = file.new_file().path().map(ToOwned::to_owned);
+                true
+            },
+            None,
+            None,
+            None
+        ));
+        assert_eq!(result.as_ref().map(Borrow::borrow), Some(path));
+        assert_eq!(count, 1);
+    }
+
+    #[test]
+    fn foreach_file_and_hunk() {
+        let path = Path::new("foo");
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(&td.path().join(path))).write_all(b"bar"));
+        let mut index = t!(repo.index());
+        t!(index.add_path(path));
+        let mut opts = DiffOptions::new();
+        opts.include_untracked(true);
+        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
+        let mut new_lines = 0;
+        t!(diff.foreach(
+            &mut |_file, _progress| { true },
+            None,
+            Some(&mut |_file, hunk| {
+                new_lines = hunk.new_lines();
+                true
+            }),
+            None
+        ));
+        assert_eq!(new_lines, 1);
+    }
+
+    #[test]
+    fn foreach_all_callbacks() {
+        let fib = vec![0, 1, 1, 2, 3, 5, 8];
+        // Verified with a node implementation of deflate, might be worth
+        // adding a deflate lib to do this inline here.
+        let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21];
+        let foo_path = Path::new("foo");
+        let bin_path = Path::new("bin");
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
+        t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib));
+        let mut index = t!(repo.index());
+        t!(index.add_path(foo_path));
+        t!(index.add_path(bin_path));
+        let mut opts = DiffOptions::new();
+        opts.include_untracked(true).show_binary(true);
+        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
+        let mut bin_content = None;
+        let mut new_lines = 0;
+        let mut line_content = None;
+        t!(diff.foreach(
+            &mut |_file, _progress| { true },
+            Some(&mut |_file, binary| {
+                bin_content = Some(binary.new_file().data().to_owned());
+                true
+            }),
+            Some(&mut |_file, hunk| {
+                new_lines = hunk.new_lines();
+                true
+            }),
+            Some(&mut |_file, _hunk, line| {
+                line_content = String::from_utf8(line.content().into()).ok();
+                true
+            })
+        ));
+        assert_eq!(bin_content, Some(deflated_fib));
+        assert_eq!(new_lines, 1);
+        assert_eq!(line_content, Some("bar\n".to_string()));
+    }
+
+    #[test]
+    fn format_email_simple() {
+        let (_td, repo) = crate::test::repo_init();
+        const COMMIT_MESSAGE: &str = "Modify some content";
+        const EXPECTED_EMAIL_START: &str = concat!(
+            "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n",
+            "From: Techcable <dummy@dummy.org>\n",
+            "Date: Tue, 11 Jan 1972 17:46:40 +0000\n",
+            "Subject: [PATCH] Modify some content\n",
+            "\n",
+            "---\n",
+            " file1.txt | 8 +++++---\n",
+            " 1 file changed, 5 insertions(+), 3 deletions(-)\n",
+            "\n",
+            "diff --git a/file1.txt b/file1.txt\n",
+            "index 94aaae8..af8f41d 100644\n",
+            "--- a/file1.txt\n",
+            "+++ b/file1.txt\n",
+            "@@ -1,15 +1,17 @@\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            "+_file1.txt_\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            "+\n",
+            "+\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            " file1.txt\n",
+            "-file1.txt\n",
+            "-file1.txt\n",
+            "-file1.txt\n",
+            "+_file1.txt_\n",
+            "+_file1.txt_\n",
+            " file1.txt\n",
+            "--\n"
+        );
+        const ORIGINAL_FILE: &str = concat!(
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n"
+        );
+        const UPDATED_FILE: &str = concat!(
+            "file1.txt\n",
+            "file1.txt\n",
+            "_file1.txt_\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "\n",
+            "\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "file1.txt\n",
+            "_file1.txt_\n",
+            "_file1.txt_\n",
+            "file1.txt\n"
+        );
+        const FILE_MODE: i32 = 0o100644;
+        let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap();
+        let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap();
+        let mut original_tree = repo.treebuilder(None).unwrap();
+        original_tree
+            .insert("file1.txt", original_file, FILE_MODE)
+            .unwrap();
+        let original_tree = original_tree.write().unwrap();
+        let mut updated_tree = repo.treebuilder(None).unwrap();
+        updated_tree
+            .insert("file1.txt", updated_file, FILE_MODE)
+            .unwrap();
+        let updated_tree = updated_tree.write().unwrap();
+        let time = Time::new(64_000_000, 0);
+        let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap();
+        let updated_commit = repo
+            .commit(
+                None,
+                &author,
+                &author,
+                COMMIT_MESSAGE,
+                &repo.find_tree(updated_tree).unwrap(),
+                &[], // NOTE: Have no parents to ensure stable hash
+            )
+            .unwrap();
+        let updated_commit = repo.find_commit(updated_commit).unwrap();
+        let mut diff = repo
+            .diff_tree_to_tree(
+                Some(&repo.find_tree(original_tree).unwrap()),
+                Some(&repo.find_tree(updated_tree).unwrap()),
+                None,
+            )
+            .unwrap();
+        #[allow(deprecated)]
+        let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap();
+        let actual_email = actual_email.as_str().unwrap();
+        assert!(
+            actual_email.starts_with(EXPECTED_EMAIL_START),
+            "Unexpected email:\n{}",
+            actual_email
+        );
+        let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines();
+        let version_line = remaining_lines.next();
+        assert!(
+            version_line.unwrap().starts_with("libgit2"),
+            "Invalid version line: {:?}",
+            version_line
+        );
+        while let Some(line) = remaining_lines.next() {
+            assert_eq!(line.trim(), "")
+        }
+    }
+
+    #[test]
+    fn foreach_diff_line_origin_value() {
+        let foo_path = Path::new("foo");
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
+        let mut index = t!(repo.index());
+        t!(index.add_path(foo_path));
+        let mut opts = DiffOptions::new();
+        opts.include_untracked(true);
+        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
+        let mut origin_values: Vec<DiffLineType> = Vec::new();
+        t!(diff.foreach(
+            &mut |_file, _progress| { true },
+            None,
+            None,
+            Some(&mut |_file, _hunk, line| {
+                origin_values.push(line.origin_value());
+                true
+            })
+        ));
+        assert_eq!(origin_values.len(), 1);
+        assert_eq!(origin_values[0], DiffLineType::Addition);
+    }
+
+    #[test]
+    fn foreach_exits_with_euser() {
+        let foo_path = Path::new("foo");
+        let bar_path = Path::new("foo");
+
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n"));
+
+        let mut index = t!(repo.index());
+        t!(index.add_path(foo_path));
+        t!(index.add_path(bar_path));
+
+        let mut opts = DiffOptions::new();
+        opts.include_untracked(true);
+        let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts)));
+
+        let mut calls = 0;
+        let result = diff.foreach(
+            &mut |_file, _progress| {
+                calls += 1;
+                false
+            },
+            None,
+            None,
+            None,
+        );
+
+        assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User);
+    }
+}
diff --git a/git2/src/email.rs b/git2/src/email.rs
new file mode 100644 (file)
index 0000000..d3ebc03
--- /dev/null
@@ -0,0 +1,183 @@
+use std::ffi::CString;
+use std::{mem, ptr};
+
+use crate::util::Binding;
+use crate::{raw, Buf, Commit, DiffFindOptions, DiffOptions, Error, IntoCString};
+use crate::{Diff, Oid, Signature};
+
+/// A structure to represent patch in mbox format for sending via email
+pub struct Email {
+    buf: Buf,
+}
+
+/// Options for controlling the formatting of the generated e-mail.
+pub struct EmailCreateOptions {
+    diff_options: DiffOptions,
+    diff_find_options: DiffFindOptions,
+    subject_prefix: Option<CString>,
+    raw: raw::git_email_create_options,
+}
+
+impl Default for EmailCreateOptions {
+    fn default() -> Self {
+        // Defaults options created in corresponding to `GIT_EMAIL_CREATE_OPTIONS_INIT`
+        let default_options = raw::git_email_create_options {
+            version: raw::GIT_EMAIL_CREATE_OPTIONS_VERSION,
+            flags: raw::GIT_EMAIL_CREATE_DEFAULT as u32,
+            diff_opts: unsafe { mem::zeroed() },
+            diff_find_opts: unsafe { mem::zeroed() },
+            subject_prefix: ptr::null(),
+            start_number: 1,
+            reroll_number: 0,
+        };
+        let mut diff_options = DiffOptions::new();
+        diff_options.show_binary(true).context_lines(3);
+        Self {
+            diff_options,
+            diff_find_options: DiffFindOptions::new(),
+            subject_prefix: None,
+            raw: default_options,
+        }
+    }
+}
+
+impl EmailCreateOptions {
+    /// Creates a new set of email create options
+    ///
+    /// By default, options include rename detection and binary
+    /// diffs to match `git format-patch`.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    fn flag(&mut self, opt: raw::git_email_create_flags_t, val: bool) -> &mut Self {
+        let opt = opt as u32;
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Flag indicating whether patch numbers are included in the subject prefix.
+    pub fn omit_numbers(&mut self, omit: bool) -> &mut Self {
+        self.flag(raw::GIT_EMAIL_CREATE_OMIT_NUMBERS, omit)
+    }
+
+    /// Flag indicating whether numbers included in the subject prefix even when
+    /// the patch is for a single commit (1/1).
+    pub fn always_number(&mut self, always: bool) -> &mut Self {
+        self.flag(raw::GIT_EMAIL_CREATE_ALWAYS_NUMBER, always)
+    }
+
+    /// Flag indicating whether rename or similarity detection are ignored.
+    pub fn ignore_renames(&mut self, ignore: bool) -> &mut Self {
+        self.flag(raw::GIT_EMAIL_CREATE_NO_RENAMES, ignore)
+    }
+
+    /// Get mutable access to `DiffOptions` that are used for creating diffs.
+    pub fn diff_options(&mut self) -> &mut DiffOptions {
+        &mut self.diff_options
+    }
+
+    /// Get mutable access to `DiffFindOptions` that are used for finding
+    /// similarities within diffs.
+    pub fn diff_find_options(&mut self) -> &mut DiffFindOptions {
+        &mut self.diff_find_options
+    }
+
+    /// Set the subject prefix
+    ///
+    /// The default value for this is "PATCH". If set to an empty string ("")
+    /// then only the patch numbers will be shown in the prefix.
+    /// If the subject_prefix is empty and patch numbers are not being shown,
+    /// the prefix will be omitted entirely.
+    pub fn subject_prefix<T: IntoCString>(&mut self, t: T) -> &mut Self {
+        self.subject_prefix = Some(t.into_c_string().unwrap());
+        self
+    }
+
+    /// Set the starting patch number; this cannot be 0.
+    ///
+    /// The default value for this is 1.
+    pub fn start_number(&mut self, number: usize) -> &mut Self {
+        self.raw.start_number = number;
+        self
+    }
+
+    /// Set the "re-roll" number.
+    ///
+    /// The default value for this is 0 (no re-roll).
+    pub fn reroll_number(&mut self, number: usize) -> &mut Self {
+        self.raw.reroll_number = number;
+        self
+    }
+
+    /// Acquire a pointer to the underlying raw options.
+    ///
+    /// This function is unsafe as the pointer is only valid so long as this
+    /// structure is not moved, modified, or used elsewhere.
+    unsafe fn raw(&mut self) -> *const raw::git_email_create_options {
+        self.raw.subject_prefix = self
+            .subject_prefix
+            .as_ref()
+            .map(|s| s.as_ptr())
+            .unwrap_or(ptr::null());
+        self.raw.diff_opts = ptr::read(self.diff_options.raw());
+        self.raw.diff_find_opts = ptr::read(self.diff_find_options.raw());
+        &self.raw as *const _
+    }
+}
+
+impl Email {
+    /// Returns a byte slice with stored e-mail patch in. `Email` could be
+    /// created by one of the `from_*` functions.
+    pub fn as_slice(&self) -> &[u8] {
+        &self.buf
+    }
+
+    /// Create a diff for a commit in mbox format for sending via email.
+    pub fn from_diff<T: IntoCString>(
+        diff: &Diff<'_>,
+        patch_idx: usize,
+        patch_count: usize,
+        commit_id: &Oid,
+        summary: T,
+        body: T,
+        author: &Signature<'_>,
+        opts: &mut EmailCreateOptions,
+    ) -> Result<Self, Error> {
+        let buf = Buf::new();
+        let summary = summary.into_c_string()?;
+        let body = body.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_email_create_from_diff(
+                buf.raw(),
+                Binding::raw(diff),
+                patch_idx,
+                patch_count,
+                Binding::raw(commit_id),
+                summary.as_ptr(),
+                body.as_ptr(),
+                Binding::raw(author),
+                opts.raw()
+            ));
+            Ok(Self { buf })
+        }
+    }
+
+    /// Create a diff for a commit in mbox format for sending via email.
+    /// The commit must not be a merge commit.
+    pub fn from_commit(commit: &Commit<'_>, opts: &mut EmailCreateOptions) -> Result<Self, Error> {
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_email_create_from_commit(
+                buf.raw(),
+                commit.raw(),
+                opts.raw()
+            ));
+            Ok(Self { buf })
+        }
+    }
+}
diff --git a/git2/src/error.rs b/git2/src/error.rs
new file mode 100644 (file)
index 0000000..076667a
--- /dev/null
@@ -0,0 +1,408 @@
+use libc::c_int;
+use std::env::JoinPathsError;
+use std::error;
+use std::ffi::{CStr, CString, NulError};
+use std::fmt;
+use std::str;
+
+use crate::{raw, ErrorClass, ErrorCode};
+
+/// A structure to represent errors coming out of libgit2.
+#[derive(Debug, PartialEq)]
+pub struct Error {
+    code: c_int,
+    klass: c_int,
+    message: Box<str>,
+}
+
+impl Error {
+    /// Creates a new error.
+    ///
+    /// This is mainly intended for implementers of custom transports or
+    /// database backends, where it is desirable to propagate an [`Error`]
+    /// through `libgit2`.
+    pub fn new<S: AsRef<str>>(code: ErrorCode, class: ErrorClass, message: S) -> Self {
+        let mut err = Error::from_str(message.as_ref());
+        err.set_code(code);
+        err.set_class(class);
+        err
+    }
+
+    /// Returns the last error that happened with the code specified by `code`.
+    ///
+    /// The `code` argument typically comes from the return value of a function
+    /// call. This code will later be returned from the `code` function.
+    pub fn last_error(code: c_int) -> Error {
+        crate::init();
+        unsafe {
+            // Note that whenever libgit2 returns an error any negative value
+            // indicates that an error happened. Auxiliary information is
+            // *usually* in `git_error_last` but unfortunately that's not always
+            // the case. Sometimes a negative error code is returned from
+            // libgit2 *without* calling `git_error_set` internally to configure
+            // the error.
+            //
+            // To handle this case and hopefully provide better error messages
+            // on our end we unconditionally call `git_error_clear` when we're done
+            // with an error. This is an attempt to clear it as aggressively as
+            // possible when we can to ensure that error information from one
+            // api invocation doesn't leak over to the next api invocation.
+            //
+            // Additionally if `git_error_last` returns null then we returned a
+            // canned error out.
+            let ptr = raw::git_error_last();
+            let err = if ptr.is_null() {
+                let mut error = Error::from_str("an unknown git error occurred");
+                error.code = code;
+                error
+            } else {
+                Error::from_raw(code, ptr)
+            };
+            raw::git_error_clear();
+            err
+        }
+    }
+
+    unsafe fn from_raw(code: c_int, ptr: *const raw::git_error) -> Error {
+        let message = CStr::from_ptr((*ptr).message as *const _).to_bytes();
+        let message = String::from_utf8_lossy(message).into_owned().into();
+        Error {
+            code,
+            klass: (*ptr).klass,
+            message,
+        }
+    }
+
+    /// Creates a new error from the given string as the error.
+    ///
+    /// The error returned will have the code `GIT_ERROR` and the class
+    /// `GIT_ERROR_NONE`.
+    pub fn from_str(s: &str) -> Error {
+        Error {
+            code: raw::GIT_ERROR as c_int,
+            klass: raw::GIT_ERROR_NONE as c_int,
+            message: s.into(),
+        }
+    }
+
+    /// Return the error code associated with this error.
+    ///
+    /// An error code is intended to be programmatically actionable most of the
+    /// time. For example the code `GIT_EAGAIN` indicates that an error could be
+    /// fixed by trying again, while the code `GIT_ERROR` is more bland and
+    /// doesn't convey anything in particular.
+    pub fn code(&self) -> ErrorCode {
+        match self.raw_code() {
+            raw::GIT_OK => super::ErrorCode::GenericError,
+            raw::GIT_ERROR => super::ErrorCode::GenericError,
+            raw::GIT_ENOTFOUND => super::ErrorCode::NotFound,
+            raw::GIT_EEXISTS => super::ErrorCode::Exists,
+            raw::GIT_EAMBIGUOUS => super::ErrorCode::Ambiguous,
+            raw::GIT_EBUFS => super::ErrorCode::BufSize,
+            raw::GIT_EUSER => super::ErrorCode::User,
+            raw::GIT_EBAREREPO => super::ErrorCode::BareRepo,
+            raw::GIT_EUNBORNBRANCH => super::ErrorCode::UnbornBranch,
+            raw::GIT_EUNMERGED => super::ErrorCode::Unmerged,
+            raw::GIT_ENONFASTFORWARD => super::ErrorCode::NotFastForward,
+            raw::GIT_EINVALIDSPEC => super::ErrorCode::InvalidSpec,
+            raw::GIT_ECONFLICT => super::ErrorCode::Conflict,
+            raw::GIT_ELOCKED => super::ErrorCode::Locked,
+            raw::GIT_EMODIFIED => super::ErrorCode::Modified,
+            raw::GIT_PASSTHROUGH => super::ErrorCode::GenericError,
+            raw::GIT_ITEROVER => super::ErrorCode::GenericError,
+            raw::GIT_EAUTH => super::ErrorCode::Auth,
+            raw::GIT_ECERTIFICATE => super::ErrorCode::Certificate,
+            raw::GIT_EAPPLIED => super::ErrorCode::Applied,
+            raw::GIT_EPEEL => super::ErrorCode::Peel,
+            raw::GIT_EEOF => super::ErrorCode::Eof,
+            raw::GIT_EINVALID => super::ErrorCode::Invalid,
+            raw::GIT_EUNCOMMITTED => super::ErrorCode::Uncommitted,
+            raw::GIT_EDIRECTORY => super::ErrorCode::Directory,
+            raw::GIT_EMERGECONFLICT => super::ErrorCode::MergeConflict,
+            raw::GIT_EMISMATCH => super::ErrorCode::HashsumMismatch,
+            raw::GIT_EINDEXDIRTY => super::ErrorCode::IndexDirty,
+            raw::GIT_EAPPLYFAIL => super::ErrorCode::ApplyFail,
+            raw::GIT_EOWNER => super::ErrorCode::Owner,
+            raw::GIT_TIMEOUT => super::ErrorCode::Timeout,
+            _ => super::ErrorCode::GenericError,
+        }
+    }
+
+    /// Modify the error code associated with this error.
+    ///
+    /// This is mainly intended to be used by implementers of custom transports
+    /// or database backends, and should be used with care.
+    pub fn set_code(&mut self, code: ErrorCode) {
+        self.code = match code {
+            ErrorCode::GenericError => raw::GIT_ERROR,
+            ErrorCode::NotFound => raw::GIT_ENOTFOUND,
+            ErrorCode::Exists => raw::GIT_EEXISTS,
+            ErrorCode::Ambiguous => raw::GIT_EAMBIGUOUS,
+            ErrorCode::BufSize => raw::GIT_EBUFS,
+            ErrorCode::User => raw::GIT_EUSER,
+            ErrorCode::BareRepo => raw::GIT_EBAREREPO,
+            ErrorCode::UnbornBranch => raw::GIT_EUNBORNBRANCH,
+            ErrorCode::Unmerged => raw::GIT_EUNMERGED,
+            ErrorCode::NotFastForward => raw::GIT_ENONFASTFORWARD,
+            ErrorCode::InvalidSpec => raw::GIT_EINVALIDSPEC,
+            ErrorCode::Conflict => raw::GIT_ECONFLICT,
+            ErrorCode::Locked => raw::GIT_ELOCKED,
+            ErrorCode::Modified => raw::GIT_EMODIFIED,
+            ErrorCode::Auth => raw::GIT_EAUTH,
+            ErrorCode::Certificate => raw::GIT_ECERTIFICATE,
+            ErrorCode::Applied => raw::GIT_EAPPLIED,
+            ErrorCode::Peel => raw::GIT_EPEEL,
+            ErrorCode::Eof => raw::GIT_EEOF,
+            ErrorCode::Invalid => raw::GIT_EINVALID,
+            ErrorCode::Uncommitted => raw::GIT_EUNCOMMITTED,
+            ErrorCode::Directory => raw::GIT_EDIRECTORY,
+            ErrorCode::MergeConflict => raw::GIT_EMERGECONFLICT,
+            ErrorCode::HashsumMismatch => raw::GIT_EMISMATCH,
+            ErrorCode::IndexDirty => raw::GIT_EINDEXDIRTY,
+            ErrorCode::ApplyFail => raw::GIT_EAPPLYFAIL,
+            ErrorCode::Owner => raw::GIT_EOWNER,
+            ErrorCode::Timeout => raw::GIT_TIMEOUT,
+        };
+    }
+
+    /// Return the error class associated with this error.
+    ///
+    /// Error classes are in general mostly just informative. For example the
+    /// class will show up in the error message but otherwise an error class is
+    /// typically not directly actionable.
+    pub fn class(&self) -> ErrorClass {
+        match self.raw_class() {
+            raw::GIT_ERROR_NONE => super::ErrorClass::None,
+            raw::GIT_ERROR_NOMEMORY => super::ErrorClass::NoMemory,
+            raw::GIT_ERROR_OS => super::ErrorClass::Os,
+            raw::GIT_ERROR_INVALID => super::ErrorClass::Invalid,
+            raw::GIT_ERROR_REFERENCE => super::ErrorClass::Reference,
+            raw::GIT_ERROR_ZLIB => super::ErrorClass::Zlib,
+            raw::GIT_ERROR_REPOSITORY => super::ErrorClass::Repository,
+            raw::GIT_ERROR_CONFIG => super::ErrorClass::Config,
+            raw::GIT_ERROR_REGEX => super::ErrorClass::Regex,
+            raw::GIT_ERROR_ODB => super::ErrorClass::Odb,
+            raw::GIT_ERROR_INDEX => super::ErrorClass::Index,
+            raw::GIT_ERROR_OBJECT => super::ErrorClass::Object,
+            raw::GIT_ERROR_NET => super::ErrorClass::Net,
+            raw::GIT_ERROR_TAG => super::ErrorClass::Tag,
+            raw::GIT_ERROR_TREE => super::ErrorClass::Tree,
+            raw::GIT_ERROR_INDEXER => super::ErrorClass::Indexer,
+            raw::GIT_ERROR_SSL => super::ErrorClass::Ssl,
+            raw::GIT_ERROR_SUBMODULE => super::ErrorClass::Submodule,
+            raw::GIT_ERROR_THREAD => super::ErrorClass::Thread,
+            raw::GIT_ERROR_STASH => super::ErrorClass::Stash,
+            raw::GIT_ERROR_CHECKOUT => super::ErrorClass::Checkout,
+            raw::GIT_ERROR_FETCHHEAD => super::ErrorClass::FetchHead,
+            raw::GIT_ERROR_MERGE => super::ErrorClass::Merge,
+            raw::GIT_ERROR_SSH => super::ErrorClass::Ssh,
+            raw::GIT_ERROR_FILTER => super::ErrorClass::Filter,
+            raw::GIT_ERROR_REVERT => super::ErrorClass::Revert,
+            raw::GIT_ERROR_CALLBACK => super::ErrorClass::Callback,
+            raw::GIT_ERROR_CHERRYPICK => super::ErrorClass::CherryPick,
+            raw::GIT_ERROR_DESCRIBE => super::ErrorClass::Describe,
+            raw::GIT_ERROR_REBASE => super::ErrorClass::Rebase,
+            raw::GIT_ERROR_FILESYSTEM => super::ErrorClass::Filesystem,
+            raw::GIT_ERROR_PATCH => super::ErrorClass::Patch,
+            raw::GIT_ERROR_WORKTREE => super::ErrorClass::Worktree,
+            raw::GIT_ERROR_SHA1 => super::ErrorClass::Sha1,
+            raw::GIT_ERROR_HTTP => super::ErrorClass::Http,
+            _ => super::ErrorClass::None,
+        }
+    }
+
+    /// Modify the error class associated with this error.
+    ///
+    /// This is mainly intended to be used by implementers of custom transports
+    /// or database backends, and should be used with care.
+    pub fn set_class(&mut self, class: ErrorClass) {
+        self.klass = match class {
+            ErrorClass::None => raw::GIT_ERROR_NONE,
+            ErrorClass::NoMemory => raw::GIT_ERROR_NOMEMORY,
+            ErrorClass::Os => raw::GIT_ERROR_OS,
+            ErrorClass::Invalid => raw::GIT_ERROR_INVALID,
+            ErrorClass::Reference => raw::GIT_ERROR_REFERENCE,
+            ErrorClass::Zlib => raw::GIT_ERROR_ZLIB,
+            ErrorClass::Repository => raw::GIT_ERROR_REPOSITORY,
+            ErrorClass::Config => raw::GIT_ERROR_CONFIG,
+            ErrorClass::Regex => raw::GIT_ERROR_REGEX,
+            ErrorClass::Odb => raw::GIT_ERROR_ODB,
+            ErrorClass::Index => raw::GIT_ERROR_INDEX,
+            ErrorClass::Object => raw::GIT_ERROR_OBJECT,
+            ErrorClass::Net => raw::GIT_ERROR_NET,
+            ErrorClass::Tag => raw::GIT_ERROR_TAG,
+            ErrorClass::Tree => raw::GIT_ERROR_TREE,
+            ErrorClass::Indexer => raw::GIT_ERROR_INDEXER,
+            ErrorClass::Ssl => raw::GIT_ERROR_SSL,
+            ErrorClass::Submodule => raw::GIT_ERROR_SUBMODULE,
+            ErrorClass::Thread => raw::GIT_ERROR_THREAD,
+            ErrorClass::Stash => raw::GIT_ERROR_STASH,
+            ErrorClass::Checkout => raw::GIT_ERROR_CHECKOUT,
+            ErrorClass::FetchHead => raw::GIT_ERROR_FETCHHEAD,
+            ErrorClass::Merge => raw::GIT_ERROR_MERGE,
+            ErrorClass::Ssh => raw::GIT_ERROR_SSH,
+            ErrorClass::Filter => raw::GIT_ERROR_FILTER,
+            ErrorClass::Revert => raw::GIT_ERROR_REVERT,
+            ErrorClass::Callback => raw::GIT_ERROR_CALLBACK,
+            ErrorClass::CherryPick => raw::GIT_ERROR_CHERRYPICK,
+            ErrorClass::Describe => raw::GIT_ERROR_DESCRIBE,
+            ErrorClass::Rebase => raw::GIT_ERROR_REBASE,
+            ErrorClass::Filesystem => raw::GIT_ERROR_FILESYSTEM,
+            ErrorClass::Patch => raw::GIT_ERROR_PATCH,
+            ErrorClass::Worktree => raw::GIT_ERROR_WORKTREE,
+            ErrorClass::Sha1 => raw::GIT_ERROR_SHA1,
+            ErrorClass::Http => raw::GIT_ERROR_HTTP,
+        } as c_int;
+    }
+
+    /// Return the raw error code associated with this error.
+    pub fn raw_code(&self) -> raw::git_error_code {
+        macro_rules! check( ($($e:ident,)*) => (
+            $(if self.code == raw::$e as c_int { raw::$e }) else *
+            else {
+                raw::GIT_ERROR
+            }
+        ) );
+        check!(
+            GIT_OK,
+            GIT_ERROR,
+            GIT_ENOTFOUND,
+            GIT_EEXISTS,
+            GIT_EAMBIGUOUS,
+            GIT_EBUFS,
+            GIT_EUSER,
+            GIT_EBAREREPO,
+            GIT_EUNBORNBRANCH,
+            GIT_EUNMERGED,
+            GIT_ENONFASTFORWARD,
+            GIT_EINVALIDSPEC,
+            GIT_ECONFLICT,
+            GIT_ELOCKED,
+            GIT_EMODIFIED,
+            GIT_EAUTH,
+            GIT_ECERTIFICATE,
+            GIT_EAPPLIED,
+            GIT_EPEEL,
+            GIT_EEOF,
+            GIT_EINVALID,
+            GIT_EUNCOMMITTED,
+            GIT_PASSTHROUGH,
+            GIT_ITEROVER,
+            GIT_RETRY,
+            GIT_EMISMATCH,
+            GIT_EINDEXDIRTY,
+            GIT_EAPPLYFAIL,
+            GIT_EOWNER,
+            GIT_TIMEOUT,
+        )
+    }
+
+    /// Return the raw error class associated with this error.
+    pub fn raw_class(&self) -> raw::git_error_t {
+        macro_rules! check( ($($e:ident,)*) => (
+            $(if self.klass == raw::$e as c_int { raw::$e }) else *
+            else {
+                raw::GIT_ERROR_NONE
+            }
+        ) );
+        check!(
+            GIT_ERROR_NONE,
+            GIT_ERROR_NOMEMORY,
+            GIT_ERROR_OS,
+            GIT_ERROR_INVALID,
+            GIT_ERROR_REFERENCE,
+            GIT_ERROR_ZLIB,
+            GIT_ERROR_REPOSITORY,
+            GIT_ERROR_CONFIG,
+            GIT_ERROR_REGEX,
+            GIT_ERROR_ODB,
+            GIT_ERROR_INDEX,
+            GIT_ERROR_OBJECT,
+            GIT_ERROR_NET,
+            GIT_ERROR_TAG,
+            GIT_ERROR_TREE,
+            GIT_ERROR_INDEXER,
+            GIT_ERROR_SSL,
+            GIT_ERROR_SUBMODULE,
+            GIT_ERROR_THREAD,
+            GIT_ERROR_STASH,
+            GIT_ERROR_CHECKOUT,
+            GIT_ERROR_FETCHHEAD,
+            GIT_ERROR_MERGE,
+            GIT_ERROR_SSH,
+            GIT_ERROR_FILTER,
+            GIT_ERROR_REVERT,
+            GIT_ERROR_CALLBACK,
+            GIT_ERROR_CHERRYPICK,
+            GIT_ERROR_DESCRIBE,
+            GIT_ERROR_REBASE,
+            GIT_ERROR_FILESYSTEM,
+            GIT_ERROR_PATCH,
+            GIT_ERROR_WORKTREE,
+            GIT_ERROR_SHA1,
+            GIT_ERROR_HTTP,
+        )
+    }
+
+    /// Return the message associated with this error
+    pub fn message(&self) -> &str {
+        &self.message
+    }
+
+    /// A low-level convenience to call [`raw::git_error_set_str`] with the
+    /// information from this error.
+    ///
+    /// Returns the [`Error::raw_code`] value of this error, which is often
+    /// needed from a C callback.
+    pub(crate) unsafe fn raw_set_git_error(&self) -> raw::git_error_code {
+        let s = CString::new(self.message()).unwrap();
+        raw::git_error_set_str(self.class() as c_int, s.as_ptr());
+        self.raw_code()
+    }
+}
+
+impl error::Error for Error {}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.message)?;
+        match self.class() {
+            ErrorClass::None => {}
+            other => write!(f, "; class={:?} ({})", other, self.klass)?,
+        }
+        match self.code() {
+            ErrorCode::GenericError => {}
+            other => write!(f, "; code={:?} ({})", other, self.code)?,
+        }
+        Ok(())
+    }
+}
+
+impl From<NulError> for Error {
+    fn from(_: NulError) -> Error {
+        Error::from_str(
+            "data contained a nul byte that could not be \
+             represented as a string",
+        )
+    }
+}
+
+impl From<JoinPathsError> for Error {
+    fn from(e: JoinPathsError) -> Error {
+        Error::from_str(&e.to_string())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{ErrorClass, ErrorCode};
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let err = repo.find_submodule("does_not_exist").err().unwrap();
+        assert_eq!(err.code(), ErrorCode::NotFound);
+        assert_eq!(err.class(), ErrorClass::Submodule);
+    }
+}
diff --git a/git2/src/index.rs b/git2/src/index.rs
new file mode 100644 (file)
index 0000000..0291d3c
--- /dev/null
@@ -0,0 +1,929 @@
+use std::ffi::{CStr, CString};
+use std::marker;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr;
+use std::slice;
+
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+
+use crate::util::{self, path_to_repo_path, Binding};
+use crate::IntoCString;
+use crate::{panic, raw, Error, IndexAddOption, IndexTime, Oid, Repository, Tree};
+
+/// A structure to represent a git [index][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Index {
+    raw: *mut raw::git_index,
+}
+
+/// An iterator over the entries in an index
+pub struct IndexEntries<'index> {
+    range: Range<usize>,
+    index: &'index Index,
+}
+
+/// An iterator over the conflicting entries in an index
+pub struct IndexConflicts<'index> {
+    conflict_iter: *mut raw::git_index_conflict_iterator,
+    _marker: marker::PhantomData<&'index Index>,
+}
+
+/// A structure to represent the information returned when a conflict is detected in an index entry
+pub struct IndexConflict {
+    /// The ancestor index entry of the two conflicting index entries
+    pub ancestor: Option<IndexEntry>,
+    /// The index entry originating from the user's copy of the repository.
+    /// Its contents conflict with 'their' index entry
+    pub our: Option<IndexEntry>,
+    /// The index entry originating from the external repository.
+    /// Its contents conflict with 'our' index entry
+    pub their: Option<IndexEntry>,
+}
+
+/// A callback function to filter index matches.
+///
+/// Used by `Index::{add_all,remove_all,update_all}`.  The first argument is the
+/// path, and the second is the pathspec that matched it.  Return 0 to confirm
+/// the operation on the item, > 0 to skip the item, and < 0 to abort the scan.
+pub type IndexMatchedPath<'a> = dyn FnMut(&Path, &[u8]) -> i32 + 'a;
+
+/// A structure to represent an entry or a file inside of an index.
+///
+/// All fields of an entry are public for modification and inspection. This is
+/// also how a new index entry is created.
+#[allow(missing_docs)]
+#[derive(Debug)]
+pub struct IndexEntry {
+    pub ctime: IndexTime,
+    pub mtime: IndexTime,
+    pub dev: u32,
+    pub ino: u32,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub file_size: u32,
+    pub id: Oid,
+    pub flags: u16,
+    pub flags_extended: u16,
+
+    /// The path of this index entry as a byte vector. Regardless of the
+    /// current platform, the directory separator is an ASCII forward slash
+    /// (`0x2F`). There are no terminating or internal NUL characters, and no
+    /// trailing slashes. Most of the time, paths will be valid utf-8 — but
+    /// not always. For more information on the path storage format, see
+    /// [these git docs][git-index-docs]. Note that libgit2 will take care of
+    /// handling the prefix compression mentioned there.
+    ///
+    /// [git-index-docs]: https://github.com/git/git/blob/a08a83db2bf27f015bec9a435f6d73e223c21c5e/Documentation/technical/index-format.txt#L107-L124
+    ///
+    /// You can turn this value into a `std::ffi::CString` with
+    /// `CString::new(&entry.path[..]).unwrap()`. To turn a reference into a
+    /// `&std::path::Path`, see the `bytes2path()` function in the private,
+    /// internal `util` module in this crate’s source code.
+    pub path: Vec<u8>,
+}
+
+impl Index {
+    /// Creates a new in-memory index.
+    ///
+    /// This index object cannot be read/written to the filesystem, but may be
+    /// used to perform in-memory index operations.
+    pub fn new() -> Result<Index, Error> {
+        crate::init();
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_index_new(&mut raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new bare Git index object as a memory representation of the Git
+    /// index file in 'index_path', without a repository to back it.
+    ///
+    /// Since there is no ODB or working directory behind this index, any Index
+    /// methods which rely on these (e.g. add_path) will fail.
+    ///
+    /// If you need an index attached to a repository, use the `index()` method
+    /// on `Repository`.
+    pub fn open(index_path: &Path) -> Result<Index, Error> {
+        crate::init();
+        let mut raw = ptr::null_mut();
+        // Normal file path OK (does not need Windows conversion).
+        let index_path = index_path.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_index_open(&mut raw, index_path));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get index on-disk version.
+    ///
+    /// Valid return values are 2, 3, or 4.  If 3 is returned, an index
+    /// with version 2 may be written instead, if the extension data in
+    /// version 3 is not necessary.
+    pub fn version(&self) -> u32 {
+        unsafe { raw::git_index_version(self.raw) }
+    }
+
+    /// Set index on-disk version.
+    ///
+    /// Valid values are 2, 3, or 4.  If 2 is given, git_index_write may
+    /// write an index with version 3 instead, if necessary to accurately
+    /// represent the index.
+    pub fn set_version(&mut self, version: u32) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_index_set_version(self.raw, version));
+        }
+        Ok(())
+    }
+
+    /// Add or update an index entry from an in-memory struct
+    ///
+    /// If a previous index entry exists that has the same path and stage as the
+    /// given 'source_entry', it will be replaced. Otherwise, the 'source_entry'
+    /// will be added.
+    pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> {
+        let path = CString::new(&entry.path[..])?;
+
+        // libgit2 encodes the length of the path in the lower bits of the
+        // `flags` entry, so mask those out and recalculate here to ensure we
+        // don't corrupt anything.
+        let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
+
+        if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
+            flags |= entry.path.len() as u16;
+        } else {
+            flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
+        }
+
+        unsafe {
+            let raw = raw::git_index_entry {
+                dev: entry.dev,
+                ino: entry.ino,
+                mode: entry.mode,
+                uid: entry.uid,
+                gid: entry.gid,
+                file_size: entry.file_size,
+                id: *entry.id.raw(),
+                flags,
+                flags_extended: entry.flags_extended,
+                path: path.as_ptr(),
+                mtime: raw::git_index_time {
+                    seconds: entry.mtime.seconds(),
+                    nanoseconds: entry.mtime.nanoseconds(),
+                },
+                ctime: raw::git_index_time {
+                    seconds: entry.ctime.seconds(),
+                    nanoseconds: entry.ctime.nanoseconds(),
+                },
+            };
+            try_call!(raw::git_index_add(self.raw, &raw));
+            Ok(())
+        }
+    }
+
+    /// Add or update an index entry from a buffer in memory
+    ///
+    /// This method will create a blob in the repository that owns the index and
+    /// then add the index entry to the index. The path of the entry represents
+    /// the position of the blob relative to the repository's root folder.
+    ///
+    /// If a previous index entry exists that has the same path as the given
+    /// 'entry', it will be replaced. Otherwise, the 'entry' will be added.
+    /// The id and the file_size of the 'entry' are updated with the real value
+    /// of the blob.
+    ///
+    /// This forces the file to be added to the index, not looking at gitignore
+    /// rules.
+    ///
+    /// If this file currently is the result of a merge conflict, this file will
+    /// no longer be marked as conflicting. The data about the conflict will be
+    /// moved to the "resolve undo" (REUC) section.
+    pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> {
+        let path = CString::new(&entry.path[..])?;
+
+        // libgit2 encodes the length of the path in the lower bits of the
+        // `flags` entry, so mask those out and recalculate here to ensure we
+        // don't corrupt anything.
+        let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
+
+        if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
+            flags |= entry.path.len() as u16;
+        } else {
+            flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
+        }
+
+        unsafe {
+            let raw = raw::git_index_entry {
+                dev: entry.dev,
+                ino: entry.ino,
+                mode: entry.mode,
+                uid: entry.uid,
+                gid: entry.gid,
+                file_size: entry.file_size,
+                id: *entry.id.raw(),
+                flags,
+                flags_extended: entry.flags_extended,
+                path: path.as_ptr(),
+                mtime: raw::git_index_time {
+                    seconds: entry.mtime.seconds(),
+                    nanoseconds: entry.mtime.nanoseconds(),
+                },
+                ctime: raw::git_index_time {
+                    seconds: entry.ctime.seconds(),
+                    nanoseconds: entry.ctime.nanoseconds(),
+                },
+            };
+
+            let ptr = data.as_ptr() as *const c_void;
+            let len = data.len() as size_t;
+            try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len));
+            Ok(())
+        }
+    }
+
+    /// Add or update an index entry from a file on disk
+    ///
+    /// The file path must be relative to the repository's working folder and
+    /// must be readable.
+    ///
+    /// This method will fail in bare index instances.
+    ///
+    /// This forces the file to be added to the index, not looking at gitignore
+    /// rules.
+    ///
+    /// If this file currently is the result of a merge conflict, this file will
+    /// no longer be marked as conflicting. The data about the conflict will be
+    /// moved to the "resolve undo" (REUC) section.
+    pub fn add_path(&mut self, path: &Path) -> Result<(), Error> {
+        let posix_path = path_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_index_add_bypath(self.raw, posix_path));
+            Ok(())
+        }
+    }
+
+    /// Add or update index entries matching files in the working directory.
+    ///
+    /// This method will fail in bare index instances.
+    ///
+    /// The `pathspecs` are a list of file names or shell glob patterns that
+    /// will matched against files in the repository's working directory. Each
+    /// file that matches will be added to the index (either updating an
+    /// existing entry or adding a new entry). You can disable glob expansion
+    /// and force exact matching with the `AddDisablePathspecMatch` flag.
+    ///
+    /// Files that are ignored will be skipped (unlike `add_path`). If a file is
+    /// already tracked in the index, then it will be updated even if it is
+    /// ignored. Pass the `AddForce` flag to skip the checking of ignore rules.
+    ///
+    /// To emulate `git add -A` and generate an error if the pathspec contains
+    /// the exact path of an ignored file (when not using `AddForce`), add the
+    /// `AddCheckPathspec` flag. This checks that each entry in `pathspecs`
+    /// that is an exact match to a filename on disk is either not ignored or
+    /// already in the index. If this check fails, the function will return
+    /// an error.
+    ///
+    /// To emulate `git add -A` with the "dry-run" option, just use a callback
+    /// function that always returns a positive value. See below for details.
+    ///
+    /// If any files are currently the result of a merge conflict, those files
+    /// will no longer be marked as conflicting. The data about the conflicts
+    /// will be moved to the "resolve undo" (REUC) section.
+    ///
+    /// If you provide a callback function, it will be invoked on each matching
+    /// item in the working directory immediately before it is added to /
+    /// updated in the index. Returning zero will add the item to the index,
+    /// greater than zero will skip the item, and less than zero will abort the
+    /// scan an return an error to the caller.
+    ///
+    /// # Example
+    ///
+    /// Emulate `git add *`:
+    ///
+    /// ```no_run
+    /// use git2::{Index, IndexAddOption, Repository};
+    ///
+    /// let repo = Repository::open("/path/to/a/repo").expect("failed to open");
+    /// let mut index = repo.index().expect("cannot get the Index file");
+    /// index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None);
+    /// index.write();
+    /// ```
+    pub fn add_all<T, I>(
+        &mut self,
+        pathspecs: I,
+        flag: IndexAddOption,
+        mut cb: Option<&mut IndexMatchedPath<'_>>,
+    ) -> Result<(), Error>
+    where
+        T: IntoCString,
+        I: IntoIterator<Item = T>,
+    {
+        let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
+        let ptr = cb.as_mut();
+        let callback = ptr
+            .as_ref()
+            .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
+        unsafe {
+            try_call!(raw::git_index_add_all(
+                self.raw,
+                &raw_strarray,
+                flag.bits() as c_uint,
+                callback,
+                ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
+            ));
+        }
+        Ok(())
+    }
+
+    /// Clear the contents (all the entries) of an index object.
+    ///
+    /// This clears the index object in memory; changes must be explicitly
+    /// written to disk for them to take effect persistently via `write_*`.
+    pub fn clear(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_index_clear(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Get the count of entries currently in the index
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_index_entrycount(&*self.raw) as usize }
+    }
+
+    /// Return `true` is there is no entry in the index
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Get one of the entries in the index by its position.
+    pub fn get(&self, n: usize) -> Option<IndexEntry> {
+        unsafe {
+            let ptr = raw::git_index_get_byindex(self.raw, n as size_t);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(Binding::from_raw(*ptr))
+            }
+        }
+    }
+
+    /// Get an iterator over the entries in this index.
+    pub fn iter(&self) -> IndexEntries<'_> {
+        IndexEntries {
+            range: 0..self.len(),
+            index: self,
+        }
+    }
+
+    /// Get an iterator over the index entries that have conflicts
+    pub fn conflicts(&self) -> Result<IndexConflicts<'_>, Error> {
+        crate::init();
+        let mut conflict_iter = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_index_conflict_iterator_new(
+                &mut conflict_iter,
+                self.raw
+            ));
+            Ok(Binding::from_raw(conflict_iter))
+        }
+    }
+
+    /// Get one of the entries in the index by its path.
+    pub fn get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry> {
+        let path = path_to_repo_path(path).unwrap();
+        unsafe {
+            let ptr = call!(raw::git_index_get_bypath(self.raw, path, stage as c_int));
+            if ptr.is_null() {
+                None
+            } else {
+                Some(Binding::from_raw(*ptr))
+            }
+        }
+    }
+
+    /// Does this index have conflicts?
+    ///
+    /// Returns `true` if the index contains conflicts, `false` if it does not.
+    pub fn has_conflicts(&self) -> bool {
+        unsafe { raw::git_index_has_conflicts(self.raw) == 1 }
+    }
+
+    /// Get the full path to the index file on disk.
+    ///
+    /// Returns `None` if this is an in-memory index.
+    pub fn path(&self) -> Option<&Path> {
+        unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) }
+    }
+
+    /// Update the contents of an existing index object in memory by reading
+    /// from the hard disk.
+    ///
+    /// If force is true, this performs a "hard" read that discards in-memory
+    /// changes and always reloads the on-disk index data. If there is no
+    /// on-disk version, the index will be cleared.
+    ///
+    /// If force is false, this does a "soft" read that reloads the index data
+    /// from disk only if it has changed since the last time it was loaded.
+    /// Purely in-memory index data will be untouched. Be aware: if there are
+    /// changes on disk, unwritten in-memory changes are discarded.
+    pub fn read(&mut self, force: bool) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_index_read(self.raw, force));
+        }
+        Ok(())
+    }
+
+    /// Read a tree into the index file with stats
+    ///
+    /// The current index contents will be replaced by the specified tree.
+    pub fn read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_index_read_tree(self.raw, &*tree.raw()));
+        }
+        Ok(())
+    }
+
+    /// Remove an entry from the index
+    pub fn remove(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
+        let path = path_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_index_remove(self.raw, path, stage as c_int));
+        }
+        Ok(())
+    }
+
+    /// Remove an index entry corresponding to a file on disk.
+    ///
+    /// The file path must be relative to the repository's working folder. It
+    /// may exist.
+    ///
+    /// If this file currently is the result of a merge conflict, this file will
+    /// no longer be marked as conflicting. The data about the conflict will be
+    /// moved to the "resolve undo" (REUC) section.
+    pub fn remove_path(&mut self, path: &Path) -> Result<(), Error> {
+        let path = path_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_index_remove_bypath(self.raw, path));
+        }
+        Ok(())
+    }
+
+    /// Remove all entries from the index under a given directory.
+    pub fn remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
+        let path = path_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_index_remove_directory(
+                self.raw,
+                path,
+                stage as c_int
+            ));
+        }
+        Ok(())
+    }
+
+    /// Remove all matching index entries.
+    ///
+    /// If you provide a callback function, it will be invoked on each matching
+    /// item in the index immediately before it is removed. Return 0 to remove
+    /// the item, > 0 to skip the item, and < 0 to abort the scan.
+    pub fn remove_all<T, I>(
+        &mut self,
+        pathspecs: I,
+        mut cb: Option<&mut IndexMatchedPath<'_>>,
+    ) -> Result<(), Error>
+    where
+        T: IntoCString,
+        I: IntoIterator<Item = T>,
+    {
+        let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
+        let ptr = cb.as_mut();
+        let callback = ptr
+            .as_ref()
+            .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
+        unsafe {
+            try_call!(raw::git_index_remove_all(
+                self.raw,
+                &raw_strarray,
+                callback,
+                ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
+            ));
+        }
+        Ok(())
+    }
+
+    /// Update all index entries to match the working directory
+    ///
+    /// This method will fail in bare index instances.
+    ///
+    /// This scans the existing index entries and synchronizes them with the
+    /// working directory, deleting them if the corresponding working directory
+    /// file no longer exists otherwise updating the information (including
+    /// adding the latest version of file to the ODB if needed).
+    ///
+    /// If you provide a callback function, it will be invoked on each matching
+    /// item in the index immediately before it is updated (either refreshed or
+    /// removed depending on working directory state). Return 0 to proceed with
+    /// updating the item, > 0 to skip the item, and < 0 to abort the scan.
+    pub fn update_all<T, I>(
+        &mut self,
+        pathspecs: I,
+        mut cb: Option<&mut IndexMatchedPath<'_>>,
+    ) -> Result<(), Error>
+    where
+        T: IntoCString,
+        I: IntoIterator<Item = T>,
+    {
+        let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
+        let ptr = cb.as_mut();
+        let callback = ptr
+            .as_ref()
+            .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
+        unsafe {
+            try_call!(raw::git_index_update_all(
+                self.raw,
+                &raw_strarray,
+                callback,
+                ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
+            ));
+        }
+        Ok(())
+    }
+
+    /// Write an existing index object from memory back to disk using an atomic
+    /// file lock.
+    pub fn write(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_index_write(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Write the index as a tree.
+    ///
+    /// This method will scan the index and write a representation of its
+    /// current state back to disk; it recursively creates tree objects for each
+    /// of the subtrees stored in the index, but only returns the OID of the
+    /// root tree. This is the OID that can be used e.g. to create a commit.
+    ///
+    /// The index instance cannot be bare, and needs to be associated to an
+    /// existing repository.
+    ///
+    /// The index must not contain any file in conflict.
+    pub fn write_tree(&mut self) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_index_write_tree(&mut raw, self.raw));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Write the index as a tree to the given repository
+    ///
+    /// This is the same as `write_tree` except that the destination repository
+    /// can be chosen.
+    pub fn write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_index_write_tree_to(&mut raw, self.raw, repo.raw()));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Find the first position of any entries matching a prefix.
+    ///
+    /// To find the first position of a path inside a given folder, suffix the prefix with a '/'.
+    pub fn find_prefix<T: IntoCString>(&self, prefix: T) -> Result<usize, Error> {
+        let mut at_pos: size_t = 0;
+        let entry_path = prefix.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_index_find_prefix(
+                &mut at_pos,
+                self.raw,
+                entry_path
+            ));
+            Ok(at_pos)
+        }
+    }
+}
+
+impl Binding for Index {
+    type Raw = *mut raw::git_index;
+    unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
+        Index { raw }
+    }
+    fn raw(&self) -> *mut raw::git_index {
+        self.raw
+    }
+}
+
+impl<'index> Binding for IndexConflicts<'index> {
+    type Raw = *mut raw::git_index_conflict_iterator;
+
+    unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
+        IndexConflicts {
+            conflict_iter: raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_index_conflict_iterator {
+        self.conflict_iter
+    }
+}
+
+extern "C" fn index_matched_path_cb(
+    path: *const c_char,
+    matched_pathspec: *const c_char,
+    payload: *mut c_void,
+) -> c_int {
+    unsafe {
+        let path = CStr::from_ptr(path).to_bytes();
+        let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
+
+        panic::wrap(|| {
+            let payload = payload as *mut &mut IndexMatchedPath<'_>;
+            (*payload)(util::bytes2path(path), matched_pathspec) as c_int
+        })
+        .unwrap_or(-1)
+    }
+}
+
+impl Drop for Index {
+    fn drop(&mut self) {
+        unsafe { raw::git_index_free(self.raw) }
+    }
+}
+
+impl<'index> Drop for IndexConflicts<'index> {
+    fn drop(&mut self) {
+        unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
+    }
+}
+
+impl<'index> Iterator for IndexEntries<'index> {
+    type Item = IndexEntry;
+    fn next(&mut self) -> Option<IndexEntry> {
+        self.range.next().map(|i| self.index.get(i).unwrap())
+    }
+}
+
+impl<'index> Iterator for IndexConflicts<'index> {
+    type Item = Result<IndexConflict, Error>;
+    fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
+        let mut ancestor = ptr::null();
+        let mut our = ptr::null();
+        let mut their = ptr::null();
+        unsafe {
+            try_call_iter!(raw::git_index_conflict_next(
+                &mut ancestor,
+                &mut our,
+                &mut their,
+                self.conflict_iter
+            ));
+            Some(Ok(IndexConflict {
+                ancestor: match ancestor.is_null() {
+                    false => Some(IndexEntry::from_raw(*ancestor)),
+                    true => None,
+                },
+                our: match our.is_null() {
+                    false => Some(IndexEntry::from_raw(*our)),
+                    true => None,
+                },
+                their: match their.is_null() {
+                    false => Some(IndexEntry::from_raw(*their)),
+                    true => None,
+                },
+            }))
+        }
+    }
+}
+
+impl Binding for IndexEntry {
+    type Raw = raw::git_index_entry;
+
+    unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
+        let raw::git_index_entry {
+            ctime,
+            mtime,
+            dev,
+            ino,
+            mode,
+            uid,
+            gid,
+            file_size,
+            id,
+            flags,
+            flags_extended,
+            path,
+        } = raw;
+
+        // libgit2 encodes the length of the path in the lower bits of `flags`,
+        // but if the length exceeds the number of bits then the path is
+        // nul-terminated.
+        let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
+        if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
+            pathlen = CStr::from_ptr(path).to_bytes().len();
+        }
+
+        let path = slice::from_raw_parts(path as *const u8, pathlen);
+
+        IndexEntry {
+            dev,
+            ino,
+            mode,
+            uid,
+            gid,
+            file_size,
+            id: Binding::from_raw(&id as *const _),
+            flags,
+            flags_extended,
+            path: path.to_vec(),
+            mtime: Binding::from_raw(mtime),
+            ctime: Binding::from_raw(ctime),
+        }
+    }
+
+    fn raw(&self) -> raw::git_index_entry {
+        // not implemented, may require a CString in storage
+        panic!()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::fs::{self, File};
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    use crate::{ErrorCode, Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
+
+    #[test]
+    fn smoke() {
+        let mut index = Index::new().unwrap();
+        assert!(index.add_path(&Path::new(".")).is_err());
+        index.clear().unwrap();
+        assert_eq!(index.len(), 0);
+        assert!(index.get(0).is_none());
+        assert!(index.path().is_none());
+        assert!(index.read(true).is_err());
+    }
+
+    #[test]
+    fn smoke_from_repo() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut index = repo.index().unwrap();
+        assert_eq!(
+            index.path().map(|s| s.to_path_buf()),
+            Some(repo.path().join("index"))
+        );
+        Index::open(&repo.path().join("index")).unwrap();
+
+        index.clear().unwrap();
+        index.read(true).unwrap();
+        index.write().unwrap();
+        index.write_tree().unwrap();
+        index.write_tree_to(&repo).unwrap();
+    }
+
+    #[test]
+    fn add_all() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut index = repo.index().unwrap();
+
+        let root = repo.path().parent().unwrap();
+        fs::create_dir(&root.join("foo")).unwrap();
+        File::create(&root.join("foo/bar")).unwrap();
+        let mut called = false;
+        index
+            .add_all(
+                ["foo"].iter(),
+                crate::IndexAddOption::DEFAULT,
+                Some(&mut |a: &Path, b: &[u8]| {
+                    assert!(!called);
+                    called = true;
+                    assert_eq!(b, b"foo");
+                    assert_eq!(a, Path::new("foo/bar"));
+                    0
+                }),
+            )
+            .unwrap();
+        assert!(called);
+
+        called = false;
+        index
+            .remove_all(
+                ["."].iter(),
+                Some(&mut |a: &Path, b: &[u8]| {
+                    assert!(!called);
+                    called = true;
+                    assert_eq!(b, b".");
+                    assert_eq!(a, Path::new("foo/bar"));
+                    0
+                }),
+            )
+            .unwrap();
+        assert!(called);
+    }
+
+    #[test]
+    fn smoke_add() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut index = repo.index().unwrap();
+
+        let root = repo.path().parent().unwrap();
+        fs::create_dir(&root.join("foo")).unwrap();
+        File::create(&root.join("foo/bar")).unwrap();
+        index.add_path(Path::new("foo/bar")).unwrap();
+        index.write().unwrap();
+        assert_eq!(index.iter().count(), 1);
+
+        // Make sure we can use this repo somewhere else now.
+        let id = index.write_tree().unwrap();
+        let tree = repo.find_tree(id).unwrap();
+        let sig = repo.signature().unwrap();
+        let id = repo.refname_to_id("HEAD").unwrap();
+        let parent = repo.find_commit(id).unwrap();
+        let commit = repo
+            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
+            .unwrap();
+        let obj = repo.find_object(commit, None).unwrap();
+        repo.reset(&obj, ResetType::Hard, None).unwrap();
+
+        let td2 = TempDir::new().unwrap();
+        let url = crate::test::path2url(&root);
+        let repo = Repository::clone(&url, td2.path()).unwrap();
+        let obj = repo.find_object(commit, None).unwrap();
+        repo.reset(&obj, ResetType::Hard, None).unwrap();
+    }
+
+    #[test]
+    fn add_then_read() {
+        let mut index = Index::new().unwrap();
+        let mut e = entry();
+        e.path = b"foobar".to_vec();
+        index.add(&e).unwrap();
+        let e = index.get(0).unwrap();
+        assert_eq!(e.path.len(), 6);
+    }
+
+    #[test]
+    fn add_then_find() {
+        let mut index = Index::new().unwrap();
+        let mut e = entry();
+        e.path = b"foo/bar".to_vec();
+        index.add(&e).unwrap();
+        let mut e = entry();
+        e.path = b"foo2/bar".to_vec();
+        index.add(&e).unwrap();
+        assert_eq!(index.get(0).unwrap().path, b"foo/bar");
+        assert_eq!(
+            index.get_path(Path::new("foo/bar"), 0).unwrap().path,
+            b"foo/bar"
+        );
+        assert_eq!(index.find_prefix(Path::new("foo2/")), Ok(1));
+        assert_eq!(
+            index.find_prefix(Path::new("empty/")).unwrap_err().code(),
+            ErrorCode::NotFound
+        );
+    }
+
+    #[test]
+    fn add_frombuffer_then_read() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut index = repo.index().unwrap();
+
+        let mut e = entry();
+        e.path = b"foobar".to_vec();
+        let content = b"the contents";
+        index.add_frombuffer(&e, content).unwrap();
+        let e = index.get(0).unwrap();
+        assert_eq!(e.path.len(), 6);
+
+        let b = repo.find_blob(e.id).unwrap();
+        assert_eq!(b.content(), content);
+    }
+
+    fn entry() -> IndexEntry {
+        IndexEntry {
+            ctime: IndexTime::new(0, 0),
+            mtime: IndexTime::new(0, 0),
+            dev: 0,
+            ino: 0,
+            mode: 0o100644,
+            uid: 0,
+            gid: 0,
+            file_size: 0,
+            id: Oid::from_bytes(&[0; 20]).unwrap(),
+            flags: 0,
+            flags_extended: 0,
+            path: Vec::new(),
+        }
+    }
+}
diff --git a/git2/src/indexer.rs b/git2/src/indexer.rs
new file mode 100644 (file)
index 0000000..ddca5fa
--- /dev/null
@@ -0,0 +1,252 @@
+use std::ffi::CStr;
+use std::path::Path;
+use std::{io, marker, mem, ptr};
+
+use libc::c_void;
+
+use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
+use crate::util::Binding;
+use crate::{raw, Error, IntoCString, Odb};
+
+/// Struct representing the progress by an in-flight transfer.
+pub struct Progress<'a> {
+    pub(crate) raw: ProgressState,
+    pub(crate) _marker: marker::PhantomData<&'a raw::git_indexer_progress>,
+}
+
+pub(crate) enum ProgressState {
+    Borrowed(*const raw::git_indexer_progress),
+    Owned(raw::git_indexer_progress),
+}
+
+/// Callback to be invoked while indexing is in progress.
+///
+/// This callback will be periodically called with updates to the progress of
+/// the indexing so far. The return value indicates whether the indexing or
+/// transfer should continue. A return value of `false` will cancel the
+/// indexing or transfer.
+///
+/// * `progress` - the progress being made so far.
+pub type IndexerProgress<'a> = dyn FnMut(Progress<'_>) -> bool + 'a;
+
+impl<'a> Progress<'a> {
+    /// Number of objects in the packfile being downloaded
+    pub fn total_objects(&self) -> usize {
+        unsafe { (*self.raw()).total_objects as usize }
+    }
+    /// Received objects that have been hashed
+    pub fn indexed_objects(&self) -> usize {
+        unsafe { (*self.raw()).indexed_objects as usize }
+    }
+    /// Objects which have been downloaded
+    pub fn received_objects(&self) -> usize {
+        unsafe { (*self.raw()).received_objects as usize }
+    }
+    /// Locally-available objects that have been injected in order to fix a thin
+    /// pack.
+    pub fn local_objects(&self) -> usize {
+        unsafe { (*self.raw()).local_objects as usize }
+    }
+    /// Number of deltas in the packfile being downloaded
+    pub fn total_deltas(&self) -> usize {
+        unsafe { (*self.raw()).total_deltas as usize }
+    }
+    /// Received deltas that have been hashed.
+    pub fn indexed_deltas(&self) -> usize {
+        unsafe { (*self.raw()).indexed_deltas as usize }
+    }
+    /// Size of the packfile received up to now
+    pub fn received_bytes(&self) -> usize {
+        unsafe { (*self.raw()).received_bytes as usize }
+    }
+
+    /// Convert this to an owned version of `Progress`.
+    pub fn to_owned(&self) -> Progress<'static> {
+        Progress {
+            raw: ProgressState::Owned(unsafe { *self.raw() }),
+            _marker: marker::PhantomData,
+        }
+    }
+}
+
+impl<'a> Binding for Progress<'a> {
+    type Raw = *const raw::git_indexer_progress;
+    unsafe fn from_raw(raw: *const raw::git_indexer_progress) -> Progress<'a> {
+        Progress {
+            raw: ProgressState::Borrowed(raw),
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *const raw::git_indexer_progress {
+        match self.raw {
+            ProgressState::Borrowed(raw) => raw,
+            ProgressState::Owned(ref raw) => raw as *const _,
+        }
+    }
+}
+
+/// Callback to be invoked while a transfer is in progress.
+///
+/// This callback will be periodically called with updates to the progress of
+/// the transfer so far. The return value indicates whether the transfer should
+/// continue. A return value of `false` will cancel the transfer.
+///
+/// * `progress` - the progress being made so far.
+#[deprecated(
+    since = "0.11.0",
+    note = "renamed to `IndexerProgress` to match upstream"
+)]
+#[allow(dead_code)]
+pub type TransportProgress<'a> = IndexerProgress<'a>;
+
+/// A stream to write and index a packfile
+///
+/// This is equivalent to [`crate::OdbPackwriter`], but allows to store the pack
+/// and index at an arbitrary path. It also does not require access to an object
+/// database if, and only if, the pack file is self-contained (i.e. not "thin").
+pub struct Indexer<'odb> {
+    raw: *mut raw::git_indexer,
+    progress: raw::git_indexer_progress,
+    progress_payload_ptr: *mut OdbPackwriterCb<'odb>,
+}
+
+impl<'a> Indexer<'a> {
+    /// Create a new indexer
+    ///
+    /// The [`Odb`] is used to resolve base objects when fixing thin packs. It
+    /// can be `None` if no thin pack is expected, in which case missing bases
+    /// will result in an error.
+    ///
+    /// `mode` is the permissions to use for the output files, use `0` for defaults.
+    ///
+    /// If `verify` is `false`, the indexer will bypass object connectivity checks.
+    pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> {
+        let path = path.into_c_string()?;
+
+        let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut);
+
+        let mut out = ptr::null_mut();
+        let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
+        let progress_payload = Box::new(OdbPackwriterCb { cb: None });
+        let progress_payload_ptr = Box::into_raw(progress_payload);
+
+        unsafe {
+            let mut opts = mem::zeroed();
+            try_call!(raw::git_indexer_options_init(
+                &mut opts,
+                raw::GIT_INDEXER_OPTIONS_VERSION
+            ));
+            opts.progress_cb = progress_cb;
+            opts.progress_cb_payload = progress_payload_ptr as *mut c_void;
+            opts.verify = verify.into();
+
+            try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts));
+        }
+
+        Ok(Self {
+            raw: out,
+            progress: Default::default(),
+            progress_payload_ptr,
+        })
+    }
+
+    /// Finalize the pack and index
+    ///
+    /// Resolves any pending deltas and writes out the index file. The returned
+    /// string is the hexadecimal checksum of the packfile, which is also used
+    /// to name the pack and index files (`pack-<checksum>.pack` and
+    /// `pack-<checksum>.idx` respectively).
+    pub fn commit(mut self) -> Result<String, Error> {
+        unsafe {
+            try_call!(raw::git_indexer_commit(self.raw, &mut self.progress));
+
+            let name = CStr::from_ptr(raw::git_indexer_name(self.raw));
+            Ok(name.to_str().expect("pack name not utf8").to_owned())
+        }
+    }
+
+    /// The callback through which progress is monitored. Be aware that this is
+    /// called inline, so performance may be affected.
+    pub fn progress<F>(&mut self, cb: F) -> &mut Self
+    where
+        F: FnMut(Progress<'_>) -> bool + 'a,
+    {
+        let progress_payload =
+            unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
+        progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
+
+        self
+    }
+}
+
+impl io::Write for Indexer<'_> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe {
+            let ptr = buf.as_ptr() as *mut c_void;
+            let len = buf.len();
+
+            let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress);
+            if res < 0 {
+                Err(io::Error::new(io::ErrorKind::Other, Error::last_error(res)))
+            } else {
+                Ok(buf.len())
+            }
+        }
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl Drop for Indexer<'_> {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_indexer_free(self.raw);
+            drop(Box::from_raw(self.progress_payload_ptr))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Buf, Indexer};
+    use std::io::prelude::*;
+
+    #[test]
+    fn indexer() {
+        let (_td, repo_source) = crate::test::repo_init();
+        let (_td, repo_target) = crate::test::repo_init();
+
+        let mut progress_called = false;
+
+        // Create an in-memory packfile
+        let mut builder = t!(repo_source.packbuilder());
+        let mut buf = Buf::new();
+        let (commit_source_id, _tree) = crate::test::commit(&repo_source);
+        t!(builder.insert_object(commit_source_id, None));
+        t!(builder.write_buf(&mut buf));
+
+        // Write it to the standard location in the target repo, but via indexer
+        let odb = repo_source.odb().unwrap();
+        let mut indexer = Indexer::new(
+            Some(&odb),
+            repo_target.path().join("objects").join("pack").as_path(),
+            0o644,
+            true,
+        )
+        .unwrap();
+        indexer.progress(|_| {
+            progress_called = true;
+            true
+        });
+        indexer.write(&buf).unwrap();
+        indexer.commit().unwrap();
+
+        // Assert that target repo picks it up as valid
+        let commit_target = repo_target.find_commit(commit_source_id).unwrap();
+        assert_eq!(commit_target.id(), commit_source_id);
+        assert!(progress_called);
+    }
+}
diff --git a/git2/src/lib.rs b/git2/src/lib.rs
new file mode 100644 (file)
index 0000000..01c9a93
--- /dev/null
@@ -0,0 +1,1684 @@
+//! # libgit2 bindings for Rust
+//!
+//! This library contains bindings to the [libgit2][1] C library which is used
+//! to manage git repositories. The library itself is a work in progress and is
+//! likely lacking some bindings here and there, so be warned.
+//!
+//! [1]: https://libgit2.github.com/
+//!
+//! The git2-rs library strives to be as close to libgit2 as possible, but also
+//! strives to make using libgit2 as safe as possible. All resource management
+//! is automatic as well as adding strong types to all interfaces (including
+//! `Result`)
+//!
+//! ## Creating a `Repository`
+//!
+//! The `Repository` is the source from which almost all other objects in git-rs
+//! are spawned. A repository can be created through opening, initializing, or
+//! cloning.
+//!
+//! ### Initializing a new repository
+//!
+//! The `init` method will create a new repository, assuming one does not
+//! already exist.
+//!
+//! ```no_run
+//! # #![allow(unstable)]
+//! use git2::Repository;
+//!
+//! let repo = match Repository::init("/path/to/a/repo") {
+//!     Ok(repo) => repo,
+//!     Err(e) => panic!("failed to init: {}", e),
+//! };
+//! ```
+//!
+//! ### Opening an existing repository
+//!
+//! ```no_run
+//! # #![allow(unstable)]
+//! use git2::Repository;
+//!
+//! let repo = match Repository::open("/path/to/a/repo") {
+//!     Ok(repo) => repo,
+//!     Err(e) => panic!("failed to open: {}", e),
+//! };
+//! ```
+//!
+//! ### Cloning an existing repository
+//!
+//! ```no_run
+//! # #![allow(unstable)]
+//! use git2::Repository;
+//!
+//! let url = "https://github.com/alexcrichton/git2-rs";
+//! let repo = match Repository::clone(url, "/path/to/a/repo") {
+//!     Ok(repo) => repo,
+//!     Err(e) => panic!("failed to clone: {}", e),
+//! };
+//! ```
+//!
+//! To clone using SSH, refer to [RepoBuilder](./build/struct.RepoBuilder.html).
+//!
+//! ## Working with a `Repository`
+//!
+//! All derivative objects, references, etc are attached to the lifetime of the
+//! source `Repository`, to ensure that they do not outlive the repository
+//! itself.
+
+#![doc(html_root_url = "https://docs.rs/git2/0.20")]
+#![allow(trivial_numeric_casts, trivial_casts)]
+#![deny(missing_docs)]
+#![warn(rust_2018_idioms)]
+#![cfg_attr(test, deny(warnings))]
+
+use bitflags::bitflags;
+use libgit2_sys as raw;
+
+use std::ffi::{CStr, CString};
+use std::fmt;
+use std::str;
+use std::sync::Once;
+
+pub use crate::apply::{ApplyLocation, ApplyOptions};
+pub use crate::attr::AttrValue;
+pub use crate::blame::{Blame, BlameHunk, BlameIter, BlameOptions};
+pub use crate::blob::{Blob, BlobWriter};
+pub use crate::branch::{Branch, Branches};
+pub use crate::buf::Buf;
+pub use crate::cherrypick::CherrypickOptions;
+pub use crate::commit::{Commit, Parents};
+pub use crate::config::{Config, ConfigEntries, ConfigEntry};
+pub use crate::cred::{Cred, CredentialHelper};
+pub use crate::describe::{Describe, DescribeFormatOptions, DescribeOptions};
+pub use crate::diff::{Deltas, Diff, DiffDelta, DiffFile, DiffOptions};
+pub use crate::diff::{DiffBinary, DiffBinaryFile, DiffBinaryKind, DiffPatchidOptions};
+pub use crate::diff::{DiffFindOptions, DiffHunk, DiffLine, DiffLineType, DiffStats};
+pub use crate::email::{Email, EmailCreateOptions};
+pub use crate::error::Error;
+pub use crate::index::{
+    Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath,
+};
+pub use crate::indexer::{Indexer, IndexerProgress, Progress};
+pub use crate::mailmap::Mailmap;
+pub use crate::mempack::Mempack;
+pub use crate::merge::{AnnotatedCommit, MergeOptions};
+pub use crate::message::{
+    message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes,
+    MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator,
+    DEFAULT_COMMENT_CHAR,
+};
+pub use crate::note::{Note, Notes};
+pub use crate::object::Object;
+pub use crate::odb::{Odb, OdbObject, OdbPackwriter, OdbReader, OdbWriter};
+pub use crate::oid::Oid;
+pub use crate::packbuilder::{PackBuilder, PackBuilderStage};
+pub use crate::patch::Patch;
+pub use crate::pathspec::{Pathspec, PathspecFailedEntries, PathspecMatchList};
+pub use crate::pathspec::{PathspecDiffEntries, PathspecEntries};
+pub use crate::proxy_options::ProxyOptions;
+pub use crate::push_update::PushUpdate;
+pub use crate::rebase::{Rebase, RebaseOperation, RebaseOperationType, RebaseOptions};
+pub use crate::reference::{Reference, ReferenceNames, References};
+pub use crate::reflog::{Reflog, ReflogEntry, ReflogIter};
+pub use crate::refspec::Refspec;
+pub use crate::remote::{
+    FetchOptions, PushOptions, Refspecs, Remote, RemoteConnection, RemoteHead, RemoteRedirect,
+};
+pub use crate::remote_callbacks::{CertificateCheckStatus, Credentials, RemoteCallbacks};
+pub use crate::remote_callbacks::{TransportMessage, UpdateTips};
+pub use crate::repo::{Repository, RepositoryInitOptions};
+pub use crate::revert::RevertOptions;
+pub use crate::revspec::Revspec;
+pub use crate::revwalk::Revwalk;
+pub use crate::signature::Signature;
+pub use crate::stash::{StashApplyOptions, StashApplyProgressCb, StashCb, StashSaveOptions};
+pub use crate::status::{StatusEntry, StatusIter, StatusOptions, StatusShow, Statuses};
+pub use crate::submodule::{Submodule, SubmoduleUpdateOptions};
+pub use crate::tag::Tag;
+pub use crate::time::{IndexTime, Time};
+pub use crate::tracing::{trace_set, TraceLevel};
+pub use crate::transaction::Transaction;
+pub use crate::tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult};
+pub use crate::treebuilder::TreeBuilder;
+pub use crate::util::IntoCString;
+pub use crate::version::Version;
+pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions};
+
+// Create a convinience method on bitflag struct which checks the given flag
+macro_rules! is_bit_set {
+    ($name:ident, $flag:expr) => {
+        #[allow(missing_docs)]
+        pub fn $name(&self) -> bool {
+            self.intersects($flag)
+        }
+    };
+}
+
+/// An enumeration of possible errors that can happen when working with a git
+/// repository.
+// Note: We omit a few native error codes, as they are unlikely to be propagated
+// to the library user. Currently:
+//
+// * GIT_EPASSTHROUGH
+// * GIT_ITEROVER
+// * GIT_RETRY
+#[derive(PartialEq, Eq, Clone, Debug, Copy)]
+pub enum ErrorCode {
+    /// Generic error
+    GenericError,
+    /// Requested object could not be found
+    NotFound,
+    /// Object exists preventing operation
+    Exists,
+    /// More than one object matches
+    Ambiguous,
+    /// Output buffer too short to hold data
+    BufSize,
+    /// User-generated error
+    User,
+    /// Operation not allowed on bare repository
+    BareRepo,
+    /// HEAD refers to branch with no commits
+    UnbornBranch,
+    /// Merge in progress prevented operation
+    Unmerged,
+    /// Reference was not fast-forwardable
+    NotFastForward,
+    /// Name/ref spec was not in a valid format
+    InvalidSpec,
+    /// Checkout conflicts prevented operation
+    Conflict,
+    /// Lock file prevented operation
+    Locked,
+    /// Reference value does not match expected
+    Modified,
+    /// Authentication error
+    Auth,
+    /// Server certificate is invalid
+    Certificate,
+    /// Patch/merge has already been applied
+    Applied,
+    /// The requested peel operation is not possible
+    Peel,
+    /// Unexpected EOF
+    Eof,
+    /// Invalid operation or input
+    Invalid,
+    /// Uncommitted changes in index prevented operation
+    Uncommitted,
+    /// Operation was not valid for a directory
+    Directory,
+    /// A merge conflict exists and cannot continue
+    MergeConflict,
+    /// Hashsum mismatch in object
+    HashsumMismatch,
+    /// Unsaved changes in the index would be overwritten
+    IndexDirty,
+    /// Patch application failed
+    ApplyFail,
+    /// The object is not owned by the current user
+    Owner,
+    /// Timeout
+    Timeout,
+}
+
+/// An enumeration of possible categories of things that can have
+/// errors when working with a git repository.
+#[derive(PartialEq, Eq, Clone, Debug, Copy)]
+pub enum ErrorClass {
+    /// Uncategorized
+    None,
+    /// Out of memory or insufficient allocated space
+    NoMemory,
+    /// Syscall or standard system library error
+    Os,
+    /// Invalid input
+    Invalid,
+    /// Error resolving or manipulating a reference
+    Reference,
+    /// ZLib failure
+    Zlib,
+    /// Bad repository state
+    Repository,
+    /// Bad configuration
+    Config,
+    /// Regex failure
+    Regex,
+    /// Bad object
+    Odb,
+    /// Invalid index data
+    Index,
+    /// Error creating or obtaining an object
+    Object,
+    /// Network error
+    Net,
+    /// Error manipulating a tag
+    Tag,
+    /// Invalid value in tree
+    Tree,
+    /// Hashing or packing error
+    Indexer,
+    /// Error from SSL
+    Ssl,
+    /// Error involving submodules
+    Submodule,
+    /// Threading error
+    Thread,
+    /// Error manipulating a stash
+    Stash,
+    /// Checkout failure
+    Checkout,
+    /// Invalid FETCH_HEAD
+    FetchHead,
+    /// Merge failure
+    Merge,
+    /// SSH failure
+    Ssh,
+    /// Error manipulating filters
+    Filter,
+    /// Error reverting commit
+    Revert,
+    /// Error from a user callback
+    Callback,
+    /// Error cherry-picking commit
+    CherryPick,
+    /// Can't describe object
+    Describe,
+    /// Error during rebase
+    Rebase,
+    /// Filesystem-related error
+    Filesystem,
+    /// Invalid patch data
+    Patch,
+    /// Error involving worktrees
+    Worktree,
+    /// Hash library error or SHA-1 collision
+    Sha1,
+    /// HTTP error
+    Http,
+}
+
+/// A listing of the possible states that a repository can be in.
+#[derive(PartialEq, Eq, Clone, Debug, Copy)]
+#[allow(missing_docs)]
+pub enum RepositoryState {
+    Clean,
+    Merge,
+    Revert,
+    RevertSequence,
+    CherryPick,
+    CherryPickSequence,
+    Bisect,
+    Rebase,
+    RebaseInteractive,
+    RebaseMerge,
+    ApplyMailbox,
+    ApplyMailboxOrRebase,
+}
+
+/// An enumeration of the possible directions for a remote.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Direction {
+    /// Data will be fetched (read) from this remote.
+    Fetch,
+    /// Data will be pushed (written) to this remote.
+    Push,
+}
+
+/// An enumeration of the operations that can be performed for the `reset`
+/// method on a `Repository`.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum ResetType {
+    /// Move the head to the given commit.
+    Soft,
+    /// Soft plus reset the index to the commit.
+    Mixed,
+    /// Mixed plus changes in the working tree are discarded.
+    Hard,
+}
+
+/// An enumeration all possible kinds objects may have.
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+pub enum ObjectType {
+    /// Any kind of git object
+    Any,
+    /// An object which corresponds to a git commit
+    Commit,
+    /// An object which corresponds to a git tree
+    Tree,
+    /// An object which corresponds to a git blob
+    Blob,
+    /// An object which corresponds to a git tag
+    Tag,
+}
+
+/// An enumeration of all possible kinds of references.
+#[derive(PartialEq, Eq, Copy, Clone, Debug)]
+pub enum ReferenceType {
+    /// A reference which points at an object id.
+    Direct,
+
+    /// A reference which points at another reference.
+    Symbolic,
+}
+
+/// An enumeration for the possible types of branches
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum BranchType {
+    /// A local branch not on a remote.
+    Local,
+    /// A branch for a remote.
+    Remote,
+}
+
+/// An enumeration of the possible priority levels of a config file.
+///
+/// The levels corresponding to the escalation logic (higher to lower) when
+/// searching for config entries.
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum ConfigLevel {
+    /// System-wide on Windows, for compatibility with portable git
+    ProgramData = 1,
+    /// System-wide configuration file, e.g. /etc/gitconfig
+    System,
+    /// XDG-compatible configuration file, e.g. ~/.config/git/config
+    XDG,
+    /// User-specific configuration, e.g. ~/.gitconfig
+    Global,
+    /// Repository specific config, e.g. $PWD/.git/config
+    Local,
+    ///  Worktree specific configuration file, e.g. $GIT_DIR/config.worktree
+    Worktree,
+    /// Application specific configuration file
+    App,
+    /// Highest level available
+    Highest = -1,
+}
+
+/// Merge file favor options for `MergeOptions` instruct the file-level
+/// merging functionality how to deal with conflicting regions of the files.
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub enum FileFavor {
+    /// When a region of a file is changed in both branches, a conflict will be
+    /// recorded in the index so that git_checkout can produce a merge file with
+    /// conflict markers in the working directory. This is the default.
+    Normal,
+    /// When a region of a file is changed in both branches, the file created
+    /// in the index will contain the "ours" side of any conflicting region.
+    /// The index will not record a conflict.
+    Ours,
+    /// When a region of a file is changed in both branches, the file created
+    /// in the index will contain the "theirs" side of any conflicting region.
+    /// The index will not record a conflict.
+    Theirs,
+    /// When a region of a file is changed in both branches, the file created
+    /// in the index will contain each unique line from each side, which has
+    /// the result of combining both files. The index will not record a conflict.
+    Union,
+}
+
+bitflags! {
+    /// Orderings that may be specified for Revwalk iteration.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct Sort: u32 {
+        /// Sort the repository contents in no particular ordering.
+        ///
+        /// This sorting is arbitrary, implementation-specific, and subject to
+        /// change at any time. This is the default sorting for new walkers.
+        const NONE = raw::GIT_SORT_NONE as u32;
+
+        /// Sort the repository contents in topological order (children before
+        /// parents).
+        ///
+        /// This sorting mode can be combined with time sorting.
+        const TOPOLOGICAL = raw::GIT_SORT_TOPOLOGICAL as u32;
+
+        /// Sort the repository contents by commit time.
+        ///
+        /// This sorting mode can be combined with topological sorting.
+        const TIME = raw::GIT_SORT_TIME as u32;
+
+        /// Iterate through the repository contents in reverse order.
+        ///
+        /// This sorting mode can be combined with any others.
+        const REVERSE = raw::GIT_SORT_REVERSE as u32;
+    }
+}
+
+impl Sort {
+    is_bit_set!(is_none, Sort::NONE);
+    is_bit_set!(is_topological, Sort::TOPOLOGICAL);
+    is_bit_set!(is_time, Sort::TIME);
+    is_bit_set!(is_reverse, Sort::REVERSE);
+}
+
+bitflags! {
+    /// Types of credentials that can be requested by a credential callback.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct CredentialType: u32 {
+        #[allow(missing_docs)]
+        const USER_PASS_PLAINTEXT = raw::GIT_CREDTYPE_USERPASS_PLAINTEXT as u32;
+        #[allow(missing_docs)]
+        const SSH_KEY = raw::GIT_CREDTYPE_SSH_KEY as u32;
+        #[allow(missing_docs)]
+        const SSH_MEMORY = raw::GIT_CREDTYPE_SSH_MEMORY as u32;
+        #[allow(missing_docs)]
+        const SSH_CUSTOM = raw::GIT_CREDTYPE_SSH_CUSTOM as u32;
+        #[allow(missing_docs)]
+        const DEFAULT = raw::GIT_CREDTYPE_DEFAULT as u32;
+        #[allow(missing_docs)]
+        const SSH_INTERACTIVE = raw::GIT_CREDTYPE_SSH_INTERACTIVE as u32;
+        #[allow(missing_docs)]
+        const USERNAME = raw::GIT_CREDTYPE_USERNAME as u32;
+    }
+}
+
+impl CredentialType {
+    is_bit_set!(is_user_pass_plaintext, CredentialType::USER_PASS_PLAINTEXT);
+    is_bit_set!(is_ssh_key, CredentialType::SSH_KEY);
+    is_bit_set!(is_ssh_memory, CredentialType::SSH_MEMORY);
+    is_bit_set!(is_ssh_custom, CredentialType::SSH_CUSTOM);
+    is_bit_set!(is_default, CredentialType::DEFAULT);
+    is_bit_set!(is_ssh_interactive, CredentialType::SSH_INTERACTIVE);
+    is_bit_set!(is_username, CredentialType::USERNAME);
+}
+
+impl Default for CredentialType {
+    fn default() -> Self {
+        CredentialType::DEFAULT
+    }
+}
+
+bitflags! {
+    /// Flags for the `flags` field of an IndexEntry.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct IndexEntryFlag: u16 {
+        /// Set when the `extended_flags` field is valid.
+        const EXTENDED = raw::GIT_INDEX_ENTRY_EXTENDED as u16;
+        /// "Assume valid" flag
+        const VALID = raw::GIT_INDEX_ENTRY_VALID as u16;
+    }
+}
+
+impl IndexEntryFlag {
+    is_bit_set!(is_extended, IndexEntryFlag::EXTENDED);
+    is_bit_set!(is_valid, IndexEntryFlag::VALID);
+}
+
+bitflags! {
+    /// Flags for the `extended_flags` field of an IndexEntry.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct IndexEntryExtendedFlag: u16 {
+        /// An "intent to add" entry from "git add -N"
+        const INTENT_TO_ADD = raw::GIT_INDEX_ENTRY_INTENT_TO_ADD as u16;
+        /// Skip the associated worktree file, for sparse checkouts
+        const SKIP_WORKTREE = raw::GIT_INDEX_ENTRY_SKIP_WORKTREE as u16;
+
+        #[allow(missing_docs)]
+        const UPTODATE = raw::GIT_INDEX_ENTRY_UPTODATE as u16;
+    }
+}
+
+impl IndexEntryExtendedFlag {
+    is_bit_set!(is_intent_to_add, IndexEntryExtendedFlag::INTENT_TO_ADD);
+    is_bit_set!(is_skip_worktree, IndexEntryExtendedFlag::SKIP_WORKTREE);
+    is_bit_set!(is_up_to_date, IndexEntryExtendedFlag::UPTODATE);
+}
+
+bitflags! {
+    /// Flags for APIs that add files matching pathspec
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct IndexAddOption: u32 {
+        #[allow(missing_docs)]
+        const DEFAULT = raw::GIT_INDEX_ADD_DEFAULT as u32;
+        #[allow(missing_docs)]
+        const FORCE = raw::GIT_INDEX_ADD_FORCE as u32;
+        #[allow(missing_docs)]
+        const DISABLE_PATHSPEC_MATCH =
+                raw::GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH as u32;
+        #[allow(missing_docs)]
+        const CHECK_PATHSPEC = raw::GIT_INDEX_ADD_CHECK_PATHSPEC as u32;
+    }
+}
+
+impl IndexAddOption {
+    is_bit_set!(is_default, IndexAddOption::DEFAULT);
+    is_bit_set!(is_force, IndexAddOption::FORCE);
+    is_bit_set!(
+        is_disable_pathspec_match,
+        IndexAddOption::DISABLE_PATHSPEC_MATCH
+    );
+    is_bit_set!(is_check_pathspec, IndexAddOption::CHECK_PATHSPEC);
+}
+
+impl Default for IndexAddOption {
+    fn default() -> Self {
+        IndexAddOption::DEFAULT
+    }
+}
+
+bitflags! {
+    /// Flags for `Repository::open_ext`
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct RepositoryOpenFlags: u32 {
+        /// Only open the specified path; don't walk upward searching.
+        const NO_SEARCH = raw::GIT_REPOSITORY_OPEN_NO_SEARCH as u32;
+        /// Search across filesystem boundaries.
+        const CROSS_FS = raw::GIT_REPOSITORY_OPEN_CROSS_FS as u32;
+        /// Force opening as bare repository, and defer loading its config.
+        const BARE = raw::GIT_REPOSITORY_OPEN_BARE as u32;
+        /// Don't try appending `/.git` to the specified repository path.
+        const NO_DOTGIT = raw::GIT_REPOSITORY_OPEN_NO_DOTGIT as u32;
+        /// Respect environment variables like `$GIT_DIR`.
+        const FROM_ENV = raw::GIT_REPOSITORY_OPEN_FROM_ENV as u32;
+    }
+}
+
+impl RepositoryOpenFlags {
+    is_bit_set!(is_no_search, RepositoryOpenFlags::NO_SEARCH);
+    is_bit_set!(is_cross_fs, RepositoryOpenFlags::CROSS_FS);
+    is_bit_set!(is_bare, RepositoryOpenFlags::BARE);
+    is_bit_set!(is_no_dotgit, RepositoryOpenFlags::NO_DOTGIT);
+    is_bit_set!(is_from_env, RepositoryOpenFlags::FROM_ENV);
+}
+
+bitflags! {
+    /// Flags for the return value of `Repository::revparse`
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct RevparseMode: u32 {
+        /// The spec targeted a single object
+        const SINGLE = raw::GIT_REVPARSE_SINGLE as u32;
+        /// The spec targeted a range of commits
+        const RANGE = raw::GIT_REVPARSE_RANGE as u32;
+        /// The spec used the `...` operator, which invokes special semantics.
+        const MERGE_BASE = raw::GIT_REVPARSE_MERGE_BASE as u32;
+    }
+}
+
+impl RevparseMode {
+    is_bit_set!(is_no_single, RevparseMode::SINGLE);
+    is_bit_set!(is_range, RevparseMode::RANGE);
+    is_bit_set!(is_merge_base, RevparseMode::MERGE_BASE);
+}
+
+bitflags! {
+    /// The results of `merge_analysis` indicating the merge opportunities.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct MergeAnalysis: u32 {
+        /// No merge is possible.
+        const ANALYSIS_NONE = raw::GIT_MERGE_ANALYSIS_NONE as u32;
+        /// A "normal" merge; both HEAD and the given merge input have diverged
+        /// from their common ancestor. The divergent commits must be merged.
+        const ANALYSIS_NORMAL = raw::GIT_MERGE_ANALYSIS_NORMAL as u32;
+        /// All given merge inputs are reachable from HEAD, meaning the
+        /// repository is up-to-date and no merge needs to be performed.
+        const ANALYSIS_UP_TO_DATE = raw::GIT_MERGE_ANALYSIS_UP_TO_DATE as u32;
+        /// The given merge input is a fast-forward from HEAD and no merge
+        /// needs to be performed.  Instead, the client can check out the
+        /// given merge input.
+        const ANALYSIS_FASTFORWARD = raw::GIT_MERGE_ANALYSIS_FASTFORWARD as u32;
+        /// The HEAD of the current repository is "unborn" and does not point to
+        /// a valid commit.  No merge can be performed, but the caller may wish
+        /// to simply set HEAD to the target commit(s).
+        const ANALYSIS_UNBORN = raw::GIT_MERGE_ANALYSIS_UNBORN as u32;
+    }
+}
+
+impl MergeAnalysis {
+    is_bit_set!(is_none, MergeAnalysis::ANALYSIS_NONE);
+    is_bit_set!(is_normal, MergeAnalysis::ANALYSIS_NORMAL);
+    is_bit_set!(is_up_to_date, MergeAnalysis::ANALYSIS_UP_TO_DATE);
+    is_bit_set!(is_fast_forward, MergeAnalysis::ANALYSIS_FASTFORWARD);
+    is_bit_set!(is_unborn, MergeAnalysis::ANALYSIS_UNBORN);
+}
+
+bitflags! {
+    /// The user's stated preference for merges.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct MergePreference: u32 {
+        /// No configuration was found that suggests a preferred behavior for
+        /// merge.
+        const NONE = raw::GIT_MERGE_PREFERENCE_NONE as u32;
+        /// There is a `merge.ff=false` configuration setting, suggesting that
+        /// the user does not want to allow a fast-forward merge.
+        const NO_FAST_FORWARD = raw::GIT_MERGE_PREFERENCE_NO_FASTFORWARD as u32;
+        /// There is a `merge.ff=only` configuration setting, suggesting that
+        /// the user only wants fast-forward merges.
+        const FASTFORWARD_ONLY = raw::GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY as u32;
+    }
+}
+
+impl MergePreference {
+    is_bit_set!(is_none, MergePreference::NONE);
+    is_bit_set!(is_no_fast_forward, MergePreference::NO_FAST_FORWARD);
+    is_bit_set!(is_fastforward_only, MergePreference::FASTFORWARD_ONLY);
+}
+
+bitflags! {
+    /// Flags controlling the behavior of ODB lookup operations
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct OdbLookupFlags: u32 {
+        /// Don't call `git_odb_refresh` if the lookup fails. Useful when doing
+        /// a batch of lookup operations for objects that may legitimately not
+        /// exist. When using this flag, you may wish to manually call
+        /// `git_odb_refresh` before processing a batch of objects.
+        const NO_REFRESH = raw::GIT_ODB_LOOKUP_NO_REFRESH as u32;
+    }
+}
+
+bitflags! {
+    /// How to handle reference updates.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct RemoteUpdateFlags: u32 {
+       /// Write the fetch results to FETCH_HEAD.
+       const UPDATE_FETCHHEAD = raw::GIT_REMOTE_UPDATE_FETCHHEAD as u32;
+       /// Report unchanged tips in the update_tips callback.
+       const REPORT_UNCHANGED = raw::GIT_REMOTE_UPDATE_REPORT_UNCHANGED as u32;
+    }
+}
+
+#[cfg(test)]
+#[macro_use]
+mod test;
+#[macro_use]
+mod panic;
+mod attr;
+mod call;
+mod util;
+
+pub mod build;
+pub mod cert;
+pub mod oid_array;
+pub mod opts;
+pub mod string_array;
+pub mod transport;
+
+mod apply;
+mod blame;
+mod blob;
+mod branch;
+mod buf;
+mod cherrypick;
+mod commit;
+mod config;
+mod cred;
+mod describe;
+mod diff;
+mod email;
+mod error;
+mod index;
+mod indexer;
+mod mailmap;
+mod mempack;
+mod merge;
+mod message;
+mod note;
+mod object;
+mod odb;
+mod oid;
+mod packbuilder;
+mod patch;
+mod pathspec;
+mod proxy_options;
+mod push_update;
+mod rebase;
+mod reference;
+mod reflog;
+mod refspec;
+mod remote;
+mod remote_callbacks;
+mod repo;
+mod revert;
+mod revspec;
+mod revwalk;
+mod signature;
+mod stash;
+mod status;
+mod submodule;
+mod tag;
+mod tagforeach;
+mod time;
+mod tracing;
+mod transaction;
+mod tree;
+mod treebuilder;
+mod version;
+mod worktree;
+
+fn init() {
+    static INIT: Once = Once::new();
+
+    INIT.call_once(|| {
+        openssl_env_init();
+    });
+
+    raw::init();
+}
+
+#[cfg(all(
+    unix,
+    not(target_os = "macos"),
+    not(target_os = "ios"),
+    feature = "https"
+))]
+fn openssl_env_init() {
+    // Currently, libgit2 leverages OpenSSL for SSL support when cloning
+    // repositories over HTTPS. This means that we're picking up an OpenSSL
+    // dependency on non-Windows platforms (where it has its own HTTPS
+    // subsystem). As a result, we need to link to OpenSSL.
+    //
+    // Now actually *linking* to OpenSSL isn't so hard. We just need to make
+    // sure to use pkg-config to discover any relevant system dependencies for
+    // differences between distributions like CentOS and Ubuntu. The actual
+    // trickiness comes about when we start *distributing* the resulting
+    // binaries. Currently Cargo is distributed in binary form as nightlies,
+    // which means we're distributing a binary with OpenSSL linked in.
+    //
+    // For historical reasons, the Linux nightly builder is running a CentOS
+    // distribution in order to have as much ABI compatibility with other
+    // distributions as possible. Sadly, however, this compatibility does not
+    // extend to OpenSSL. Currently OpenSSL has two major versions, 0.9 and 1.0,
+    // which are incompatible (many ABI differences). The CentOS builder we
+    // build on has version 1.0, as do most distributions today. Some still have
+    // 0.9, however. This means that if we are to distribute the binaries built
+    // by the CentOS machine, we would only be compatible with OpenSSL 1.0 and
+    // we would fail to run (a dynamic linker error at runtime) on systems with
+    // only 9.8 installed (hopefully).
+    //
+    // But wait, the plot thickens! Apparently CentOS has dubbed their OpenSSL
+    // library as `libssl.so.10`, notably the `10` is included at the end. On
+    // the other hand Ubuntu, for example, only distributes `libssl.so`. This
+    // means that the binaries created at CentOS are hard-wired to probe for a
+    // file called `libssl.so.10` at runtime (using the LD_LIBRARY_PATH), which
+    // will not be found on ubuntu. The conclusion of this is that binaries
+    // built on CentOS cannot be distributed to Ubuntu and run successfully.
+    //
+    // There are a number of sneaky things we could do, including, but not
+    // limited to:
+    //
+    // 1. Create a shim program which runs "just before" cargo runs. The
+    //    responsibility of this shim program would be to locate `libssl.so`,
+    //    whatever it's called, on the current system, make sure there's a
+    //    symlink *somewhere* called `libssl.so.10`, and then set up
+    //    LD_LIBRARY_PATH and run the actual cargo.
+    //
+    //    This approach definitely seems unconventional, and is borderline
+    //    overkill for this problem. It's also dubious if we can find a
+    //    libssl.so reliably on the target system.
+    //
+    // 2. Somehow re-work the CentOS installation so that the linked-against
+    //    library is called libssl.so instead of libssl.so.10
+    //
+    //    The problem with this approach is that systems with 0.9 installed will
+    //    start to silently fail, due to also having libraries called libssl.so
+    //    (probably symlinked under a more appropriate version).
+    //
+    // 3. Compile Cargo against both OpenSSL 1.0 *and* OpenSSL 0.9, and
+    //    distribute both. Also make sure that the linked-against name of the
+    //    library is `libssl.so`. At runtime we determine which version is
+    //    installed, and we then the appropriate binary.
+    //
+    //    This approach clearly has drawbacks in terms of infrastructure and
+    //    feasibility.
+    //
+    // 4. Build a nightly of Cargo for each distribution we'd like to support.
+    //    You would then pick the appropriate Cargo nightly to install locally.
+    //
+    // So, with all this in mind, the decision was made to *statically* link
+    // OpenSSL. This solves any problem of relying on a downstream OpenSSL
+    // version being available. This does, however, open a can of worms related
+    // to security issues. It's generally a good idea to dynamically link
+    // OpenSSL as you'll get security updates over time without having to do
+    // anything (the system administrator will update the local openssl
+    // package). By statically linking, we're forfeiting this feature.
+    //
+    // The conclusion was made it is likely appropriate for the Cargo nightlies
+    // to statically link OpenSSL, but highly encourage distributions and
+    // packagers of Cargo to dynamically link OpenSSL. Packagers are targeting
+    // one system and are distributing to only that system, so none of the
+    // problems mentioned above would arise.
+    //
+    // In order to support this, a new package was made: openssl-static-sys.
+    // This package currently performs a fairly simple task:
+    //
+    // 1. Run pkg-config to discover where openssl is installed.
+    // 2. If openssl is installed in a nonstandard location, *and* static copies
+    //    of the libraries are available, copy them to $OUT_DIR.
+    //
+    // This library will bring in libssl.a and libcrypto.a into the local build,
+    // allowing them to be picked up by this crate. This allows us to configure
+    // our own buildbots to have pkg-config point to these local pre-built
+    // copies of a static OpenSSL (with very few dependencies) while allowing
+    // most other builds of Cargo to naturally dynamically link OpenSSL.
+    //
+    // So in summary, if you're with me so far, we've statically linked OpenSSL
+    // to the Cargo binary (or any binary, for that matter) and we're ready to
+    // distribute it to *all* linux distributions. Remember that our original
+    // intent for openssl was for HTTPS support, which implies that we need some
+    // for of CA certificate store to validate certificates. This is normally
+    // installed in a standard system location.
+    //
+    // Unfortunately, as one might imagine, OpenSSL is configured for where this
+    // standard location is at *build time*, but it often varies widely
+    // per-system. Consequently, it was discovered that OpenSSL will respect the
+    // SSL_CERT_FILE and SSL_CERT_DIR environment variables in order to assist
+    // in discovering the location of this file (hurray!).
+    //
+    // So, finally getting to the point, this function solely exists to support
+    // our static builds of OpenSSL by probing for the "standard system
+    // location" of certificates and setting relevant environment variable to
+    // point to them.
+    //
+    // Ah, and as a final note, this is only a problem on Linux, not on OS X. On
+    // OS X the OpenSSL binaries are stable enough that we can just rely on
+    // dynamic linkage (plus they have some weird modifications to OpenSSL which
+    // means we wouldn't want to link statically).
+    openssl_probe::init_ssl_cert_env_vars();
+}
+
+#[cfg(any(
+    windows,
+    target_os = "macos",
+    target_os = "ios",
+    not(feature = "https")
+))]
+fn openssl_env_init() {}
+
+unsafe fn opt_bytes<'a, T>(_anchor: &'a T, c: *const libc::c_char) -> Option<&'a [u8]> {
+    if c.is_null() {
+        None
+    } else {
+        Some(CStr::from_ptr(c).to_bytes())
+    }
+}
+
+fn opt_cstr<T: IntoCString>(o: Option<T>) -> Result<Option<CString>, Error> {
+    match o {
+        Some(s) => s.into_c_string().map(Some),
+        None => Ok(None),
+    }
+}
+
+impl ObjectType {
+    /// Convert an object type to its string representation.
+    pub fn str(&self) -> &'static str {
+        unsafe {
+            let ptr = call!(raw::git_object_type2string(*self)) as *const _;
+            let data = CStr::from_ptr(ptr).to_bytes();
+            str::from_utf8(data).unwrap()
+        }
+    }
+
+    /// Determine if the given git_object_t is a valid loose object type.
+    pub fn is_loose(&self) -> bool {
+        unsafe { call!(raw::git_object_typeisloose(*self)) == 1 }
+    }
+
+    /// Convert a raw git_object_t to an ObjectType
+    pub fn from_raw(raw: raw::git_object_t) -> Option<ObjectType> {
+        match raw {
+            raw::GIT_OBJECT_ANY => Some(ObjectType::Any),
+            raw::GIT_OBJECT_COMMIT => Some(ObjectType::Commit),
+            raw::GIT_OBJECT_TREE => Some(ObjectType::Tree),
+            raw::GIT_OBJECT_BLOB => Some(ObjectType::Blob),
+            raw::GIT_OBJECT_TAG => Some(ObjectType::Tag),
+            _ => None,
+        }
+    }
+
+    /// Convert this kind into its raw representation
+    pub fn raw(&self) -> raw::git_object_t {
+        call::convert(self)
+    }
+
+    /// Convert a string object type representation to its object type.
+    pub fn from_str(s: &str) -> Option<ObjectType> {
+        let raw = unsafe { call!(raw::git_object_string2type(CString::new(s).unwrap())) };
+        ObjectType::from_raw(raw)
+    }
+}
+
+impl fmt::Display for ObjectType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.str().fmt(f)
+    }
+}
+
+impl ReferenceType {
+    /// Convert an object type to its string representation.
+    pub fn str(&self) -> &'static str {
+        match self {
+            ReferenceType::Direct => "direct",
+            ReferenceType::Symbolic => "symbolic",
+        }
+    }
+
+    /// Convert a raw git_reference_t to a ReferenceType.
+    pub fn from_raw(raw: raw::git_reference_t) -> Option<ReferenceType> {
+        match raw {
+            raw::GIT_REFERENCE_DIRECT => Some(ReferenceType::Direct),
+            raw::GIT_REFERENCE_SYMBOLIC => Some(ReferenceType::Symbolic),
+            _ => None,
+        }
+    }
+}
+
+impl fmt::Display for ReferenceType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.str().fmt(f)
+    }
+}
+
+impl ConfigLevel {
+    /// Converts a raw configuration level to a ConfigLevel
+    pub fn from_raw(raw: raw::git_config_level_t) -> ConfigLevel {
+        match raw {
+            raw::GIT_CONFIG_LEVEL_PROGRAMDATA => ConfigLevel::ProgramData,
+            raw::GIT_CONFIG_LEVEL_SYSTEM => ConfigLevel::System,
+            raw::GIT_CONFIG_LEVEL_XDG => ConfigLevel::XDG,
+            raw::GIT_CONFIG_LEVEL_GLOBAL => ConfigLevel::Global,
+            raw::GIT_CONFIG_LEVEL_LOCAL => ConfigLevel::Local,
+            raw::GIT_CONFIG_LEVEL_WORKTREE => ConfigLevel::Worktree,
+            raw::GIT_CONFIG_LEVEL_APP => ConfigLevel::App,
+            raw::GIT_CONFIG_HIGHEST_LEVEL => ConfigLevel::Highest,
+            n => panic!("unknown config level: {}", n),
+        }
+    }
+}
+
+impl SubmoduleIgnore {
+    /// Converts a [`raw::git_submodule_ignore_t`] to a [`SubmoduleIgnore`]
+    pub fn from_raw(raw: raw::git_submodule_ignore_t) -> Self {
+        match raw {
+            raw::GIT_SUBMODULE_IGNORE_UNSPECIFIED => SubmoduleIgnore::Unspecified,
+            raw::GIT_SUBMODULE_IGNORE_NONE => SubmoduleIgnore::None,
+            raw::GIT_SUBMODULE_IGNORE_UNTRACKED => SubmoduleIgnore::Untracked,
+            raw::GIT_SUBMODULE_IGNORE_DIRTY => SubmoduleIgnore::Dirty,
+            raw::GIT_SUBMODULE_IGNORE_ALL => SubmoduleIgnore::All,
+            n => panic!("unknown submodule ignore rule: {}", n),
+        }
+    }
+}
+
+impl SubmoduleUpdate {
+    /// Converts a [`raw::git_submodule_update_t`] to a [`SubmoduleUpdate`]
+    pub fn from_raw(raw: raw::git_submodule_update_t) -> Self {
+        match raw {
+            raw::GIT_SUBMODULE_UPDATE_CHECKOUT => SubmoduleUpdate::Checkout,
+            raw::GIT_SUBMODULE_UPDATE_REBASE => SubmoduleUpdate::Rebase,
+            raw::GIT_SUBMODULE_UPDATE_MERGE => SubmoduleUpdate::Merge,
+            raw::GIT_SUBMODULE_UPDATE_NONE => SubmoduleUpdate::None,
+            raw::GIT_SUBMODULE_UPDATE_DEFAULT => SubmoduleUpdate::Default,
+            n => panic!("unknown submodule update strategy: {}", n),
+        }
+    }
+}
+
+bitflags! {
+    /// Status flags for a single file
+    ///
+    /// A combination of these values will be returned to indicate the status of
+    /// a file.  Status compares the working directory, the index, and the
+    /// current HEAD of the repository.  The `STATUS_INDEX_*` set of flags
+    /// represents the status of file in the index relative to the HEAD, and the
+    /// `STATUS_WT_*` set of flags represent the status of the file in the
+    /// working directory relative to the index.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct Status: u32 {
+        #[allow(missing_docs)]
+        const CURRENT = raw::GIT_STATUS_CURRENT as u32;
+
+        #[allow(missing_docs)]
+        const INDEX_NEW = raw::GIT_STATUS_INDEX_NEW as u32;
+        #[allow(missing_docs)]
+        const INDEX_MODIFIED = raw::GIT_STATUS_INDEX_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const INDEX_DELETED = raw::GIT_STATUS_INDEX_DELETED as u32;
+        #[allow(missing_docs)]
+        const INDEX_RENAMED = raw::GIT_STATUS_INDEX_RENAMED as u32;
+        #[allow(missing_docs)]
+        const INDEX_TYPECHANGE = raw::GIT_STATUS_INDEX_TYPECHANGE as u32;
+
+        #[allow(missing_docs)]
+        const WT_NEW = raw::GIT_STATUS_WT_NEW as u32;
+        #[allow(missing_docs)]
+        const WT_MODIFIED = raw::GIT_STATUS_WT_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const WT_DELETED = raw::GIT_STATUS_WT_DELETED as u32;
+        #[allow(missing_docs)]
+        const WT_TYPECHANGE = raw::GIT_STATUS_WT_TYPECHANGE as u32;
+        #[allow(missing_docs)]
+        const WT_RENAMED = raw::GIT_STATUS_WT_RENAMED as u32;
+
+        #[allow(missing_docs)]
+        const IGNORED = raw::GIT_STATUS_IGNORED as u32;
+        #[allow(missing_docs)]
+        const CONFLICTED = raw::GIT_STATUS_CONFLICTED as u32;
+    }
+}
+
+impl Status {
+    is_bit_set!(is_index_new, Status::INDEX_NEW);
+    is_bit_set!(is_index_modified, Status::INDEX_MODIFIED);
+    is_bit_set!(is_index_deleted, Status::INDEX_DELETED);
+    is_bit_set!(is_index_renamed, Status::INDEX_RENAMED);
+    is_bit_set!(is_index_typechange, Status::INDEX_TYPECHANGE);
+    is_bit_set!(is_wt_new, Status::WT_NEW);
+    is_bit_set!(is_wt_modified, Status::WT_MODIFIED);
+    is_bit_set!(is_wt_deleted, Status::WT_DELETED);
+    is_bit_set!(is_wt_typechange, Status::WT_TYPECHANGE);
+    is_bit_set!(is_wt_renamed, Status::WT_RENAMED);
+    is_bit_set!(is_ignored, Status::IGNORED);
+    is_bit_set!(is_conflicted, Status::CONFLICTED);
+}
+
+bitflags! {
+    /// Mode options for RepositoryInitOptions
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct RepositoryInitMode: u32 {
+        /// Use permissions configured by umask - the default
+        const SHARED_UMASK = raw::GIT_REPOSITORY_INIT_SHARED_UMASK as u32;
+        /// Use `--shared=group` behavior, chmod'ing the new repo to be
+        /// group writable and \"g+sx\" for sticky group assignment
+        const SHARED_GROUP = raw::GIT_REPOSITORY_INIT_SHARED_GROUP as u32;
+        /// Use `--shared=all` behavior, adding world readability.
+        const SHARED_ALL = raw::GIT_REPOSITORY_INIT_SHARED_ALL as u32;
+    }
+}
+
+impl RepositoryInitMode {
+    is_bit_set!(is_shared_umask, RepositoryInitMode::SHARED_UMASK);
+    is_bit_set!(is_shared_group, RepositoryInitMode::SHARED_GROUP);
+    is_bit_set!(is_shared_all, RepositoryInitMode::SHARED_ALL);
+}
+
+/// What type of change is described by a `DiffDelta`?
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Delta {
+    /// No changes
+    Unmodified,
+    /// Entry does not exist in old version
+    Added,
+    /// Entry does not exist in new version
+    Deleted,
+    /// Entry content changed between old and new
+    Modified,
+    /// Entry was renamed between old and new
+    Renamed,
+    /// Entry was copied from another old entry
+    Copied,
+    /// Entry is ignored item in workdir
+    Ignored,
+    /// Entry is untracked item in workdir
+    Untracked,
+    /// Type of entry changed between old and new
+    Typechange,
+    /// Entry is unreadable
+    Unreadable,
+    /// Entry in the index is conflicted
+    Conflicted,
+}
+
+/// Valid modes for index and tree entries.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum FileMode {
+    /// Unreadable
+    Unreadable,
+    /// Tree
+    Tree,
+    /// Blob
+    Blob,
+    /// Group writable blob. Obsolete mode kept for compatibility reasons
+    BlobGroupWritable,
+    /// Blob executable
+    BlobExecutable,
+    /// Link
+    Link,
+    /// Commit
+    Commit,
+}
+
+impl From<FileMode> for i32 {
+    fn from(mode: FileMode) -> i32 {
+        match mode {
+            FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE as i32,
+            FileMode::Tree => raw::GIT_FILEMODE_TREE as i32,
+            FileMode::Blob => raw::GIT_FILEMODE_BLOB as i32,
+            FileMode::BlobGroupWritable => raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE as i32,
+            FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE as i32,
+            FileMode::Link => raw::GIT_FILEMODE_LINK as i32,
+            FileMode::Commit => raw::GIT_FILEMODE_COMMIT as i32,
+        }
+    }
+}
+
+impl From<FileMode> for u32 {
+    fn from(mode: FileMode) -> u32 {
+        match mode {
+            FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE as u32,
+            FileMode::Tree => raw::GIT_FILEMODE_TREE as u32,
+            FileMode::Blob => raw::GIT_FILEMODE_BLOB as u32,
+            FileMode::BlobGroupWritable => raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE as u32,
+            FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE as u32,
+            FileMode::Link => raw::GIT_FILEMODE_LINK as u32,
+            FileMode::Commit => raw::GIT_FILEMODE_COMMIT as u32,
+        }
+    }
+}
+
+bitflags! {
+    /// Return codes for submodule status.
+    ///
+    /// A combination of these flags will be returned to describe the status of a
+    /// submodule.  Depending on the "ignore" property of the submodule, some of
+    /// the flags may never be returned because they indicate changes that are
+    /// supposed to be ignored.
+    ///
+    /// Submodule info is contained in 4 places: the HEAD tree, the index, config
+    /// files (both .git/config and .gitmodules), and the working directory.  Any
+    /// or all of those places might be missing information about the submodule
+    /// depending on what state the repo is in.  We consider all four places to
+    /// build the combination of status flags.
+    ///
+    /// There are four values that are not really status, but give basic info
+    /// about what sources of submodule data are available.  These will be
+    /// returned even if ignore is set to "ALL".
+    ///
+    /// * IN_HEAD   - superproject head contains submodule
+    /// * IN_INDEX  - superproject index contains submodule
+    /// * IN_CONFIG - superproject gitmodules has submodule
+    /// * IN_WD     - superproject workdir has submodule
+    ///
+    /// The following values will be returned so long as ignore is not "ALL".
+    ///
+    /// * INDEX_ADDED       - in index, not in head
+    /// * INDEX_DELETED     - in head, not in index
+    /// * INDEX_MODIFIED    - index and head don't match
+    /// * WD_UNINITIALIZED  - workdir contains empty directory
+    /// * WD_ADDED          - in workdir, not index
+    /// * WD_DELETED        - in index, not workdir
+    /// * WD_MODIFIED       - index and workdir head don't match
+    ///
+    /// The following can only be returned if ignore is "NONE" or "UNTRACKED".
+    ///
+    /// * WD_INDEX_MODIFIED - submodule workdir index is dirty
+    /// * WD_WD_MODIFIED    - submodule workdir has modified files
+    ///
+    /// Lastly, the following will only be returned for ignore "NONE".
+    ///
+    /// * WD_UNTRACKED      - workdir contains untracked files
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct SubmoduleStatus: u32 {
+        #[allow(missing_docs)]
+        const IN_HEAD = raw::GIT_SUBMODULE_STATUS_IN_HEAD as u32;
+        #[allow(missing_docs)]
+        const IN_INDEX = raw::GIT_SUBMODULE_STATUS_IN_INDEX as u32;
+        #[allow(missing_docs)]
+        const IN_CONFIG = raw::GIT_SUBMODULE_STATUS_IN_CONFIG as u32;
+        #[allow(missing_docs)]
+        const IN_WD = raw::GIT_SUBMODULE_STATUS_IN_WD as u32;
+        #[allow(missing_docs)]
+        const INDEX_ADDED = raw::GIT_SUBMODULE_STATUS_INDEX_ADDED as u32;
+        #[allow(missing_docs)]
+        const INDEX_DELETED = raw::GIT_SUBMODULE_STATUS_INDEX_DELETED as u32;
+        #[allow(missing_docs)]
+        const INDEX_MODIFIED = raw::GIT_SUBMODULE_STATUS_INDEX_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const WD_UNINITIALIZED =
+                raw::GIT_SUBMODULE_STATUS_WD_UNINITIALIZED as u32;
+        #[allow(missing_docs)]
+        const WD_ADDED = raw::GIT_SUBMODULE_STATUS_WD_ADDED as u32;
+        #[allow(missing_docs)]
+        const WD_DELETED = raw::GIT_SUBMODULE_STATUS_WD_DELETED as u32;
+        #[allow(missing_docs)]
+        const WD_MODIFIED = raw::GIT_SUBMODULE_STATUS_WD_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const WD_INDEX_MODIFIED =
+                raw::GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const WD_WD_MODIFIED = raw::GIT_SUBMODULE_STATUS_WD_WD_MODIFIED as u32;
+        #[allow(missing_docs)]
+        const WD_UNTRACKED = raw::GIT_SUBMODULE_STATUS_WD_UNTRACKED as u32;
+    }
+}
+
+impl SubmoduleStatus {
+    is_bit_set!(is_in_head, SubmoduleStatus::IN_HEAD);
+    is_bit_set!(is_in_index, SubmoduleStatus::IN_INDEX);
+    is_bit_set!(is_in_config, SubmoduleStatus::IN_CONFIG);
+    is_bit_set!(is_in_wd, SubmoduleStatus::IN_WD);
+    is_bit_set!(is_index_added, SubmoduleStatus::INDEX_ADDED);
+    is_bit_set!(is_index_deleted, SubmoduleStatus::INDEX_DELETED);
+    is_bit_set!(is_index_modified, SubmoduleStatus::INDEX_MODIFIED);
+    is_bit_set!(is_wd_uninitialized, SubmoduleStatus::WD_UNINITIALIZED);
+    is_bit_set!(is_wd_added, SubmoduleStatus::WD_ADDED);
+    is_bit_set!(is_wd_deleted, SubmoduleStatus::WD_DELETED);
+    is_bit_set!(is_wd_modified, SubmoduleStatus::WD_MODIFIED);
+    is_bit_set!(is_wd_wd_modified, SubmoduleStatus::WD_WD_MODIFIED);
+    is_bit_set!(is_wd_untracked, SubmoduleStatus::WD_UNTRACKED);
+}
+
+/// Submodule ignore values
+///
+/// These values represent settings for the `submodule.$name.ignore`
+/// configuration value which says how deeply to look at the working
+/// directory when getting the submodule status.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum SubmoduleIgnore {
+    /// Use the submodule's configuration
+    Unspecified,
+    /// Any change or untracked file is considered dirty
+    None,
+    /// Only dirty if tracked files have changed
+    Untracked,
+    /// Only dirty if HEAD has moved
+    Dirty,
+    /// Never dirty
+    All,
+}
+
+/// Submodule update values
+///
+/// These values represent settings for the `submodule.$name.update`
+/// configuration value which says how to handle `git submodule update`
+/// for this submodule. The value is usually set in the ".gitmodules"
+/// file and copied to ".git/config" when the submodule is initialized.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum SubmoduleUpdate {
+    /// The default; when a submodule is updated, checkout the new detached
+    /// HEAD to the submodule directory.
+    Checkout,
+    /// Update by rebasing the current checked out branch onto the commit from
+    /// the superproject.
+    Rebase,
+    /// Update by merging the commit in the superproject into the current
+    /// checkout out branch of the submodule.
+    Merge,
+    /// Do not update this submodule even when the commit in the superproject
+    /// is updated.
+    None,
+    /// Not used except as static initializer when we don't want any particular
+    /// update rule to be specified.
+    Default,
+}
+
+bitflags! {
+    /// ...
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct PathspecFlags: u32 {
+        /// Use the default pathspec matching configuration.
+        const DEFAULT = raw::GIT_PATHSPEC_DEFAULT as u32;
+        /// Force matching to ignore case, otherwise matching will use native
+        /// case sensitivity of the platform filesystem.
+        const IGNORE_CASE = raw::GIT_PATHSPEC_IGNORE_CASE as u32;
+        /// Force case sensitive matches, otherwise match will use the native
+        /// case sensitivity of the platform filesystem.
+        const USE_CASE = raw::GIT_PATHSPEC_USE_CASE as u32;
+        /// Disable glob patterns and just use simple string comparison for
+        /// matching.
+        const NO_GLOB = raw::GIT_PATHSPEC_NO_GLOB as u32;
+        /// Means that match functions return the error code `NotFound` if no
+        /// matches are found. By default no matches is a success.
+        const NO_MATCH_ERROR = raw::GIT_PATHSPEC_NO_MATCH_ERROR as u32;
+        /// Means that the list returned should track which patterns matched
+        /// which files so that at the end of the match we can identify patterns
+        /// that did not match any files.
+        const FIND_FAILURES = raw::GIT_PATHSPEC_FIND_FAILURES as u32;
+        /// Means that the list returned does not need to keep the actual
+        /// matching filenames. Use this to just test if there were any matches
+        /// at all or in combination with `PATHSPEC_FAILURES` to validate a
+        /// pathspec.
+        const FAILURES_ONLY = raw::GIT_PATHSPEC_FAILURES_ONLY as u32;
+    }
+}
+
+impl PathspecFlags {
+    is_bit_set!(is_default, PathspecFlags::DEFAULT);
+    is_bit_set!(is_ignore_case, PathspecFlags::IGNORE_CASE);
+    is_bit_set!(is_use_case, PathspecFlags::USE_CASE);
+    is_bit_set!(is_no_glob, PathspecFlags::NO_GLOB);
+    is_bit_set!(is_no_match_error, PathspecFlags::NO_MATCH_ERROR);
+    is_bit_set!(is_find_failures, PathspecFlags::FIND_FAILURES);
+    is_bit_set!(is_failures_only, PathspecFlags::FAILURES_ONLY);
+}
+
+impl Default for PathspecFlags {
+    fn default() -> Self {
+        PathspecFlags::DEFAULT
+    }
+}
+
+bitflags! {
+    /// Types of notifications emitted from checkouts.
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct CheckoutNotificationType: u32 {
+        /// Notification about a conflict.
+        const CONFLICT = raw::GIT_CHECKOUT_NOTIFY_CONFLICT as u32;
+        /// Notification about a dirty file.
+        const DIRTY = raw::GIT_CHECKOUT_NOTIFY_DIRTY as u32;
+        /// Notification about an updated file.
+        const UPDATED = raw::GIT_CHECKOUT_NOTIFY_UPDATED as u32;
+        /// Notification about an untracked file.
+        const UNTRACKED = raw::GIT_CHECKOUT_NOTIFY_UNTRACKED as u32;
+        /// Notification about an ignored file.
+        const IGNORED = raw::GIT_CHECKOUT_NOTIFY_IGNORED as u32;
+    }
+}
+
+impl CheckoutNotificationType {
+    is_bit_set!(is_conflict, CheckoutNotificationType::CONFLICT);
+    is_bit_set!(is_dirty, CheckoutNotificationType::DIRTY);
+    is_bit_set!(is_updated, CheckoutNotificationType::UPDATED);
+    is_bit_set!(is_untracked, CheckoutNotificationType::UNTRACKED);
+    is_bit_set!(is_ignored, CheckoutNotificationType::IGNORED);
+}
+
+/// Possible output formats for diff data
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum DiffFormat {
+    /// full git diff
+    Patch,
+    /// just the headers of the patch
+    PatchHeader,
+    /// like git diff --raw
+    Raw,
+    /// like git diff --name-only
+    NameOnly,
+    /// like git diff --name-status
+    NameStatus,
+    /// git diff as used by git patch-id
+    PatchId,
+}
+
+bitflags! {
+    /// Formatting options for diff stats
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct DiffStatsFormat: raw::git_diff_stats_format_t {
+        /// Don't generate any stats
+        const NONE = raw::GIT_DIFF_STATS_NONE;
+        /// Equivalent of `--stat` in git
+        const FULL = raw::GIT_DIFF_STATS_FULL;
+        /// Equivalent of `--shortstat` in git
+        const SHORT = raw::GIT_DIFF_STATS_SHORT;
+        /// Equivalent of `--numstat` in git
+        const NUMBER = raw::GIT_DIFF_STATS_NUMBER;
+        /// Extended header information such as creations, renames and mode
+        /// changes, equivalent of `--summary` in git
+        const INCLUDE_SUMMARY = raw::GIT_DIFF_STATS_INCLUDE_SUMMARY;
+    }
+}
+
+impl DiffStatsFormat {
+    is_bit_set!(is_none, DiffStatsFormat::NONE);
+    is_bit_set!(is_full, DiffStatsFormat::FULL);
+    is_bit_set!(is_short, DiffStatsFormat::SHORT);
+    is_bit_set!(is_number, DiffStatsFormat::NUMBER);
+    is_bit_set!(is_include_summary, DiffStatsFormat::INCLUDE_SUMMARY);
+}
+
+/// Automatic tag following options.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum AutotagOption {
+    /// Use the setting from the remote's configuration
+    Unspecified,
+    /// Ask the server for tags pointing to objects we're already downloading
+    Auto,
+    /// Don't ask for any tags beyond the refspecs
+    None,
+    /// Ask for all the tags
+    All,
+}
+
+/// Configuration for how pruning is done on a fetch
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum FetchPrune {
+    /// Use the setting from the configuration
+    Unspecified,
+    /// Force pruning on
+    On,
+    /// Force pruning off
+    Off,
+}
+
+#[allow(missing_docs)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum StashApplyProgress {
+    /// None
+    None,
+    /// Loading the stashed data from the object database
+    LoadingStash,
+    /// The stored index is being analyzed
+    AnalyzeIndex,
+    /// The modified files are being analyzed
+    AnalyzeModified,
+    /// The untracked and ignored files are being analyzed
+    AnalyzeUntracked,
+    /// The untracked files are being written to disk
+    CheckoutUntracked,
+    /// The modified files are being written to disk
+    CheckoutModified,
+    /// The stash was applied successfully
+    Done,
+}
+
+bitflags! {
+    #[allow(missing_docs)]
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct StashApplyFlags: u32 {
+        #[allow(missing_docs)]
+        const DEFAULT = raw::GIT_STASH_APPLY_DEFAULT as u32;
+        /// Try to reinstate not only the working tree's changes,
+        /// but also the index's changes.
+        const REINSTATE_INDEX = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32;
+    }
+}
+
+impl StashApplyFlags {
+    is_bit_set!(is_default, StashApplyFlags::DEFAULT);
+    is_bit_set!(is_reinstate_index, StashApplyFlags::REINSTATE_INDEX);
+}
+
+impl Default for StashApplyFlags {
+    fn default() -> Self {
+        StashApplyFlags::DEFAULT
+    }
+}
+
+bitflags! {
+    #[allow(missing_docs)]
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct StashFlags: u32 {
+        #[allow(missing_docs)]
+        const DEFAULT = raw::GIT_STASH_DEFAULT as u32;
+        /// All changes already added to the index are left intact in
+        /// the working directory
+        const KEEP_INDEX = raw::GIT_STASH_KEEP_INDEX as u32;
+        /// All untracked files are also stashed and then cleaned up
+        /// from the working directory
+        const INCLUDE_UNTRACKED = raw::GIT_STASH_INCLUDE_UNTRACKED as u32;
+        /// All ignored files are also stashed and then cleaned up from
+        /// the working directory
+        const INCLUDE_IGNORED = raw::GIT_STASH_INCLUDE_IGNORED as u32;
+        /// All changes in the index and working directory are left intact
+        const KEEP_ALL = raw::GIT_STASH_KEEP_ALL as u32;
+    }
+}
+
+impl StashFlags {
+    is_bit_set!(is_default, StashFlags::DEFAULT);
+    is_bit_set!(is_keep_index, StashFlags::KEEP_INDEX);
+    is_bit_set!(is_include_untracked, StashFlags::INCLUDE_UNTRACKED);
+    is_bit_set!(is_include_ignored, StashFlags::INCLUDE_IGNORED);
+}
+
+impl Default for StashFlags {
+    fn default() -> Self {
+        StashFlags::DEFAULT
+    }
+}
+
+bitflags! {
+    #[allow(missing_docs)]
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct AttrCheckFlags: u32 {
+        /// Check the working directory, then the index.
+        const FILE_THEN_INDEX = raw::GIT_ATTR_CHECK_FILE_THEN_INDEX as u32;
+        /// Check the index, then the working directory.
+        const INDEX_THEN_FILE = raw::GIT_ATTR_CHECK_INDEX_THEN_FILE as u32;
+        /// Check the index only.
+        const INDEX_ONLY = raw::GIT_ATTR_CHECK_INDEX_ONLY as u32;
+        /// Do not use the system gitattributes file.
+        const NO_SYSTEM = raw::GIT_ATTR_CHECK_NO_SYSTEM as u32;
+    }
+}
+
+impl Default for AttrCheckFlags {
+    fn default() -> Self {
+        AttrCheckFlags::FILE_THEN_INDEX
+    }
+}
+
+bitflags! {
+    #[allow(missing_docs)]
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct DiffFlags: u32 {
+        /// File(s) treated as binary data.
+        const BINARY = raw::GIT_DIFF_FLAG_BINARY as u32;
+        /// File(s) treated as text data.
+        const NOT_BINARY = raw::GIT_DIFF_FLAG_NOT_BINARY as u32;
+        /// `id` value is known correct.
+        const VALID_ID = raw::GIT_DIFF_FLAG_VALID_ID as u32;
+        /// File exists at this side of the delta.
+        const EXISTS = raw::GIT_DIFF_FLAG_EXISTS as u32;
+    }
+}
+
+impl DiffFlags {
+    is_bit_set!(is_binary, DiffFlags::BINARY);
+    is_bit_set!(is_not_binary, DiffFlags::NOT_BINARY);
+    is_bit_set!(has_valid_id, DiffFlags::VALID_ID);
+    is_bit_set!(exists, DiffFlags::EXISTS);
+}
+
+bitflags! {
+    /// Options for [`Reference::normalize_name`].
+    #[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
+    pub struct ReferenceFormat: u32 {
+        /// No particular normalization.
+        const NORMAL = raw::GIT_REFERENCE_FORMAT_NORMAL as u32;
+        /// Control whether one-level refname are accepted (i.e., refnames that
+        /// do not contain multiple `/`-separated components). Those are
+        /// expected to be written only using uppercase letters and underscore
+        /// (e.g. `HEAD`, `FETCH_HEAD`).
+        const ALLOW_ONELEVEL = raw::GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL as u32;
+        /// Interpret the provided name as a reference pattern for a refspec (as
+        /// used with remote repositories). If this option is enabled, the name
+        /// is allowed to contain a single `*` in place of a full pathname
+        /// components (e.g., `foo/*/bar` but not `foo/bar*`).
+        const REFSPEC_PATTERN = raw::GIT_REFERENCE_FORMAT_REFSPEC_PATTERN as u32;
+        /// Interpret the name as part of a refspec in shorthand form so the
+        /// `ALLOW_ONELEVEL` naming rules aren't enforced and `main` becomes a
+        /// valid name.
+        const REFSPEC_SHORTHAND = raw::GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND as u32;
+    }
+}
+
+impl ReferenceFormat {
+    is_bit_set!(is_allow_onelevel, ReferenceFormat::ALLOW_ONELEVEL);
+    is_bit_set!(is_refspec_pattern, ReferenceFormat::REFSPEC_PATTERN);
+    is_bit_set!(is_refspec_shorthand, ReferenceFormat::REFSPEC_SHORTHAND);
+}
+
+impl Default for ReferenceFormat {
+    fn default() -> Self {
+        ReferenceFormat::NORMAL
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{FileMode, ObjectType};
+
+    #[test]
+    fn convert() {
+        assert_eq!(ObjectType::Blob.str(), "blob");
+        assert_eq!(ObjectType::from_str("blob"), Some(ObjectType::Blob));
+        assert!(ObjectType::Blob.is_loose());
+    }
+
+    #[test]
+    fn convert_filemode() {
+        assert_eq!(i32::from(FileMode::Blob), 0o100644);
+        assert_eq!(i32::from(FileMode::BlobGroupWritable), 0o100664);
+        assert_eq!(i32::from(FileMode::BlobExecutable), 0o100755);
+        assert_eq!(u32::from(FileMode::Blob), 0o100644);
+        assert_eq!(u32::from(FileMode::BlobGroupWritable), 0o100664);
+        assert_eq!(u32::from(FileMode::BlobExecutable), 0o100755);
+    }
+
+    #[test]
+    fn bitflags_partial_eq() {
+        use super::{
+            AttrCheckFlags, CheckoutNotificationType, CredentialType, DiffFlags, DiffStatsFormat,
+            IndexAddOption, IndexEntryExtendedFlag, IndexEntryFlag, MergeAnalysis, MergePreference,
+            OdbLookupFlags, PathspecFlags, ReferenceFormat, RepositoryInitMode,
+            RepositoryOpenFlags, RevparseMode, Sort, StashApplyFlags, StashFlags, Status,
+            SubmoduleStatus,
+        };
+
+        assert_eq!(
+            AttrCheckFlags::FILE_THEN_INDEX,
+            AttrCheckFlags::FILE_THEN_INDEX
+        );
+        assert_eq!(
+            CheckoutNotificationType::CONFLICT,
+            CheckoutNotificationType::CONFLICT
+        );
+        assert_eq!(
+            CredentialType::USER_PASS_PLAINTEXT,
+            CredentialType::USER_PASS_PLAINTEXT
+        );
+        assert_eq!(DiffFlags::BINARY, DiffFlags::BINARY);
+        assert_eq!(
+            DiffStatsFormat::INCLUDE_SUMMARY,
+            DiffStatsFormat::INCLUDE_SUMMARY
+        );
+        assert_eq!(
+            IndexAddOption::CHECK_PATHSPEC,
+            IndexAddOption::CHECK_PATHSPEC
+        );
+        assert_eq!(
+            IndexEntryExtendedFlag::INTENT_TO_ADD,
+            IndexEntryExtendedFlag::INTENT_TO_ADD
+        );
+        assert_eq!(IndexEntryFlag::EXTENDED, IndexEntryFlag::EXTENDED);
+        assert_eq!(
+            MergeAnalysis::ANALYSIS_FASTFORWARD,
+            MergeAnalysis::ANALYSIS_FASTFORWARD
+        );
+        assert_eq!(
+            MergePreference::FASTFORWARD_ONLY,
+            MergePreference::FASTFORWARD_ONLY
+        );
+        assert_eq!(OdbLookupFlags::NO_REFRESH, OdbLookupFlags::NO_REFRESH);
+        assert_eq!(PathspecFlags::FAILURES_ONLY, PathspecFlags::FAILURES_ONLY);
+        assert_eq!(
+            ReferenceFormat::ALLOW_ONELEVEL,
+            ReferenceFormat::ALLOW_ONELEVEL
+        );
+        assert_eq!(
+            RepositoryInitMode::SHARED_ALL,
+            RepositoryInitMode::SHARED_ALL
+        );
+        assert_eq!(RepositoryOpenFlags::CROSS_FS, RepositoryOpenFlags::CROSS_FS);
+        assert_eq!(RevparseMode::RANGE, RevparseMode::RANGE);
+        assert_eq!(Sort::REVERSE, Sort::REVERSE);
+        assert_eq!(
+            StashApplyFlags::REINSTATE_INDEX,
+            StashApplyFlags::REINSTATE_INDEX
+        );
+        assert_eq!(StashFlags::INCLUDE_IGNORED, StashFlags::INCLUDE_IGNORED);
+        assert_eq!(Status::WT_MODIFIED, Status::WT_MODIFIED);
+        assert_eq!(SubmoduleStatus::WD_ADDED, SubmoduleStatus::WD_ADDED);
+    }
+}
diff --git a/git2/src/mailmap.rs b/git2/src/mailmap.rs
new file mode 100644 (file)
index 0000000..096b322
--- /dev/null
@@ -0,0 +1,134 @@
+use std::ffi::CString;
+use std::ptr;
+
+use crate::util::Binding;
+use crate::{raw, Error, Signature};
+
+/// A structure to represent a repository's .mailmap file.
+///
+/// The representation cannot be written to disk.
+pub struct Mailmap {
+    raw: *mut raw::git_mailmap,
+}
+
+impl Binding for Mailmap {
+    type Raw = *mut raw::git_mailmap;
+
+    unsafe fn from_raw(ptr: *mut raw::git_mailmap) -> Mailmap {
+        Mailmap { raw: ptr }
+    }
+
+    fn raw(&self) -> *mut raw::git_mailmap {
+        self.raw
+    }
+}
+
+impl Drop for Mailmap {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_mailmap_free(self.raw);
+        }
+    }
+}
+
+impl Mailmap {
+    /// Creates an empty, in-memory mailmap object.
+    pub fn new() -> Result<Mailmap, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_mailmap_new(&mut ret));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Creates an in-memory mailmap object representing the given buffer.
+    pub fn from_buffer(buf: &str) -> Result<Mailmap, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let len = buf.len();
+        let buf = CString::new(buf)?;
+        unsafe {
+            try_call!(raw::git_mailmap_from_buffer(&mut ret, buf, len));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Adds a new entry to this in-memory mailmap object.
+    pub fn add_entry(
+        &mut self,
+        real_name: Option<&str>,
+        real_email: Option<&str>,
+        replace_name: Option<&str>,
+        replace_email: &str,
+    ) -> Result<(), Error> {
+        let real_name = crate::opt_cstr(real_name)?;
+        let real_email = crate::opt_cstr(real_email)?;
+        let replace_name = crate::opt_cstr(replace_name)?;
+        let replace_email = CString::new(replace_email)?;
+        unsafe {
+            try_call!(raw::git_mailmap_add_entry(
+                self.raw,
+                real_name,
+                real_email,
+                replace_name,
+                replace_email
+            ));
+            Ok(())
+        }
+    }
+
+    /// Resolves a signature to its real name and email address.
+    pub fn resolve_signature(&self, sig: &Signature<'_>) -> Result<Signature<'static>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_mailmap_resolve_signature(
+                &mut ret,
+                &*self.raw,
+                sig.raw()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn smoke() {
+        let sig_name = "name";
+        let sig_email = "email";
+        let sig = t!(Signature::now(sig_name, sig_email));
+
+        let mut mm = t!(Mailmap::new());
+
+        let mailmapped_sig = t!(mm.resolve_signature(&sig));
+        assert_eq!(mailmapped_sig.name(), Some(sig_name));
+        assert_eq!(mailmapped_sig.email(), Some(sig_email));
+
+        t!(mm.add_entry(None, None, None, sig_email));
+        t!(mm.add_entry(
+            Some("real name"),
+            Some("real@email"),
+            Some(sig_name),
+            sig_email,
+        ));
+
+        let mailmapped_sig = t!(mm.resolve_signature(&sig));
+        assert_eq!(mailmapped_sig.name(), Some("real name"));
+        assert_eq!(mailmapped_sig.email(), Some("real@email"));
+    }
+
+    #[test]
+    fn from_buffer() {
+        let buf = "<prøper@emæil> <email>";
+        let mm = t!(Mailmap::from_buffer(&buf));
+
+        let sig = t!(Signature::now("name", "email"));
+        let mailmapped_sig = t!(mm.resolve_signature(&sig));
+        assert_eq!(mailmapped_sig.name(), Some("name"));
+        assert_eq!(mailmapped_sig.email(), Some("prøper@emæil"));
+    }
+}
diff --git a/git2/src/mempack.rs b/git2/src/mempack.rs
new file mode 100644 (file)
index 0000000..a780707
--- /dev/null
@@ -0,0 +1,49 @@
+use std::marker;
+
+use crate::util::Binding;
+use crate::{raw, Buf, Error, Odb, Repository};
+
+/// A structure to represent a mempack backend for the object database. The
+/// Mempack is bound to the Odb that it was created from, and cannot outlive
+/// that Odb.
+pub struct Mempack<'odb> {
+    raw: *mut raw::git_odb_backend,
+    _marker: marker::PhantomData<&'odb Odb<'odb>>,
+}
+
+impl<'odb> Binding for Mempack<'odb> {
+    type Raw = *mut raw::git_odb_backend;
+
+    unsafe fn from_raw(raw: *mut raw::git_odb_backend) -> Mempack<'odb> {
+        Mempack {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *mut raw::git_odb_backend {
+        self.raw
+    }
+}
+
+// We don't need to implement `Drop` for Mempack because it is owned by the
+// odb to which it is attached, and that will take care of freeing the mempack
+// and associated memory.
+
+impl<'odb> Mempack<'odb> {
+    /// Dumps the contents of the mempack into the provided buffer.
+    pub fn dump(&self, repo: &Repository, buf: &mut Buf) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_mempack_dump(buf.raw(), repo.raw(), self.raw));
+        }
+        Ok(())
+    }
+
+    /// Clears all data in the mempack.
+    pub fn reset(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_mempack_reset(self.raw));
+        }
+        Ok(())
+    }
+}
diff --git a/git2/src/merge.rs b/git2/src/merge.rs
new file mode 100644 (file)
index 0000000..6bd30c1
--- /dev/null
@@ -0,0 +1,194 @@
+use libc::c_uint;
+use std::marker;
+use std::mem;
+use std::str;
+
+use crate::call::Convert;
+use crate::util::Binding;
+use crate::{raw, Commit, FileFavor, Oid};
+
+/// A structure to represent an annotated commit, the input to merge and rebase.
+///
+/// An annotated commit contains information about how it was looked up, which
+/// may be useful for functions like merge or rebase to provide context to the
+/// operation.
+pub struct AnnotatedCommit<'repo> {
+    raw: *mut raw::git_annotated_commit,
+    _marker: marker::PhantomData<Commit<'repo>>,
+}
+
+/// Options to specify when merging.
+pub struct MergeOptions {
+    raw: raw::git_merge_options,
+}
+
+impl<'repo> AnnotatedCommit<'repo> {
+    /// Gets the commit ID that the given git_annotated_commit refers to
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_annotated_commit_id(self.raw)) }
+    }
+
+    /// Get the refname that the given git_annotated_commit refers to
+    ///
+    /// Returns None if it is not valid utf8
+    pub fn refname(&self) -> Option<&str> {
+        str::from_utf8(self.refname_bytes()).ok()
+    }
+
+    /// Get the refname that the given git_annotated_commit refers to.
+    pub fn refname_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_annotated_commit_ref(&*self.raw)).unwrap() }
+    }
+}
+
+impl Default for MergeOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl MergeOptions {
+    /// Creates a default set of merge options.
+    pub fn new() -> MergeOptions {
+        let mut opts = MergeOptions {
+            raw: unsafe { mem::zeroed() },
+        };
+        assert_eq!(unsafe { raw::git_merge_init_options(&mut opts.raw, 1) }, 0);
+        opts
+    }
+
+    fn flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
+        if val {
+            self.raw.flags |= opt;
+        } else {
+            self.raw.flags &= !opt;
+        }
+        self
+    }
+
+    /// Detect file renames
+    pub fn find_renames(&mut self, find: bool) -> &mut MergeOptions {
+        self.flag(raw::GIT_MERGE_FIND_RENAMES as u32, find)
+    }
+
+    /// If a conflict occurs, exit immediately instead of attempting to continue
+    /// resolving conflicts
+    pub fn fail_on_conflict(&mut self, fail: bool) -> &mut MergeOptions {
+        self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, fail)
+    }
+
+    /// Do not write the REUC extension on the generated index
+    pub fn skip_reuc(&mut self, skip: bool) -> &mut MergeOptions {
+        self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, skip)
+    }
+
+    /// If the commits being merged have multiple merge bases, do not build a
+    /// recursive merge base (by merging the multiple merge bases), instead
+    /// simply use the first base.
+    pub fn no_recursive(&mut self, disable: bool) -> &mut MergeOptions {
+        self.flag(raw::GIT_MERGE_NO_RECURSIVE as u32, disable)
+    }
+
+    /// Similarity to consider a file renamed (default 50)
+    pub fn rename_threshold(&mut self, thresh: u32) -> &mut MergeOptions {
+        self.raw.rename_threshold = thresh;
+        self
+    }
+
+    ///  Maximum similarity sources to examine for renames (default 200).
+    /// If the number of rename candidates (add / delete pairs) is greater
+    /// than this value, inexact rename detection is aborted. This setting
+    /// overrides the `merge.renameLimit` configuration value.
+    pub fn target_limit(&mut self, limit: u32) -> &mut MergeOptions {
+        self.raw.target_limit = limit as c_uint;
+        self
+    }
+
+    /// Maximum number of times to merge common ancestors to build a
+    /// virtual merge base when faced with criss-cross merges.  When
+    /// this limit is reached, the next ancestor will simply be used
+    /// instead of attempting to merge it.  The default is unlimited.
+    pub fn recursion_limit(&mut self, limit: u32) -> &mut MergeOptions {
+        self.raw.recursion_limit = limit as c_uint;
+        self
+    }
+
+    /// Specify a side to favor for resolving conflicts
+    pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeOptions {
+        self.raw.file_favor = favor.convert();
+        self
+    }
+
+    fn file_flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions {
+        if val {
+            self.raw.file_flags |= opt;
+        } else {
+            self.raw.file_flags &= !opt;
+        }
+        self
+    }
+
+    /// Create standard conflicted merge files
+    pub fn standard_style(&mut self, standard: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard)
+    }
+
+    /// Create diff3-style file
+    pub fn diff3_style(&mut self, diff3: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3)
+    }
+
+    /// Condense non-alphanumeric regions for simplified diff file
+    pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify)
+    }
+
+    /// Ignore all whitespace
+    pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore)
+    }
+
+    /// Ignore changes in amount of whitespace
+    pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore)
+    }
+
+    /// Ignore whitespace at end of line
+    pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore)
+    }
+
+    /// Use the "patience diff" algorithm
+    pub fn patience(&mut self, patience: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience)
+    }
+
+    /// Take extra time to find minimal diff
+    pub fn minimal(&mut self, minimal: bool) -> &mut MergeOptions {
+        self.file_flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal)
+    }
+
+    /// Acquire a pointer to the underlying raw options.
+    pub unsafe fn raw(&self) -> *const raw::git_merge_options {
+        &self.raw as *const _
+    }
+}
+
+impl<'repo> Binding for AnnotatedCommit<'repo> {
+    type Raw = *mut raw::git_annotated_commit;
+    unsafe fn from_raw(raw: *mut raw::git_annotated_commit) -> AnnotatedCommit<'repo> {
+        AnnotatedCommit {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_annotated_commit {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for AnnotatedCommit<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_annotated_commit_free(self.raw) }
+    }
+}
diff --git a/git2/src/message.rs b/git2/src/message.rs
new file mode 100644 (file)
index 0000000..a7041da
--- /dev/null
@@ -0,0 +1,349 @@
+use core::ops::Range;
+use std::ffi::CStr;
+use std::ffi::CString;
+use std::iter::FusedIterator;
+use std::ptr;
+
+use libc::{c_char, c_int};
+
+use crate::util::Binding;
+use crate::{raw, Buf, Error, IntoCString};
+
+/// Clean up a message, removing extraneous whitespace, and ensure that the
+/// message ends with a newline. If `comment_char` is `Some`, also remove comment
+/// lines starting with that character.
+pub fn message_prettify<T: IntoCString>(
+    message: T,
+    comment_char: Option<u8>,
+) -> Result<String, Error> {
+    _message_prettify(message.into_c_string()?, comment_char)
+}
+
+fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error> {
+    let ret = Buf::new();
+    unsafe {
+        try_call!(raw::git_message_prettify(
+            ret.raw(),
+            message,
+            comment_char.is_some() as c_int,
+            comment_char.unwrap_or(0) as c_char
+        ));
+    }
+    Ok(ret.as_str().unwrap().to_string())
+}
+
+/// The default comment character for `message_prettify` ('#')
+pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#');
+
+/// Get the trailers for the given message.
+///
+/// Use this function when you are dealing with a UTF-8-encoded message.
+pub fn message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error> {
+    _message_trailers(message.into_c_string()?).map(|res| MessageTrailersStrs(res))
+}
+
+/// Get the trailers for the given message.
+///
+/// Use this function when the message might not be UTF-8-encoded,
+/// or if you want to handle the returned trailer key–value pairs
+/// as bytes.
+pub fn message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error> {
+    _message_trailers(message.into_c_string()?).map(|res| MessageTrailersBytes(res))
+}
+
+fn _message_trailers(message: CString) -> Result<MessageTrailers, Error> {
+    let ret = MessageTrailers::new();
+    unsafe {
+        try_call!(raw::git_message_trailers(ret.raw(), message));
+    }
+    Ok(ret)
+}
+
+/// Collection of UTF-8-encoded trailers.
+///
+/// Use `iter()` to get access to the values.
+pub struct MessageTrailersStrs(MessageTrailers);
+
+impl MessageTrailersStrs {
+    /// Create a borrowed iterator.
+    pub fn iter(&self) -> MessageTrailersStrsIterator<'_> {
+        MessageTrailersStrsIterator(self.0.iter())
+    }
+    /// The number of trailer key–value pairs.
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+    /// Convert to the “bytes” variant.
+    pub fn to_bytes(self) -> MessageTrailersBytes {
+        MessageTrailersBytes(self.0)
+    }
+}
+
+/// Collection of unencoded (bytes) trailers.
+///
+/// Use `iter()` to get access to the values.
+pub struct MessageTrailersBytes(MessageTrailers);
+
+impl MessageTrailersBytes {
+    /// Create a borrowed iterator.
+    pub fn iter(&self) -> MessageTrailersBytesIterator<'_> {
+        MessageTrailersBytesIterator(self.0.iter())
+    }
+    /// The number of trailer key–value pairs.
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+}
+
+struct MessageTrailers {
+    raw: raw::git_message_trailer_array,
+}
+
+impl MessageTrailers {
+    fn new() -> MessageTrailers {
+        crate::init();
+        unsafe {
+            Binding::from_raw(&mut raw::git_message_trailer_array {
+                trailers: ptr::null_mut(),
+                count: 0,
+                _trailer_block: ptr::null_mut(),
+            } as *mut _)
+        }
+    }
+    fn iter(&self) -> MessageTrailersIterator<'_> {
+        MessageTrailersIterator {
+            trailers: self,
+            range: Range {
+                start: 0,
+                end: self.raw.count,
+            },
+        }
+    }
+    fn len(&self) -> usize {
+        self.raw.count
+    }
+}
+
+impl Drop for MessageTrailers {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_message_trailer_array_free(&mut self.raw);
+        }
+    }
+}
+
+impl Binding for MessageTrailers {
+    type Raw = *mut raw::git_message_trailer_array;
+    unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers {
+        MessageTrailers { raw: *raw }
+    }
+    fn raw(&self) -> *mut raw::git_message_trailer_array {
+        &self.raw as *const _ as *mut _
+    }
+}
+
+struct MessageTrailersIterator<'a> {
+    trailers: &'a MessageTrailers,
+    range: Range<usize>,
+}
+
+fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) {
+    unsafe {
+        let addr = trailers.raw.trailers.wrapping_add(index);
+        ((*addr).key, (*addr).value)
+    }
+}
+
+/// Borrowed iterator over the UTF-8-encoded trailers.
+pub struct MessageTrailersStrsIterator<'a>(MessageTrailersIterator<'a>);
+
+impl<'pair> Iterator for MessageTrailersStrsIterator<'pair> {
+    type Item = (&'pair str, &'pair str);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0
+            .range
+            .next()
+            .map(|index| to_str_tuple(&self.0.trailers, index))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.0.range.size_hint()
+    }
+}
+
+impl FusedIterator for MessageTrailersStrsIterator<'_> {}
+
+impl ExactSizeIterator for MessageTrailersStrsIterator<'_> {
+    fn len(&self) -> usize {
+        self.0.range.len()
+    }
+}
+
+impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.0
+            .range
+            .next_back()
+            .map(|index| to_str_tuple(&self.0.trailers, index))
+    }
+}
+
+fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) {
+    unsafe {
+        let (rkey, rvalue) = to_raw_tuple(&trailers, index);
+        let key = CStr::from_ptr(rkey).to_str().unwrap();
+        let value = CStr::from_ptr(rvalue).to_str().unwrap();
+        (key, value)
+    }
+}
+
+/// Borrowed iterator over the raw (bytes) trailers.
+pub struct MessageTrailersBytesIterator<'a>(MessageTrailersIterator<'a>);
+
+impl<'pair> Iterator for MessageTrailersBytesIterator<'pair> {
+    type Item = (&'pair [u8], &'pair [u8]);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0
+            .range
+            .next()
+            .map(|index| to_bytes_tuple(&self.0.trailers, index))
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.0.range.size_hint()
+    }
+}
+
+impl FusedIterator for MessageTrailersBytesIterator<'_> {}
+
+impl ExactSizeIterator for MessageTrailersBytesIterator<'_> {
+    fn len(&self) -> usize {
+        self.0.range.len()
+    }
+}
+
+impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> {
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.0
+            .range
+            .next_back()
+            .map(|index| to_bytes_tuple(&self.0.trailers, index))
+    }
+}
+
+fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) {
+    unsafe {
+        let (rkey, rvalue) = to_raw_tuple(&trailers, index);
+        let key = CStr::from_ptr(rkey).to_bytes();
+        let value = CStr::from_ptr(rvalue).to_bytes();
+        (key, value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn prettify() {
+        use crate::{message_prettify, DEFAULT_COMMENT_CHAR};
+
+        // This does not attempt to duplicate the extensive tests for
+        // git_message_prettify in libgit2, just a few representative values to
+        // make sure the interface works as expected.
+        assert_eq!(message_prettify("1\n\n\n2", None).unwrap(), "1\n\n2\n");
+        assert_eq!(
+            message_prettify("1\n\n\n2\n\n\n3", None).unwrap(),
+            "1\n\n2\n\n3\n"
+        );
+        assert_eq!(
+            message_prettify("1\n# comment\n# more", None).unwrap(),
+            "1\n# comment\n# more\n"
+        );
+        assert_eq!(
+            message_prettify("1\n# comment\n# more", DEFAULT_COMMENT_CHAR).unwrap(),
+            "1\n"
+        );
+        assert_eq!(
+            message_prettify("1\n; comment\n; more", Some(';' as u8)).unwrap(),
+            "1\n"
+        );
+    }
+
+    #[test]
+    fn trailers() {
+        use crate::{message_trailers_bytes, message_trailers_strs, MessageTrailersStrs};
+        use std::collections::HashMap;
+
+        // no trailers
+        let message1 = "
+WHAT ARE WE HERE FOR
+
+What are we here for?
+
+Just to be eaten?
+";
+        let expected: HashMap<&str, &str> = HashMap::new();
+        assert_eq!(expected, to_map(&message_trailers_strs(message1).unwrap()));
+
+        // standard PSA
+        let message2 = "
+Attention all
+
+We are out of tomatoes.
+
+Spoken-by: Major Turnips
+Transcribed-by: Seargant Persimmons
+Signed-off-by: Colonel Kale
+";
+        let expected: HashMap<&str, &str> = vec![
+            ("Spoken-by", "Major Turnips"),
+            ("Transcribed-by", "Seargant Persimmons"),
+            ("Signed-off-by", "Colonel Kale"),
+        ]
+        .into_iter()
+        .collect();
+        assert_eq!(expected, to_map(&message_trailers_strs(message2).unwrap()));
+
+        // ignore everything after `---`
+        let message3 = "
+The fate of Seargant Green-Peppers
+
+Seargant Green-Peppers was killed by Caterpillar Battalion 44.
+
+Signed-off-by: Colonel Kale
+---
+I never liked that guy, anyway.
+
+Opined-by: Corporal Garlic
+";
+        let expected: HashMap<&str, &str> = vec![("Signed-off-by", "Colonel Kale")]
+            .into_iter()
+            .collect();
+        assert_eq!(expected, to_map(&message_trailers_strs(message3).unwrap()));
+
+        // Raw bytes message; not valid UTF-8
+        // Source: https://stackoverflow.com/a/3886015/1725151
+        let message4 = b"
+Be honest guys
+
+Am I a malformed brussels sprout?
+
+Signed-off-by: Lieutenant \xe2\x28\xa1prout
+";
+
+        let trailer = message_trailers_bytes(&message4[..]).unwrap();
+        let expected = (&b"Signed-off-by"[..], &b"Lieutenant \xe2\x28\xa1prout"[..]);
+        let actual = trailer.iter().next().unwrap();
+        assert_eq!(expected, actual);
+
+        fn to_map(trailers: &MessageTrailersStrs) -> HashMap<&str, &str> {
+            let mut map = HashMap::with_capacity(trailers.len());
+            for (key, value) in trailers.iter() {
+                map.insert(key, value);
+            }
+            map
+        }
+    }
+}
diff --git a/git2/src/note.rs b/git2/src/note.rs
new file mode 100644 (file)
index 0000000..50e5800
--- /dev/null
@@ -0,0 +1,147 @@
+use std::marker;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, signature, Error, Oid, Repository, Signature};
+
+/// A structure representing a [note][note] in git.
+///
+/// [note]: http://alblue.bandlem.com/2011/11/git-tip-of-week-git-notes.html
+pub struct Note<'repo> {
+    raw: *mut raw::git_note,
+
+    // Hmm, the current libgit2 version does not have this inside of it, but
+    // perhaps it's a good idea to keep it around? Can always remove it later I
+    // suppose...
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// An iterator over all of the notes within a repository.
+pub struct Notes<'repo> {
+    raw: *mut raw::git_note_iterator,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl<'repo> Note<'repo> {
+    /// Get the note author
+    pub fn author(&self) -> Signature<'_> {
+        unsafe { signature::from_raw_const(self, raw::git_note_author(&*self.raw)) }
+    }
+
+    /// Get the note committer
+    pub fn committer(&self) -> Signature<'_> {
+        unsafe { signature::from_raw_const(self, raw::git_note_committer(&*self.raw)) }
+    }
+
+    /// Get the note message, in bytes.
+    pub fn message_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_note_message(&*self.raw)).unwrap() }
+    }
+
+    /// Get the note message as a string, returning `None` if it is not UTF-8.
+    pub fn message(&self) -> Option<&str> {
+        str::from_utf8(self.message_bytes()).ok()
+    }
+
+    /// Get the note object's id
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_note_id(&*self.raw)) }
+    }
+}
+
+impl<'repo> Binding for Note<'repo> {
+    type Raw = *mut raw::git_note;
+    unsafe fn from_raw(raw: *mut raw::git_note) -> Note<'repo> {
+        Note {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_note {
+        self.raw
+    }
+}
+
+impl<'repo> std::fmt::Debug for Note<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("Note").field("id", &self.id()).finish()
+    }
+}
+
+impl<'repo> Drop for Note<'repo> {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_note_free(self.raw);
+        }
+    }
+}
+
+impl<'repo> Binding for Notes<'repo> {
+    type Raw = *mut raw::git_note_iterator;
+    unsafe fn from_raw(raw: *mut raw::git_note_iterator) -> Notes<'repo> {
+        Notes {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_note_iterator {
+        self.raw
+    }
+}
+
+impl<'repo> Iterator for Notes<'repo> {
+    type Item = Result<(Oid, Oid), Error>;
+    fn next(&mut self) -> Option<Result<(Oid, Oid), Error>> {
+        let mut note_id = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        let mut annotated_id = note_id;
+        unsafe {
+            try_call_iter!(raw::git_note_next(
+                &mut note_id,
+                &mut annotated_id,
+                self.raw
+            ));
+            Some(Ok((
+                Binding::from_raw(&note_id as *const _),
+                Binding::from_raw(&annotated_id as *const _),
+            )))
+        }
+    }
+}
+
+impl<'repo> Drop for Notes<'repo> {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_note_iterator_free(self.raw);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        assert!(repo.notes(None).is_err());
+
+        let sig = repo.signature().unwrap();
+        let head = repo.head().unwrap().target().unwrap();
+        let note = repo.note(&sig, &sig, None, head, "foo", false).unwrap();
+        assert_eq!(repo.notes(None).unwrap().count(), 1);
+
+        let note_obj = repo.find_note(None, head).unwrap();
+        assert_eq!(note_obj.id(), note);
+        assert_eq!(note_obj.message(), Some("foo"));
+
+        let (a, b) = repo.notes(None).unwrap().next().unwrap().unwrap();
+        assert_eq!(a, note);
+        assert_eq!(b, head);
+
+        assert_eq!(repo.note_default_ref().unwrap(), "refs/notes/commits");
+
+        assert_eq!(sig.name(), note_obj.author().name());
+        assert_eq!(sig.name(), note_obj.committer().name());
+        assert!(sig.when() == note_obj.committer().when());
+    }
+}
diff --git a/git2/src/object.rs b/git2/src/object.rs
new file mode 100644 (file)
index 0000000..fcae006
--- /dev/null
@@ -0,0 +1,248 @@
+use std::marker;
+use std::mem;
+use std::ptr;
+
+use crate::util::Binding;
+use crate::{raw, Blob, Buf, Commit, Error, ObjectType, Oid, Repository, Tag, Tree};
+use crate::{Describe, DescribeOptions};
+
+/// A structure to represent a git [object][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Object<'repo> {
+    raw: *mut raw::git_object,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl<'repo> Object<'repo> {
+    /// Get the id (SHA1) of a repository object
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_object_id(&*self.raw)) }
+    }
+
+    /// Get the object type of an object.
+    ///
+    /// If the type is unknown, then `None` is returned.
+    pub fn kind(&self) -> Option<ObjectType> {
+        ObjectType::from_raw(unsafe { raw::git_object_type(&*self.raw) })
+    }
+
+    /// Recursively peel an object until an object of the specified type is met.
+    ///
+    /// If you pass `Any` as the target type, then the object will be
+    /// peeled until the type changes (e.g. a tag will be chased until the
+    /// referenced object is no longer a tag).
+    pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_object_peel(&mut raw, &*self.raw(), kind));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Recursively peel an object until a blob is found
+    pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
+        self.peel(ObjectType::Blob)
+            .map(|o| o.cast_or_panic(ObjectType::Blob))
+    }
+
+    /// Recursively peel an object until a commit is found
+    pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
+        self.peel(ObjectType::Commit)
+            .map(|o| o.cast_or_panic(ObjectType::Commit))
+    }
+
+    /// Recursively peel an object until a tag is found
+    pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
+        self.peel(ObjectType::Tag)
+            .map(|o| o.cast_or_panic(ObjectType::Tag))
+    }
+
+    /// Recursively peel an object until a tree is found
+    pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
+        self.peel(ObjectType::Tree)
+            .map(|o| o.cast_or_panic(ObjectType::Tree))
+    }
+
+    /// Get a short abbreviated OID string for the object
+    ///
+    /// This starts at the "core.abbrev" length (default 7 characters) and
+    /// iteratively extends to a longer string if that length is ambiguous. The
+    /// result will be unambiguous (at least until new objects are added to the
+    /// repository).
+    pub fn short_id(&self) -> Result<Buf, Error> {
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_object_short_id(buf.raw(), &*self.raw()));
+            Ok(buf)
+        }
+    }
+
+    /// Attempt to view this object as a commit.
+    ///
+    /// Returns `None` if the object is not actually a commit.
+    pub fn as_commit(&self) -> Option<&Commit<'repo>> {
+        self.cast(ObjectType::Commit)
+    }
+
+    /// Attempt to consume this object and return a commit.
+    ///
+    /// Returns `Err(self)` if this object is not actually a commit.
+    pub fn into_commit(self) -> Result<Commit<'repo>, Object<'repo>> {
+        self.cast_into(ObjectType::Commit)
+    }
+
+    /// Attempt to view this object as a tag.
+    ///
+    /// Returns `None` if the object is not actually a tag.
+    pub fn as_tag(&self) -> Option<&Tag<'repo>> {
+        self.cast(ObjectType::Tag)
+    }
+
+    /// Attempt to consume this object and return a tag.
+    ///
+    /// Returns `Err(self)` if this object is not actually a tag.
+    pub fn into_tag(self) -> Result<Tag<'repo>, Object<'repo>> {
+        self.cast_into(ObjectType::Tag)
+    }
+
+    /// Attempt to view this object as a tree.
+    ///
+    /// Returns `None` if the object is not actually a tree.
+    pub fn as_tree(&self) -> Option<&Tree<'repo>> {
+        self.cast(ObjectType::Tree)
+    }
+
+    /// Attempt to consume this object and return a tree.
+    ///
+    /// Returns `Err(self)` if this object is not actually a tree.
+    pub fn into_tree(self) -> Result<Tree<'repo>, Object<'repo>> {
+        self.cast_into(ObjectType::Tree)
+    }
+
+    /// Attempt to view this object as a blob.
+    ///
+    /// Returns `None` if the object is not actually a blob.
+    pub fn as_blob(&self) -> Option<&Blob<'repo>> {
+        self.cast(ObjectType::Blob)
+    }
+
+    /// Attempt to consume this object and return a blob.
+    ///
+    /// Returns `Err(self)` if this object is not actually a blob.
+    pub fn into_blob(self) -> Result<Blob<'repo>, Object<'repo>> {
+        self.cast_into(ObjectType::Blob)
+    }
+
+    /// Describes a commit
+    ///
+    /// Performs a describe operation on this commitish object.
+    pub fn describe(&self, opts: &DescribeOptions) -> Result<Describe<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_describe_commit(&mut ret, self.raw, opts.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    fn cast<T>(&self, kind: ObjectType) -> Option<&T> {
+        assert_eq!(mem::size_of::<Object<'_>>(), mem::size_of::<T>());
+        if self.kind() == Some(kind) {
+            unsafe { Some(&*(self as *const _ as *const T)) }
+        } else {
+            None
+        }
+    }
+
+    fn cast_into<T>(self, kind: ObjectType) -> Result<T, Object<'repo>> {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<T>());
+        if self.kind() == Some(kind) {
+            Ok(unsafe {
+                let other = ptr::read(&self as *const _ as *const T);
+                mem::forget(self);
+                other
+            })
+        } else {
+            Err(self)
+        }
+    }
+}
+
+/// This trait is useful to export cast_or_panic into crate but not outside
+pub trait CastOrPanic {
+    fn cast_or_panic<T>(self, kind: ObjectType) -> T;
+}
+
+impl<'repo> CastOrPanic for Object<'repo> {
+    fn cast_or_panic<T>(self, kind: ObjectType) -> T {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<T>());
+        if self.kind() == Some(kind) {
+            unsafe {
+                let other = ptr::read(&self as *const _ as *const T);
+                mem::forget(self);
+                other
+            }
+        } else {
+            let buf;
+            let akind = match self.kind() {
+                Some(akind) => akind.str(),
+                None => {
+                    buf = format!("unknown ({})", unsafe { raw::git_object_type(&*self.raw) });
+                    &buf
+                }
+            };
+            panic!(
+                "Expected object {} to be {} but it is {}",
+                self.id(),
+                kind.str(),
+                akind
+            )
+        }
+    }
+}
+
+impl<'repo> Clone for Object<'repo> {
+    fn clone(&self) -> Object<'repo> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            let rc = raw::git_object_dup(&mut raw, self.raw);
+            assert_eq!(rc, 0);
+            Binding::from_raw(raw)
+        }
+    }
+}
+
+impl<'repo> std::fmt::Debug for Object<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("Object");
+        match self.kind() {
+            Some(kind) => ds.field("kind", &kind),
+            None => ds.field(
+                "kind",
+                &format!("Unknow ({})", unsafe { raw::git_object_type(&*self.raw) }),
+            ),
+        };
+        ds.field("id", &self.id());
+        ds.finish()
+    }
+}
+
+impl<'repo> Binding for Object<'repo> {
+    type Raw = *mut raw::git_object;
+
+    unsafe fn from_raw(raw: *mut raw::git_object) -> Object<'repo> {
+        Object {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_object {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Object<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_object_free(self.raw) }
+    }
+}
diff --git a/git2/src/odb.rs b/git2/src/odb.rs
new file mode 100644 (file)
index 0000000..2019908
--- /dev/null
@@ -0,0 +1,770 @@
+use std::io;
+use std::marker;
+use std::ptr;
+use std::slice;
+
+use std::ffi::CString;
+
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+
+use crate::panic;
+use crate::util::Binding;
+use crate::{
+    raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress,
+};
+
+/// A structure to represent a git object database
+pub struct Odb<'repo> {
+    raw: *mut raw::git_odb,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+// `git_odb` uses locking and atomics internally.
+unsafe impl<'repo> Send for Odb<'repo> {}
+unsafe impl<'repo> Sync for Odb<'repo> {}
+
+impl<'repo> Binding for Odb<'repo> {
+    type Raw = *mut raw::git_odb;
+
+    unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> {
+        Odb {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_odb {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Odb<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_odb_free(self.raw) }
+    }
+}
+
+impl<'repo> Odb<'repo> {
+    /// Creates an object database without any backends.
+    pub fn new<'a>() -> Result<Odb<'a>, Error> {
+        crate::init();
+        unsafe {
+            let mut out = ptr::null_mut();
+            try_call!(raw::git_odb_new(&mut out));
+            Ok(Odb::from_raw(out))
+        }
+    }
+
+    /// Create object database reading stream.
+    ///
+    /// Note that most backends do not support streaming reads because they store their objects as compressed/delta'ed blobs.
+    /// If the backend does not support streaming reads, use the `read` method instead.
+    pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> {
+        let mut out = ptr::null_mut();
+        let mut size = 0usize;
+        let mut otype: raw::git_object_t = ObjectType::Any.raw();
+        unsafe {
+            try_call!(raw::git_odb_open_rstream(
+                &mut out,
+                &mut size,
+                &mut otype,
+                self.raw,
+                oid.raw()
+            ));
+            Ok((
+                OdbReader::from_raw(out),
+                size,
+                ObjectType::from_raw(otype).unwrap(),
+            ))
+        }
+    }
+
+    /// Create object database writing stream.
+    ///
+    /// The type and final length of the object must be specified when opening the stream.
+    /// If the backend does not support streaming writes, use the `write` method instead.
+    pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> {
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_odb_open_wstream(
+                &mut out,
+                self.raw,
+                size as raw::git_object_size_t,
+                obj_type.raw()
+            ));
+            Ok(OdbWriter::from_raw(out))
+        }
+    }
+
+    /// Iterate over all objects in the object database.s
+    pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error>
+    where
+        C: FnMut(&Oid) -> bool,
+    {
+        unsafe {
+            let mut data = ForeachCbData {
+                callback: &mut callback,
+            };
+            let cb: raw::git_odb_foreach_cb = Some(foreach_cb);
+            try_call!(raw::git_odb_foreach(
+                self.raw(),
+                cb,
+                &mut data as *mut _ as *mut _
+            ));
+            Ok(())
+        }
+    }
+
+    /// Read an object from the database.
+    pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> {
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw()));
+            Ok(OdbObject::from_raw(out))
+        }
+    }
+
+    /// Reads the header of an object from the database
+    /// without reading the full content.
+    pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> {
+        let mut size: usize = 0;
+        let mut kind_id: i32 = ObjectType::Any.raw();
+
+        unsafe {
+            try_call!(raw::git_odb_read_header(
+                &mut size as *mut size_t,
+                &mut kind_id as *mut raw::git_object_t,
+                self.raw,
+                oid.raw()
+            ));
+
+            Ok((size, ObjectType::from_raw(kind_id).unwrap()))
+        }
+    }
+
+    /// Write an object to the database.
+    pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> {
+        unsafe {
+            let mut out = raw::git_oid {
+                id: [0; raw::GIT_OID_RAWSZ],
+            };
+            try_call!(raw::git_odb_write(
+                &mut out,
+                self.raw,
+                data.as_ptr() as *const c_void,
+                data.len(),
+                kind.raw()
+            ));
+            Ok(Oid::from_raw(&mut out))
+        }
+    }
+
+    /// Create stream for writing a pack file to the ODB
+    pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> {
+        let mut out = ptr::null_mut();
+        let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
+        let progress_payload = Box::new(OdbPackwriterCb { cb: None });
+        let progress_payload_ptr = Box::into_raw(progress_payload);
+
+        unsafe {
+            try_call!(raw::git_odb_write_pack(
+                &mut out,
+                self.raw,
+                progress_cb,
+                progress_payload_ptr as *mut c_void
+            ));
+        }
+
+        Ok(OdbPackwriter {
+            raw: out,
+            progress: Default::default(),
+            progress_payload_ptr,
+        })
+    }
+
+    /// Checks if the object database has an object.
+    pub fn exists(&self, oid: Oid) -> bool {
+        unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 }
+    }
+
+    /// Checks if the object database has an object, with extended flags.
+    pub fn exists_ext(&self, oid: Oid, flags: OdbLookupFlags) -> bool {
+        unsafe { raw::git_odb_exists_ext(self.raw, oid.raw(), flags.bits() as c_uint) != 0 }
+    }
+
+    /// Potentially finds an object that starts with the given prefix.
+    pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> {
+        unsafe {
+            let mut out = raw::git_oid {
+                id: [0; raw::GIT_OID_RAWSZ],
+            };
+            try_call!(raw::git_odb_exists_prefix(
+                &mut out,
+                self.raw,
+                short_oid.raw(),
+                len
+            ));
+            Ok(Oid::from_raw(&out))
+        }
+    }
+
+    /// Refresh the object database.
+    /// This should never be needed, and is
+    /// provided purely for convenience.
+    /// The object database will automatically
+    /// refresh when an object is not found when
+    /// requested.
+    pub fn refresh(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_odb_refresh(self.raw));
+            Ok(())
+        }
+    }
+
+    /// Adds an alternate disk backend to the object database.
+    pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> {
+        unsafe {
+            let path = CString::new(path)?;
+            try_call!(raw::git_odb_add_disk_alternate(self.raw, path));
+            Ok(())
+        }
+    }
+
+    /// Create a new mempack backend, and add it to this odb with the given
+    /// priority. Higher values give the backend higher precedence. The default
+    /// loose and pack backends have priorities 1 and 2 respectively (hard-coded
+    /// in libgit2). A reference to the new mempack backend is returned on
+    /// success. The lifetime of the backend must be contained within the
+    /// lifetime of this odb, since deletion of the odb will also result in
+    /// deletion of the mempack backend.
+    ///
+    /// Here is an example that fails to compile because it tries to hold the
+    /// mempack reference beyond the Odb's lifetime:
+    ///
+    /// ```compile_fail
+    /// use git2::Odb;
+    /// let mempack = {
+    ///     let odb = Odb::new().unwrap();
+    ///     odb.add_new_mempack_backend(1000).unwrap()
+    /// };
+    /// ```
+    pub fn add_new_mempack_backend<'odb>(
+        &'odb self,
+        priority: i32,
+    ) -> Result<Mempack<'odb>, Error> {
+        unsafe {
+            let mut mempack = ptr::null_mut();
+            // The mempack backend object in libgit2 is only ever freed by an
+            // odb that has the backend in its list. So to avoid potentially
+            // leaking the mempack backend, this API ensures that the backend
+            // is added to the odb before returning it. The lifetime of the
+            // mempack is also bound to the lifetime of the odb, so that users
+            // can't end up with a dangling reference to a mempack object that
+            // was actually freed when the odb was destroyed.
+            try_call!(raw::git_mempack_new(&mut mempack));
+            try_call!(raw::git_odb_add_backend(
+                self.raw,
+                mempack,
+                priority as c_int
+            ));
+            Ok(Mempack::from_raw(mempack))
+        }
+    }
+}
+
+/// An object from the Object Database.
+pub struct OdbObject<'a> {
+    raw: *mut raw::git_odb_object,
+    _marker: marker::PhantomData<Object<'a>>,
+}
+
+impl<'a> Binding for OdbObject<'a> {
+    type Raw = *mut raw::git_odb_object;
+
+    unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> {
+        OdbObject {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *mut raw::git_odb_object {
+        self.raw
+    }
+}
+
+impl<'a> Drop for OdbObject<'a> {
+    fn drop(&mut self) {
+        unsafe { raw::git_odb_object_free(self.raw) }
+    }
+}
+
+impl<'a> OdbObject<'a> {
+    /// Get the object type.
+    pub fn kind(&self) -> ObjectType {
+        unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() }
+    }
+
+    /// Get the object size.
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_odb_object_size(self.raw) }
+    }
+
+    /// Get the object data.
+    pub fn data(&self) -> &[u8] {
+        unsafe {
+            let size = self.len();
+            let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8;
+            let buffer = slice::from_raw_parts(ptr, size);
+            return buffer;
+        }
+    }
+
+    /// Get the object id.
+    pub fn id(&self) -> Oid {
+        unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) }
+    }
+}
+
+/// A structure to represent a git ODB rstream
+pub struct OdbReader<'repo> {
+    raw: *mut raw::git_odb_stream,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another
+// thread and continuing to read will work.
+unsafe impl<'repo> Send for OdbReader<'repo> {}
+
+impl<'repo> Binding for OdbReader<'repo> {
+    type Raw = *mut raw::git_odb_stream;
+
+    unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> {
+        OdbReader {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_odb_stream {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for OdbReader<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_odb_stream_free(self.raw) }
+    }
+}
+
+impl<'repo> io::Read for OdbReader<'repo> {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        unsafe {
+            let ptr = buf.as_ptr() as *mut c_char;
+            let len = buf.len();
+            let res = raw::git_odb_stream_read(self.raw, ptr, len);
+            if res < 0 {
+                Err(io::Error::new(io::ErrorKind::Other, "Read error"))
+            } else {
+                Ok(res as _)
+            }
+        }
+    }
+}
+
+/// A structure to represent a git ODB wstream
+pub struct OdbWriter<'repo> {
+    raw: *mut raw::git_odb_stream,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another
+// thread and continuing to write will work.
+unsafe impl<'repo> Send for OdbWriter<'repo> {}
+
+impl<'repo> OdbWriter<'repo> {
+    /// Finish writing to an ODB stream
+    ///
+    /// This method can be used to finalize writing object to the database and get an identifier.
+    /// The object will take its final name and will be available to the odb.
+    /// This method will fail if the total number of received bytes differs from the size declared with odb_writer()
+    /// Attempting write after finishing will be ignored.
+    pub fn finalize(&mut self) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+}
+
+impl<'repo> Binding for OdbWriter<'repo> {
+    type Raw = *mut raw::git_odb_stream;
+
+    unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> {
+        OdbWriter {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_odb_stream {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for OdbWriter<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_odb_stream_free(self.raw) }
+    }
+}
+
+impl<'repo> io::Write for OdbWriter<'repo> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe {
+            let ptr = buf.as_ptr() as *const c_char;
+            let len = buf.len();
+            let res = raw::git_odb_stream_write(self.raw, ptr, len);
+            if res < 0 {
+                Err(io::Error::new(io::ErrorKind::Other, "Write error"))
+            } else {
+                Ok(buf.len())
+            }
+        }
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+pub(crate) struct OdbPackwriterCb<'repo> {
+    pub(crate) cb: Option<Box<IndexerProgress<'repo>>>,
+}
+
+/// A stream to write a packfile to the ODB
+pub struct OdbPackwriter<'repo> {
+    raw: *mut raw::git_odb_writepack,
+    progress: raw::git_indexer_progress,
+    progress_payload_ptr: *mut OdbPackwriterCb<'repo>,
+}
+
+impl<'repo> OdbPackwriter<'repo> {
+    /// Finish writing the packfile
+    pub fn commit(&mut self) -> Result<i32, Error> {
+        unsafe {
+            let writepack = &*self.raw;
+            let res = match writepack.commit {
+                Some(commit) => commit(self.raw, &mut self.progress),
+                None => -1,
+            };
+
+            if res < 0 {
+                Err(Error::last_error(res))
+            } else {
+                Ok(res)
+            }
+        }
+    }
+
+    /// The callback through which progress is monitored. Be aware that this is
+    /// called inline, so performance may be affected.
+    pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo>
+    where
+        F: FnMut(Progress<'_>) -> bool + 'repo,
+    {
+        let progress_payload =
+            unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) };
+
+        progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>);
+        self
+    }
+}
+
+impl<'repo> io::Write for OdbPackwriter<'repo> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe {
+            let ptr = buf.as_ptr() as *mut c_void;
+            let len = buf.len();
+
+            let writepack = &*self.raw;
+            let res = match writepack.append {
+                Some(append) => append(self.raw, ptr, len, &mut self.progress),
+                None => -1,
+            };
+
+            if res < 0 {
+                Err(io::Error::new(io::ErrorKind::Other, "Write error"))
+            } else {
+                Ok(buf.len())
+            }
+        }
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+impl<'repo> Drop for OdbPackwriter<'repo> {
+    fn drop(&mut self) {
+        unsafe {
+            let writepack = &*self.raw;
+            match writepack.free {
+                Some(free) => free(self.raw),
+                None => (),
+            };
+
+            drop(Box::from_raw(self.progress_payload_ptr));
+        }
+    }
+}
+
+pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
+
+struct ForeachCbData<'a> {
+    pub callback: &'a mut ForeachCb<'a>,
+}
+
+extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int {
+    panic::wrap(|| unsafe {
+        let data = &mut *(payload as *mut ForeachCbData<'_>);
+        let res = {
+            let callback = &mut data.callback;
+            callback(&Binding::from_raw(id))
+        };
+
+        if res {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(1)
+}
+
+pub(crate) extern "C" fn write_pack_progress_cb(
+    stats: *const raw::git_indexer_progress,
+    payload: *mut c_void,
+) -> c_int {
+    let ok = panic::wrap(|| unsafe {
+        let payload = &mut *(payload as *mut OdbPackwriterCb<'_>);
+
+        let callback = match payload.cb {
+            Some(ref mut cb) => cb,
+            None => return true,
+        };
+
+        let progress: Progress<'_> = Binding::from_raw(stats);
+        callback(progress)
+    });
+    if ok == Some(true) {
+        0
+    } else {
+        -1
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Buf, ObjectType, Oid, Repository};
+    use std::io::prelude::*;
+    use tempfile::TempDir;
+
+    #[test]
+    fn read() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let id = repo.blob(&dat).unwrap();
+        let db = repo.odb().unwrap();
+        let obj = db.read(id).unwrap();
+        let data = obj.data();
+        let size = obj.len();
+        assert_eq!(size, 5);
+        assert_eq!(dat, data);
+        assert_eq!(id, obj.id());
+    }
+
+    #[test]
+    fn read_header() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let id = repo.blob(&dat).unwrap();
+        let db = repo.odb().unwrap();
+        let (size, kind) = db.read_header(id).unwrap();
+
+        assert_eq!(size, 5);
+        assert_eq!(kind, ObjectType::Blob);
+    }
+
+    #[test]
+    fn write() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let db = repo.odb().unwrap();
+        let id = db.write(ObjectType::Blob, &dat).unwrap();
+        let blob = repo.find_blob(id).unwrap();
+        assert_eq!(blob.content(), dat);
+    }
+
+    #[test]
+    fn writer() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let db = repo.odb().unwrap();
+        let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap();
+        let wl = ws.write(&dat[0..3]).unwrap();
+        assert_eq!(wl, 3);
+        let wl = ws.write(&dat[3..5]).unwrap();
+        assert_eq!(wl, 2);
+        let id = ws.finalize().unwrap();
+        let blob = repo.find_blob(id).unwrap();
+        assert_eq!(blob.content(), dat);
+    }
+
+    #[test]
+    fn exists() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let db = repo.odb().unwrap();
+        let id = db.write(ObjectType::Blob, &dat).unwrap();
+        assert!(db.exists(id));
+    }
+
+    #[test]
+    fn exists_prefix() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let dat = [4, 3, 5, 6, 9];
+        let db = repo.odb().unwrap();
+        let id = db.write(ObjectType::Blob, &dat).unwrap();
+        let id_prefix_str = &id.to_string()[0..10];
+        let id_prefix = Oid::from_str(id_prefix_str).unwrap();
+        let found_oid = db.exists_prefix(id_prefix, 10).unwrap();
+        assert_eq!(found_oid, id);
+    }
+
+    #[test]
+    fn packwriter() {
+        let (_td, repo_source) = crate::test::repo_init();
+        let (_td, repo_target) = crate::test::repo_init();
+        let mut builder = t!(repo_source.packbuilder());
+        let mut buf = Buf::new();
+        let (commit_source_id, _tree) = crate::test::commit(&repo_source);
+        t!(builder.insert_object(commit_source_id, None));
+        t!(builder.write_buf(&mut buf));
+        let db = repo_target.odb().unwrap();
+        let mut packwriter = db.packwriter().unwrap();
+        packwriter.write(&buf).unwrap();
+        packwriter.commit().unwrap();
+        let commit_target = repo_target.find_commit(commit_source_id).unwrap();
+        assert_eq!(commit_target.id(), commit_source_id);
+    }
+
+    #[test]
+    fn packwriter_progress() {
+        let mut progress_called = false;
+        {
+            let (_td, repo_source) = crate::test::repo_init();
+            let (_td, repo_target) = crate::test::repo_init();
+            let mut builder = t!(repo_source.packbuilder());
+            let mut buf = Buf::new();
+            let (commit_source_id, _tree) = crate::test::commit(&repo_source);
+            t!(builder.insert_object(commit_source_id, None));
+            t!(builder.write_buf(&mut buf));
+            let db = repo_target.odb().unwrap();
+            let mut packwriter = db.packwriter().unwrap();
+            packwriter.progress(|_| {
+                progress_called = true;
+                true
+            });
+            packwriter.write(&buf).unwrap();
+            packwriter.commit().unwrap();
+        }
+        assert_eq!(progress_called, true);
+    }
+
+    #[test]
+    fn write_with_mempack() {
+        use crate::{Buf, ResetType};
+        use std::io::Write;
+        use std::path::Path;
+
+        // Create a repo, add a mempack backend
+        let (_td, repo) = crate::test::repo_init();
+        let odb = repo.odb().unwrap();
+        let mempack = odb.add_new_mempack_backend(1000).unwrap();
+
+        // Sanity check that foo doesn't exist initially
+        let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
+        assert!(!foo_file.exists());
+
+        // Make a commit that adds foo. This writes new stuff into the mempack
+        // backend.
+        let (oid1, _id) = crate::test::commit(&repo);
+        let commit1 = repo.find_commit(oid1).unwrap();
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+        assert!(foo_file.exists());
+
+        // Dump the mempack modifications into a buf, and reset it. This "erases"
+        // commit-related objects from the repository. Ensure the commit appears
+        // to have become invalid, by checking for failure in `reset --hard`.
+        let mut buf = Buf::new();
+        mempack.dump(&repo, &mut buf).unwrap();
+        mempack.reset().unwrap();
+        assert!(repo
+            .reset(commit1.as_object(), ResetType::Hard, None)
+            .is_err());
+
+        // Write the buf into a packfile in the repo. This brings back the
+        // missing objects, and we verify everything is good again.
+        let mut packwriter = odb.packwriter().unwrap();
+        packwriter.write(&buf).unwrap();
+        packwriter.commit().unwrap();
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+        assert!(foo_file.exists());
+    }
+
+    #[test]
+    fn stream_read() {
+        // Test for read impl of OdbReader.
+        const FOO_TEXT: &[u8] = b"this is a test";
+        let (_td, repo) = crate::test::repo_init();
+        let p = repo.path().parent().unwrap().join("foo");
+        std::fs::write(&p, FOO_TEXT).unwrap();
+        let mut index = repo.index().unwrap();
+        index.add_path(std::path::Path::new("foo")).unwrap();
+        let tree_id = index.write_tree().unwrap();
+        let tree = repo.find_tree(tree_id).unwrap();
+        let sig = repo.signature().unwrap();
+        let head_id = repo.refname_to_id("HEAD").unwrap();
+        let parent = repo.find_commit(head_id).unwrap();
+        let _commit = repo
+            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
+            .unwrap();
+
+        // Try reading from a commit object.
+        let odb = repo.odb().unwrap();
+        let oid = repo.refname_to_id("HEAD").unwrap();
+        let (mut reader, size, ty) = odb.reader(oid).unwrap();
+        assert!(ty == ObjectType::Commit);
+        let mut x = [0; 10000];
+        let r = reader.read(&mut x).unwrap();
+        assert!(r == size);
+
+        // Try reading from a blob. This assumes it is a loose object (packed
+        // objects can't read).
+        let commit = repo.find_commit(oid).unwrap();
+        let tree = commit.tree().unwrap();
+        let entry = tree.get_name("foo").unwrap();
+        let (mut reader, size, ty) = odb.reader(entry.id()).unwrap();
+        assert_eq!(size, FOO_TEXT.len());
+        assert!(ty == ObjectType::Blob);
+        let mut x = [0; 10000];
+        let r = reader.read(&mut x).unwrap();
+        assert_eq!(r, 14);
+        assert_eq!(&x[..FOO_TEXT.len()], FOO_TEXT);
+    }
+}
diff --git a/git2/src/oid.rs b/git2/src/oid.rs
new file mode 100644 (file)
index 0000000..35516cb
--- /dev/null
@@ -0,0 +1,258 @@
+use std::cmp::Ordering;
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::path::Path;
+use std::str;
+
+use crate::{raw, Error, IntoCString, ObjectType};
+
+use crate::util::{c_cmp_to_ordering, Binding};
+
+/// Unique identity of any object (commit, tree, blob, tag).
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub struct Oid {
+    raw: raw::git_oid,
+}
+
+impl Oid {
+    /// Parse a hex-formatted object id into an Oid structure.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the string is empty, is longer than 40 hex
+    /// characters, or contains any non-hex characters.
+    pub fn from_str(s: &str) -> Result<Oid, Error> {
+        crate::init();
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_oid_fromstrn(
+                &mut raw,
+                s.as_bytes().as_ptr() as *const libc::c_char,
+                s.len() as libc::size_t
+            ));
+        }
+        Ok(Oid { raw })
+    }
+
+    /// Parse a raw object id into an Oid structure.
+    ///
+    /// If the array given is not 20 bytes in length, an error is returned.
+    pub fn from_bytes(bytes: &[u8]) -> Result<Oid, Error> {
+        crate::init();
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        if bytes.len() != raw::GIT_OID_RAWSZ {
+            Err(Error::from_str("raw byte array must be 20 bytes"))
+        } else {
+            unsafe {
+                try_call!(raw::git_oid_fromraw(&mut raw, bytes.as_ptr()));
+            }
+            Ok(Oid { raw })
+        }
+    }
+
+    /// Creates an all zero Oid structure.
+    pub fn zero() -> Oid {
+        let out = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        Oid { raw: out }
+    }
+
+    /// Hashes the provided data as an object of the provided type, and returns
+    /// an Oid corresponding to the result. This does not store the object
+    /// inside any object database or repository.
+    pub fn hash_object(kind: ObjectType, bytes: &[u8]) -> Result<Oid, Error> {
+        crate::init();
+
+        let mut out = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_odb_hash(
+                &mut out,
+                bytes.as_ptr() as *const libc::c_void,
+                bytes.len(),
+                kind.raw()
+            ));
+        }
+
+        Ok(Oid { raw: out })
+    }
+
+    /// Hashes the content of the provided file as an object of the provided type,
+    /// and returns an Oid corresponding to the result. This does not store the object
+    /// inside any object database or repository.
+    pub fn hash_file<P: AsRef<Path>>(kind: ObjectType, path: P) -> Result<Oid, Error> {
+        crate::init();
+
+        // Normal file path OK (does not need Windows conversion).
+        let rpath = path.as_ref().into_c_string()?;
+
+        let mut out = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_odb_hashfile(&mut out, rpath, kind.raw()));
+        }
+
+        Ok(Oid { raw: out })
+    }
+
+    /// View this OID as a byte-slice 20 bytes in length.
+    pub fn as_bytes(&self) -> &[u8] {
+        &self.raw.id
+    }
+
+    /// Test if this OID is all zeros.
+    pub fn is_zero(&self) -> bool {
+        unsafe { raw::git_oid_iszero(&self.raw) == 1 }
+    }
+}
+
+impl Binding for Oid {
+    type Raw = *const raw::git_oid;
+
+    unsafe fn from_raw(oid: *const raw::git_oid) -> Oid {
+        Oid { raw: *oid }
+    }
+    fn raw(&self) -> *const raw::git_oid {
+        &self.raw as *const _
+    }
+}
+
+impl fmt::Debug for Oid {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self, f)
+    }
+}
+
+impl fmt::Display for Oid {
+    /// Hex-encode this Oid into a formatter.
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let mut dst = [0u8; raw::GIT_OID_HEXSZ + 1];
+        unsafe {
+            raw::git_oid_tostr(
+                dst.as_mut_ptr() as *mut libc::c_char,
+                dst.len() as libc::size_t,
+                &self.raw,
+            );
+        }
+        let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
+        str::from_utf8(s).unwrap().fmt(f)
+    }
+}
+
+impl str::FromStr for Oid {
+    type Err = Error;
+
+    /// Parse a hex-formatted object id into an Oid structure.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if the string is empty, is longer than 40 hex
+    /// characters, or contains any non-hex characters.
+    fn from_str(s: &str) -> Result<Oid, Error> {
+        Oid::from_str(s)
+    }
+}
+
+impl PartialEq for Oid {
+    fn eq(&self, other: &Oid) -> bool {
+        unsafe { raw::git_oid_equal(&self.raw, &other.raw) != 0 }
+    }
+}
+impl Eq for Oid {}
+
+impl PartialOrd for Oid {
+    fn partial_cmp(&self, other: &Oid) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Oid {
+    fn cmp(&self, other: &Oid) -> Ordering {
+        c_cmp_to_ordering(unsafe { raw::git_oid_cmp(&self.raw, &other.raw) })
+    }
+}
+
+impl Hash for Oid {
+    fn hash<H: Hasher>(&self, into: &mut H) {
+        self.raw.id.hash(into)
+    }
+}
+
+impl AsRef<[u8]> for Oid {
+    fn as_ref(&self) -> &[u8] {
+        self.as_bytes()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::fs::File;
+    use std::io::prelude::*;
+
+    use super::Error;
+    use super::Oid;
+    use crate::ObjectType;
+    use tempfile::TempDir;
+
+    #[test]
+    fn conversions() {
+        assert!(Oid::from_str("foo").is_err());
+        assert!(Oid::from_str("decbf2be529ab6557d5429922251e5ee36519817").is_ok());
+        assert!(Oid::from_bytes(b"foo").is_err());
+        assert!(Oid::from_bytes(b"00000000000000000000").is_ok());
+    }
+
+    #[test]
+    fn comparisons() -> Result<(), Error> {
+        assert_eq!(Oid::from_str("decbf2b")?, Oid::from_str("decbf2b")?);
+        assert!(Oid::from_str("decbf2b")? <= Oid::from_str("decbf2b")?);
+        assert!(Oid::from_str("decbf2b")? >= Oid::from_str("decbf2b")?);
+        {
+            let o = Oid::from_str("decbf2b")?;
+            assert_eq!(o, o);
+            assert!(o <= o);
+            assert!(o >= o);
+        }
+        assert_eq!(
+            Oid::from_str("decbf2b")?,
+            Oid::from_str("decbf2b000000000000000000000000000000000")?
+        );
+        assert!(
+            Oid::from_bytes(b"00000000000000000000")? < Oid::from_bytes(b"00000000000000000001")?
+        );
+        assert!(Oid::from_bytes(b"00000000000000000000")? < Oid::from_str("decbf2b")?);
+        assert_eq!(
+            Oid::from_bytes(b"00000000000000000000")?,
+            Oid::from_str("3030303030303030303030303030303030303030")?
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn zero_is_zero() {
+        assert!(Oid::zero().is_zero());
+    }
+
+    #[test]
+    fn hash_object() {
+        let bytes = "Hello".as_bytes();
+        assert!(Oid::hash_object(ObjectType::Blob, bytes).is_ok());
+    }
+
+    #[test]
+    fn hash_file() {
+        let td = TempDir::new().unwrap();
+        let path = td.path().join("hello.txt");
+        let mut file = File::create(&path).unwrap();
+        file.write_all("Hello".as_bytes()).unwrap();
+        assert!(Oid::hash_file(ObjectType::Blob, &path).is_ok());
+    }
+}
diff --git a/git2/src/oid_array.rs b/git2/src/oid_array.rs
new file mode 100644 (file)
index 0000000..0d87ce9
--- /dev/null
@@ -0,0 +1,52 @@
+//! Bindings to libgit2's raw `git_oidarray` type
+
+use std::ops::Deref;
+
+use crate::oid::Oid;
+use crate::raw;
+use crate::util::Binding;
+use std::mem;
+use std::slice;
+
+/// An oid array structure used by libgit2
+///
+/// Some APIs return arrays of OIDs which originate from libgit2. This
+/// wrapper type behaves a little like `Vec<&Oid>` but does so without copying
+/// the underlying Oids until necessary.
+pub struct OidArray {
+    raw: raw::git_oidarray,
+}
+
+impl Deref for OidArray {
+    type Target = [Oid];
+
+    fn deref(&self) -> &[Oid] {
+        unsafe {
+            debug_assert_eq!(mem::size_of::<Oid>(), mem::size_of_val(&*self.raw.ids));
+
+            slice::from_raw_parts(self.raw.ids as *const Oid, self.raw.count as usize)
+        }
+    }
+}
+
+impl Binding for OidArray {
+    type Raw = raw::git_oidarray;
+    unsafe fn from_raw(raw: raw::git_oidarray) -> OidArray {
+        OidArray { raw }
+    }
+    fn raw(&self) -> raw::git_oidarray {
+        self.raw
+    }
+}
+
+impl<'repo> std::fmt::Debug for OidArray {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_tuple("OidArray").field(&self.deref()).finish()
+    }
+}
+
+impl Drop for OidArray {
+    fn drop(&mut self) {
+        unsafe { raw::git_oidarray_free(&mut self.raw) }
+    }
+}
diff --git a/git2/src/opts.rs b/git2/src/opts.rs
new file mode 100644 (file)
index 0000000..ab63661
--- /dev/null
@@ -0,0 +1,465 @@
+//! Bindings to libgit2's git_libgit2_opts function.
+
+use std::ffi::CString;
+use std::ptr;
+
+use crate::string_array::StringArray;
+use crate::util::Binding;
+use crate::{raw, Buf, ConfigLevel, Error, IntoCString};
+
+/// Set the search path for a level of config data. The search path applied to
+/// shared attributes and ignore files, too.
+///
+/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
+/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
+///
+/// `path` lists directories delimited by `GIT_PATH_LIST_SEPARATOR`.
+/// Use magic path `$PATH` to include the old value of the path
+/// (if you want to prepend or append, for instance).
+///
+/// This function is unsafe as it mutates the global state but cannot guarantee
+/// thread-safety. It needs to be externally synchronized with calls to access
+/// the global state.
+pub unsafe fn set_search_path<P>(level: ConfigLevel, path: P) -> Result<(), Error>
+where
+    P: IntoCString,
+{
+    crate::init();
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int,
+        level as libc::c_int,
+        path.into_c_string()?.as_ptr()
+    ));
+    Ok(())
+}
+
+/// Reset the search path for a given level of config data to the default
+/// (generally based on environment variables).
+///
+/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
+/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
+///
+/// This function is unsafe as it mutates the global state but cannot guarantee
+/// thread-safety. It needs to be externally synchronized with calls to access
+/// the global state.
+pub unsafe fn reset_search_path(level: ConfigLevel) -> Result<(), Error> {
+    crate::init();
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int,
+        level as libc::c_int,
+        core::ptr::null::<u8>()
+    ));
+    Ok(())
+}
+
+/// Get the search path for a given level of config data.
+///
+/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`],
+/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`].
+///
+/// This function is unsafe as it mutates the global state but cannot guarantee
+/// thread-safety. It needs to be externally synchronized with calls to access
+/// the global state.
+pub unsafe fn get_search_path(level: ConfigLevel) -> Result<CString, Error> {
+    crate::init();
+    let buf = Buf::new();
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_SEARCH_PATH as libc::c_int,
+        level as libc::c_int,
+        buf.raw() as *const _
+    ));
+    buf.into_c_string()
+}
+
+/// Controls whether or not libgit2 will cache loaded objects.  Enabled by
+/// default, but disabling this can improve performance and memory usage if
+/// loading a large number of objects that will not be referenced again.
+/// Disabling this will cause repository objects to clear their caches when next
+/// accessed.
+pub fn enable_caching(enabled: bool) {
+    crate::init();
+    let error = unsafe {
+        raw::git_libgit2_opts(
+            raw::GIT_OPT_ENABLE_CACHING as libc::c_int,
+            enabled as libc::c_int,
+        )
+    };
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+}
+
+/// Controls whether or not libgit2 will verify when writing an object that all
+/// objects it references are valid. Enabled by default, but disabling this can
+/// significantly improve performance, at the cost of potentially allowing the
+/// creation of objects that reference invalid objects (due to programming
+/// error or repository corruption).
+pub fn strict_object_creation(enabled: bool) {
+    crate::init();
+    let error = unsafe {
+        raw::git_libgit2_opts(
+            raw::GIT_OPT_ENABLE_STRICT_OBJECT_CREATION as libc::c_int,
+            enabled as libc::c_int,
+        )
+    };
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+}
+
+/// Controls whether or not libgit2 will verify that objects loaded have the
+/// expected hash. Enabled by default, but disabling this can significantly
+/// improve performance, at the cost of relying on repository integrity
+/// without checking it.
+pub fn strict_hash_verification(enabled: bool) {
+    crate::init();
+    let error = unsafe {
+        raw::git_libgit2_opts(
+            raw::GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION as libc::c_int,
+            enabled as libc::c_int,
+        )
+    };
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+}
+
+/// Returns the list of git extensions that are supported. This is the list of
+/// built-in extensions supported by libgit2 and custom extensions that have
+/// been added with [`set_extensions`]. Extensions that have been negated will
+/// not be returned.
+///
+/// # Safety
+///
+/// libgit2 stores user extensions in a static variable.
+/// This function is effectively reading a `static mut` and should be treated as such
+pub unsafe fn get_extensions() -> Result<StringArray, Error> {
+    crate::init();
+
+    let mut extensions = raw::git_strarray {
+        strings: ptr::null_mut(),
+        count: 0,
+    };
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_EXTENSIONS as libc::c_int,
+        &mut extensions
+    ));
+
+    Ok(StringArray::from_raw(extensions))
+}
+
+/// Set that the given git extensions are supported by the caller. Extensions
+/// supported by libgit2 may be negated by prefixing them with a `!`.
+/// For example: setting extensions to `[ "!noop", "newext" ]` indicates that
+/// the caller does not want to support repositories with the `noop` extension
+/// but does want to support repositories with the `newext` extension.
+///
+/// # Safety
+///
+/// libgit2 stores user extensions in a static variable.
+/// This function is effectively modifying a `static mut` and should be treated as such
+pub unsafe fn set_extensions<E>(extensions: &[E]) -> Result<(), Error>
+where
+    for<'x> &'x E: IntoCString,
+{
+    crate::init();
+
+    let extensions = extensions
+        .iter()
+        .map(|e| e.into_c_string())
+        .collect::<Result<Vec<_>, _>>()?;
+
+    let extension_ptrs = extensions.iter().map(|e| e.as_ptr()).collect::<Vec<_>>();
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_EXTENSIONS as libc::c_int,
+        extension_ptrs.as_ptr(),
+        extension_ptrs.len() as libc::size_t
+    ));
+
+    Ok(())
+}
+
+/// Set whether or not to verify ownership before performing a repository.
+/// Enabled by default, but disabling this can lead to code execution vulnerabilities.
+pub unsafe fn set_verify_owner_validation(enabled: bool) -> Result<(), Error> {
+    crate::init();
+    let error = raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_OWNER_VALIDATION as libc::c_int,
+        enabled as libc::c_int,
+    );
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+    Ok(())
+}
+
+/// Set the SSL certificate-authority location to `file`. `file` is the location
+/// of a file containing several certificates concatenated together.
+pub unsafe fn set_ssl_cert_file<P>(file: P) -> Result<(), Error>
+where
+    P: IntoCString,
+{
+    crate::init();
+
+    unsafe {
+        try_call!(raw::git_libgit2_opts(
+            raw::GIT_OPT_SET_SSL_CERT_LOCATIONS as libc::c_int,
+            file.into_c_string()?.as_ptr(),
+            core::ptr::null::<libc::c_char>()
+        ));
+    }
+
+    Ok(())
+}
+
+/// Set the SSL certificate-authority location to `path`. `path` is the location
+/// of a directory holding several certificates, one per file.
+pub unsafe fn set_ssl_cert_dir<P>(path: P) -> Result<(), Error>
+where
+    P: IntoCString,
+{
+    crate::init();
+
+    unsafe {
+        try_call!(raw::git_libgit2_opts(
+            raw::GIT_OPT_SET_SSL_CERT_LOCATIONS as libc::c_int,
+            core::ptr::null::<libc::c_char>(),
+            path.into_c_string()?.as_ptr()
+        ));
+    }
+
+    Ok(())
+}
+
+/// Get the maximum mmap window size
+///
+/// # Safety
+/// This function is reading a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn get_mwindow_size() -> Result<libc::size_t, Error> {
+    crate::init();
+
+    let mut size = 0;
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_MWINDOW_SIZE as libc::c_int,
+        &mut size
+    ));
+
+    Ok(size)
+}
+
+/// Set the maximum mmap window size
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn set_mwindow_size(size: libc::size_t) -> Result<(), Error> {
+    crate::init();
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_MWINDOW_SIZE as libc::c_int,
+        size
+    ));
+
+    Ok(())
+}
+
+/// Get the maximum memory that will be mapped in total by the library
+///
+/// # Safety
+/// This function is reading a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn get_mwindow_mapped_limit() -> Result<libc::size_t, Error> {
+    crate::init();
+
+    let mut limit = 0;
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_MWINDOW_MAPPED_LIMIT as libc::c_int,
+        &mut limit
+    ));
+
+    Ok(limit)
+}
+
+/// Set the maximum amount of memory that can be mapped at any time
+/// by the library.
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn set_mwindow_mapped_limit(limit: libc::size_t) -> Result<(), Error> {
+    crate::init();
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_MWINDOW_MAPPED_LIMIT as libc::c_int,
+        limit
+    ));
+
+    Ok(())
+}
+
+/// Get the maximum number of files that will be mapped at any time by the
+/// library.
+///
+/// # Safety
+/// This function is reading a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn get_mwindow_file_limit() -> Result<libc::size_t, Error> {
+    crate::init();
+
+    let mut limit = 0;
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_MWINDOW_FILE_LIMIT as libc::c_int,
+        &mut limit
+    ));
+
+    Ok(limit)
+}
+
+/// Set the maximum number of files that can be mapped at any time
+/// by the library. The default (0) is unlimited.
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn set_mwindow_file_limit(limit: libc::size_t) -> Result<(), Error> {
+    crate::init();
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_MWINDOW_FILE_LIMIT as libc::c_int,
+        limit
+    ));
+
+    Ok(())
+}
+
+/// Get server connect timeout in milliseconds
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn get_server_connect_timeout_in_milliseconds() -> Result<libc::c_int, Error> {
+    crate::init();
+
+    let mut server_connect_timeout = 0;
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_SERVER_CONNECT_TIMEOUT as libc::c_int,
+        &mut server_connect_timeout
+    ));
+
+    Ok(server_connect_timeout)
+}
+
+/// Set server connect timeout in milliseconds
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn set_server_connect_timeout_in_milliseconds(
+    timeout: libc::c_int,
+) -> Result<(), Error> {
+    crate::init();
+
+    let error = raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_SERVER_CONNECT_TIMEOUT as libc::c_int,
+        timeout,
+    );
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+
+    Ok(())
+}
+
+/// Get server timeout in milliseconds
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn get_server_timeout_in_milliseconds() -> Result<libc::c_int, Error> {
+    crate::init();
+
+    let mut server_timeout = 0;
+
+    try_call!(raw::git_libgit2_opts(
+        raw::GIT_OPT_GET_SERVER_TIMEOUT as libc::c_int,
+        &mut server_timeout
+    ));
+
+    Ok(server_timeout)
+}
+
+/// Set server timeout in milliseconds
+///
+/// # Safety
+/// This function is modifying a C global without synchronization, so it is not
+/// thread safe, and should only be called before any thread is spawned.
+pub unsafe fn set_server_timeout_in_milliseconds(timeout: libc::c_int) -> Result<(), Error> {
+    crate::init();
+
+    let error = raw::git_libgit2_opts(
+        raw::GIT_OPT_SET_SERVER_TIMEOUT as libc::c_int,
+        timeout as libc::c_int,
+    );
+    // This function cannot actually fail, but the function has an error return
+    // for other options that can.
+    debug_assert!(error >= 0);
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn smoke() {
+        strict_hash_verification(false);
+    }
+
+    #[test]
+    fn mwindow_size() {
+        unsafe {
+            assert!(set_mwindow_size(1024).is_ok());
+            assert!(get_mwindow_size().unwrap() == 1024);
+        }
+    }
+
+    #[test]
+    fn mwindow_mapped_limit() {
+        unsafe {
+            assert!(set_mwindow_mapped_limit(1024).is_ok());
+            assert!(get_mwindow_mapped_limit().unwrap() == 1024);
+        }
+    }
+
+    #[test]
+    fn mwindow_file_limit() {
+        unsafe {
+            assert!(set_mwindow_file_limit(1024).is_ok());
+            assert!(get_mwindow_file_limit().unwrap() == 1024);
+        }
+    }
+
+    #[test]
+    fn server_connect_timeout() {
+        unsafe {
+            assert!(set_server_connect_timeout_in_milliseconds(5000).is_ok());
+            assert!(get_server_connect_timeout_in_milliseconds().unwrap() == 5000);
+        }
+    }
+
+    #[test]
+    fn server_timeout() {
+        unsafe {
+            assert!(set_server_timeout_in_milliseconds(10_000).is_ok());
+            assert!(get_server_timeout_in_milliseconds().unwrap() == 10_000);
+        }
+    }
+}
diff --git a/git2/src/packbuilder.rs b/git2/src/packbuilder.rs
new file mode 100644 (file)
index 0000000..de47bbc
--- /dev/null
@@ -0,0 +1,503 @@
+use libc::{c_int, c_uint, c_void, size_t};
+use std::marker;
+use std::path::Path;
+use std::ptr;
+use std::slice;
+use std::str;
+
+use crate::odb::{write_pack_progress_cb, OdbPackwriterCb};
+use crate::util::Binding;
+use crate::IntoCString;
+use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk};
+
+#[derive(PartialEq, Eq, Clone, Debug, Copy)]
+/// Stages that are reported by the `PackBuilder` progress callback.
+pub enum PackBuilderStage {
+    /// Adding objects to the pack
+    AddingObjects,
+    /// Deltafication of the pack
+    Deltafication,
+}
+
+pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a;
+pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a;
+
+/// A builder for creating a packfile
+pub struct PackBuilder<'repo> {
+    raw: *mut raw::git_packbuilder,
+    _progress: Option<Box<Box<ProgressCb<'repo>>>>,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl<'repo> PackBuilder<'repo> {
+    /// Insert a single object. For an optimal pack it's mandatory to insert
+    /// objects in recency order, commits followed by trees and blobs.
+    pub fn insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
+        let name = crate::opt_cstr(name)?;
+        unsafe {
+            try_call!(raw::git_packbuilder_insert(self.raw, id.raw(), name));
+        }
+        Ok(())
+    }
+
+    /// Insert a root tree object. This will add the tree as well as all
+    /// referenced trees and blobs.
+    pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_packbuilder_insert_tree(self.raw, id.raw()));
+        }
+        Ok(())
+    }
+
+    /// Insert a commit object. This will add a commit as well as the completed
+    /// referenced tree.
+    pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_packbuilder_insert_commit(self.raw, id.raw()));
+        }
+        Ok(())
+    }
+
+    /// Insert objects as given by the walk. Those commits and all objects they
+    /// reference will be inserted into the packbuilder.
+    pub fn insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw()));
+        }
+        Ok(())
+    }
+
+    /// Recursively insert an object and its referenced objects. Insert the
+    /// object as well as any object it references.
+    pub fn insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> {
+        let name = crate::opt_cstr(name)?;
+        unsafe {
+            try_call!(raw::git_packbuilder_insert_recur(self.raw, id.raw(), name));
+        }
+        Ok(())
+    }
+
+    /// Write the contents of the packfile to an in-memory buffer. The contents
+    /// of the buffer will become a valid packfile, even though there will be
+    /// no attached index.
+    pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw));
+        }
+        Ok(())
+    }
+
+    /// Write the new pack and corresponding index file to path.
+    /// To set a progress callback, use `set_progress_callback` before calling this method.
+    pub fn write(&mut self, path: &Path, mode: u32) -> Result<(), Error> {
+        let path = path.into_c_string()?;
+        let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb);
+        let progress_payload = Box::new(OdbPackwriterCb { cb: None });
+        let progress_payload_ptr = Box::into_raw(progress_payload);
+
+        unsafe {
+            try_call!(raw::git_packbuilder_write(
+                self.raw,
+                path,
+                mode,
+                progress_cb,
+                progress_payload_ptr as *mut _
+            ));
+        }
+        Ok(())
+    }
+
+    /// Create the new pack and pass each object to the callback.
+    pub fn foreach<F>(&mut self, mut cb: F) -> Result<(), Error>
+    where
+        F: FnMut(&[u8]) -> bool,
+    {
+        let mut cb = &mut cb as &mut ForEachCb<'_>;
+        let ptr = &mut cb as *mut _;
+        let foreach: raw::git_packbuilder_foreach_cb = Some(foreach_c);
+        unsafe {
+            try_call!(raw::git_packbuilder_foreach(
+                self.raw,
+                foreach,
+                ptr as *mut _
+            ));
+        }
+        Ok(())
+    }
+
+    /// `progress` will be called with progress information during pack
+    /// building. Be aware that this is called inline with pack building
+    /// operations, so performance may be affected.
+    ///
+    /// There can only be one progress callback attached, this will replace any
+    /// existing one. See `unset_progress_callback` to remove the current
+    /// progress callback without attaching a new one.
+    pub fn set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error>
+    where
+        F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo,
+    {
+        let mut progress = Box::new(Box::new(progress) as Box<ProgressCb<'_>>);
+        let ptr = &mut *progress as *mut _;
+        let progress_c: raw::git_packbuilder_progress = Some(progress_c);
+        unsafe {
+            try_call!(raw::git_packbuilder_set_callbacks(
+                self.raw,
+                progress_c,
+                ptr as *mut _
+            ));
+        }
+        self._progress = Some(progress);
+        Ok(())
+    }
+
+    /// Remove the current progress callback.  See `set_progress_callback` to
+    /// set the progress callback.
+    pub fn unset_progress_callback(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_packbuilder_set_callbacks(
+                self.raw,
+                None,
+                ptr::null_mut()
+            ));
+            self._progress = None;
+        }
+        Ok(())
+    }
+
+    /// Set the number of threads to be used.
+    ///
+    /// Returns the number of threads to be used.
+    pub fn set_threads(&mut self, threads: u32) -> u32 {
+        unsafe { raw::git_packbuilder_set_threads(self.raw, threads) }
+    }
+
+    /// Get the total number of objects the packbuilder will write out.
+    pub fn object_count(&self) -> usize {
+        unsafe { raw::git_packbuilder_object_count(self.raw) }
+    }
+
+    /// Get the number of objects the packbuilder has already written out.
+    pub fn written(&self) -> usize {
+        unsafe { raw::git_packbuilder_written(self.raw) }
+    }
+
+    /// Get the packfile's hash. A packfile's name is derived from the sorted
+    /// hashing of all object names. This is only correct after the packfile
+    /// has been written.
+    #[deprecated = "use `name()` to retrieve the filename"]
+    #[allow(deprecated)]
+    pub fn hash(&self) -> Option<Oid> {
+        if self.object_count() == 0 {
+            unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) }
+        } else {
+            None
+        }
+    }
+
+    /// Get the unique name for the resulting packfile.
+    ///
+    /// The packfile's name is derived from the packfile's content. This is only
+    /// correct after the packfile has been written.
+    ///
+    /// Returns `None` if the packfile has not been written or if the name is
+    /// not valid utf-8.
+    pub fn name(&self) -> Option<&str> {
+        self.name_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the unique name for the resulting packfile, in bytes.
+    ///
+    /// The packfile's name is derived from the packfile's content. This is only
+    /// correct after the packfile has been written.
+    pub fn name_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_packbuilder_name(self.raw)) }
+    }
+}
+
+impl<'repo> Binding for PackBuilder<'repo> {
+    type Raw = *mut raw::git_packbuilder;
+    unsafe fn from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo> {
+        PackBuilder {
+            raw: ptr,
+            _progress: None,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_packbuilder {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for PackBuilder<'repo> {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_packbuilder_set_callbacks(self.raw, None, ptr::null_mut());
+            raw::git_packbuilder_free(self.raw);
+        }
+    }
+}
+
+impl Binding for PackBuilderStage {
+    type Raw = raw::git_packbuilder_stage_t;
+    unsafe fn from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage {
+        match raw {
+            raw::GIT_PACKBUILDER_ADDING_OBJECTS => PackBuilderStage::AddingObjects,
+            raw::GIT_PACKBUILDER_DELTAFICATION => PackBuilderStage::Deltafication,
+            _ => panic!("Unknown git diff binary kind"),
+        }
+    }
+    fn raw(&self) -> raw::git_packbuilder_stage_t {
+        match *self {
+            PackBuilderStage::AddingObjects => raw::GIT_PACKBUILDER_ADDING_OBJECTS,
+            PackBuilderStage::Deltafication => raw::GIT_PACKBUILDER_DELTAFICATION,
+        }
+    }
+}
+
+extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int {
+    unsafe {
+        let buf = slice::from_raw_parts(buf as *const u8, size as usize);
+
+        let r = panic::wrap(|| {
+            let data = data as *mut &mut ForEachCb<'_>;
+            (*data)(buf)
+        });
+        if r == Some(true) {
+            0
+        } else {
+            -1
+        }
+    }
+}
+
+extern "C" fn progress_c(
+    stage: raw::git_packbuilder_stage_t,
+    current: c_uint,
+    total: c_uint,
+    data: *mut c_void,
+) -> c_int {
+    unsafe {
+        let stage = Binding::from_raw(stage);
+
+        let r = panic::wrap(|| {
+            let data = data as *mut Box<ProgressCb<'_>>;
+            (*data)(stage, current, total)
+        });
+        if r == Some(true) {
+            0
+        } else {
+            -1
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Buf, Oid};
+
+    // hash of a packfile constructed without any objects in it
+    const EMPTY_PACKFILE_OID: &str = "029d08823bd8a8eab510ad6ac75c823cfd3ed31e";
+
+    fn pack_header(len: u8) -> Vec<u8> {
+        [].iter()
+            .chain(b"PACK") // signature
+            .chain(&[0, 0, 0, 2]) // version number
+            .chain(&[0, 0, 0, len]) // number of objects
+            .cloned()
+            .collect::<Vec<u8>>()
+    }
+
+    fn empty_pack_header() -> Vec<u8> {
+        pack_header(0)
+            .iter()
+            .chain(&[
+                0x02, 0x9d, 0x08, 0x82, 0x3b, // ^
+                0xd8, 0xa8, 0xea, 0xb5, 0x10, // | SHA-1 of the zero
+                0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header
+                0x3c, 0xfd, 0x3e, 0xd3, 0x1e,
+            ]) // v
+            .cloned()
+            .collect::<Vec<u8>>()
+    }
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let _builder = t!(repo.packbuilder());
+    }
+
+    #[test]
+    fn smoke_write_buf() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let mut buf = Buf::new();
+        t!(builder.write_buf(&mut buf));
+        #[allow(deprecated)]
+        {
+            assert!(builder.hash().unwrap().is_zero());
+        }
+        assert!(builder.name().is_none());
+        assert_eq!(&*buf, &*empty_pack_header());
+    }
+
+    #[test]
+    fn smoke_write() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        t!(builder.write(repo.path(), 0));
+        #[allow(deprecated)]
+        {
+            assert!(builder.hash().unwrap() == Oid::from_str(EMPTY_PACKFILE_OID).unwrap());
+        }
+        assert!(builder.name().unwrap() == EMPTY_PACKFILE_OID);
+    }
+
+    #[test]
+    fn smoke_foreach() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let mut buf = Vec::<u8>::new();
+        t!(builder.foreach(|bytes| {
+            buf.extend(bytes);
+            true
+        }));
+        assert_eq!(&*buf, &*empty_pack_header());
+    }
+
+    #[test]
+    fn insert_write_buf() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let mut buf = Buf::new();
+        let (commit, _tree) = crate::test::commit(&repo);
+        t!(builder.insert_object(commit, None));
+        assert_eq!(builder.object_count(), 1);
+        t!(builder.write_buf(&mut buf));
+        // Just check that the correct number of objects are written
+        assert_eq!(&buf[0..12], &*pack_header(1));
+    }
+
+    #[test]
+    fn insert_tree_write_buf() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let mut buf = Buf::new();
+        let (_commit, tree) = crate::test::commit(&repo);
+        // will insert the tree itself and the blob, 2 objects
+        t!(builder.insert_tree(tree));
+        assert_eq!(builder.object_count(), 2);
+        t!(builder.write_buf(&mut buf));
+        // Just check that the correct number of objects are written
+        assert_eq!(&buf[0..12], &*pack_header(2));
+    }
+
+    #[test]
+    fn insert_commit_write_buf() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let mut buf = Buf::new();
+        let (commit, _tree) = crate::test::commit(&repo);
+        // will insert the commit, its tree and the blob, 3 objects
+        t!(builder.insert_commit(commit));
+        assert_eq!(builder.object_count(), 3);
+        t!(builder.write_buf(&mut buf));
+        // Just check that the correct number of objects are written
+        assert_eq!(&buf[0..12], &*pack_header(3));
+    }
+
+    #[test]
+    fn insert_write() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let (commit, _tree) = crate::test::commit(&repo);
+        t!(builder.insert_object(commit, None));
+        assert_eq!(builder.object_count(), 1);
+        t!(builder.write(repo.path(), 0));
+        t!(repo.find_commit(commit));
+    }
+
+    #[test]
+    fn insert_tree_write() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let (_commit, tree) = crate::test::commit(&repo);
+        // will insert the tree itself and the blob, 2 objects
+        t!(builder.insert_tree(tree));
+        assert_eq!(builder.object_count(), 2);
+        t!(builder.write(repo.path(), 0));
+        t!(repo.find_tree(tree));
+    }
+
+    #[test]
+    fn insert_commit_write() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let (commit, _tree) = crate::test::commit(&repo);
+        // will insert the commit, its tree and the blob, 3 objects
+        t!(builder.insert_commit(commit));
+        assert_eq!(builder.object_count(), 3);
+        t!(builder.write(repo.path(), 0));
+        t!(repo.find_commit(commit));
+    }
+
+    #[test]
+    fn progress_callback() {
+        let mut progress_called = false;
+        {
+            let (_td, repo) = crate::test::repo_init();
+            let mut builder = t!(repo.packbuilder());
+            let (commit, _tree) = crate::test::commit(&repo);
+            t!(builder.set_progress_callback(|_, _, _| {
+                progress_called = true;
+                true
+            }));
+            t!(builder.insert_commit(commit));
+            t!(builder.write_buf(&mut Buf::new()));
+        }
+        assert_eq!(progress_called, true);
+    }
+
+    #[test]
+    fn clear_progress_callback() {
+        let mut progress_called = false;
+        {
+            let (_td, repo) = crate::test::repo_init();
+            let mut builder = t!(repo.packbuilder());
+            let (commit, _tree) = crate::test::commit(&repo);
+            t!(builder.set_progress_callback(|_, _, _| {
+                progress_called = true;
+                true
+            }));
+            t!(builder.unset_progress_callback());
+            t!(builder.insert_commit(commit));
+            t!(builder.write_buf(&mut Buf::new()));
+        }
+        assert_eq!(progress_called, false);
+    }
+
+    #[test]
+    fn progress_callback_with_write() {
+        let mut progress_called = false;
+        {
+            let (_td, repo) = crate::test::repo_init();
+            let mut builder = t!(repo.packbuilder());
+            let (commit, _tree) = crate::test::commit(&repo);
+            t!(builder.set_progress_callback(|_, _, _| {
+                progress_called = true;
+                true
+            }));
+            t!(builder.insert_commit(commit));
+            t!(builder.write(repo.path(), 0));
+        }
+        assert_eq!(progress_called, true);
+    }
+
+    #[test]
+    fn set_threads() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut builder = t!(repo.packbuilder());
+        let used = builder.set_threads(4);
+        // Will be 1 if not compiled with threading.
+        assert!(used == 1 || used == 4);
+    }
+}
diff --git a/git2/src/panic.rs b/git2/src/panic.rs
new file mode 100644 (file)
index 0000000..3e1b208
--- /dev/null
@@ -0,0 +1,33 @@
+use std::any::Any;
+use std::cell::RefCell;
+
+thread_local!(static LAST_ERROR: RefCell<Option<Box<dyn Any + Send>>> = {
+    RefCell::new(None)
+});
+
+pub fn wrap<T, F: FnOnce() -> T + std::panic::UnwindSafe>(f: F) -> Option<T> {
+    use std::panic;
+    if LAST_ERROR.with(|slot| slot.borrow().is_some()) {
+        return None;
+    }
+    match panic::catch_unwind(f) {
+        Ok(ret) => Some(ret),
+        Err(e) => {
+            LAST_ERROR.with(move |slot| {
+                *slot.borrow_mut() = Some(e);
+            });
+            None
+        }
+    }
+}
+
+pub fn check() {
+    let err = LAST_ERROR.with(|slot| slot.borrow_mut().take());
+    if let Some(err) = err {
+        std::panic::resume_unwind(err);
+    }
+}
+
+pub fn panicked() -> bool {
+    LAST_ERROR.with(|slot| slot.borrow().is_some())
+}
diff --git a/git2/src/patch.rs b/git2/src/patch.rs
new file mode 100644 (file)
index 0000000..67b84c0
--- /dev/null
@@ -0,0 +1,235 @@
+use libc::{c_int, c_void};
+use std::marker::PhantomData;
+use std::path::Path;
+use std::ptr;
+
+use crate::diff::{print_cb, LineCb};
+use crate::util::{into_opt_c_string, Binding};
+use crate::{raw, Blob, Buf, Diff, DiffDelta, DiffHunk, DiffLine, DiffOptions, Error};
+
+/// A structure representing the text changes in a single diff delta.
+///
+/// This is an opaque structure.
+pub struct Patch<'buffers> {
+    raw: *mut raw::git_patch,
+    buffers: PhantomData<&'buffers ()>,
+}
+
+unsafe impl<'buffers> Send for Patch<'buffers> {}
+
+impl<'buffers> Binding for Patch<'buffers> {
+    type Raw = *mut raw::git_patch;
+    unsafe fn from_raw(raw: Self::Raw) -> Self {
+        Patch {
+            raw,
+            buffers: PhantomData,
+        }
+    }
+    fn raw(&self) -> Self::Raw {
+        self.raw
+    }
+}
+
+impl<'buffers> Drop for Patch<'buffers> {
+    fn drop(&mut self) {
+        unsafe { raw::git_patch_free(self.raw) }
+    }
+}
+
+impl<'buffers> Patch<'buffers> {
+    /// Return a Patch for one file in a Diff.
+    ///
+    /// Returns Ok(None) for an unchanged or binary file.
+    pub fn from_diff(diff: &Diff<'buffers>, idx: usize) -> Result<Option<Self>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_patch_from_diff(&mut ret, diff.raw(), idx));
+            Ok(Binding::from_raw_opt(ret))
+        }
+    }
+
+    /// Generate a Patch by diffing two blobs.
+    pub fn from_blobs(
+        old_blob: &Blob<'buffers>,
+        old_path: Option<&Path>,
+        new_blob: &Blob<'buffers>,
+        new_path: Option<&Path>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Self, Error> {
+        let mut ret = ptr::null_mut();
+        let old_path = into_opt_c_string(old_path)?;
+        let new_path = into_opt_c_string(new_path)?;
+        unsafe {
+            try_call!(raw::git_patch_from_blobs(
+                &mut ret,
+                old_blob.raw(),
+                old_path,
+                new_blob.raw(),
+                new_path,
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Generate a Patch by diffing a blob and a buffer.
+    pub fn from_blob_and_buffer(
+        old_blob: &Blob<'buffers>,
+        old_path: Option<&Path>,
+        new_buffer: &'buffers [u8],
+        new_path: Option<&Path>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Self, Error> {
+        let mut ret = ptr::null_mut();
+        let old_path = into_opt_c_string(old_path)?;
+        let new_path = into_opt_c_string(new_path)?;
+        unsafe {
+            try_call!(raw::git_patch_from_blob_and_buffer(
+                &mut ret,
+                old_blob.raw(),
+                old_path,
+                new_buffer.as_ptr() as *const c_void,
+                new_buffer.len(),
+                new_path,
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Generate a Patch by diffing two buffers.
+    pub fn from_buffers(
+        old_buffer: &'buffers [u8],
+        old_path: Option<&Path>,
+        new_buffer: &'buffers [u8],
+        new_path: Option<&Path>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Self, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let old_path = into_opt_c_string(old_path)?;
+        let new_path = into_opt_c_string(new_path)?;
+        unsafe {
+            try_call!(raw::git_patch_from_buffers(
+                &mut ret,
+                old_buffer.as_ptr() as *const c_void,
+                old_buffer.len(),
+                old_path,
+                new_buffer.as_ptr() as *const c_void,
+                new_buffer.len(),
+                new_path,
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the DiffDelta associated with the Patch.
+    pub fn delta(&self) -> DiffDelta<'buffers> {
+        unsafe { Binding::from_raw(raw::git_patch_get_delta(self.raw) as *mut _) }
+    }
+
+    /// Get the number of hunks in the Patch.
+    pub fn num_hunks(&self) -> usize {
+        unsafe { raw::git_patch_num_hunks(self.raw) }
+    }
+
+    /// Get the number of lines of context, additions, and deletions in the Patch.
+    pub fn line_stats(&self) -> Result<(usize, usize, usize), Error> {
+        let mut context = 0;
+        let mut additions = 0;
+        let mut deletions = 0;
+        unsafe {
+            try_call!(raw::git_patch_line_stats(
+                &mut context,
+                &mut additions,
+                &mut deletions,
+                self.raw
+            ));
+        }
+        Ok((context, additions, deletions))
+    }
+
+    /// Get a DiffHunk and its total line count from the Patch.
+    pub fn hunk(&self, hunk_idx: usize) -> Result<(DiffHunk<'buffers>, usize), Error> {
+        let mut ret = ptr::null();
+        let mut lines = 0;
+        unsafe {
+            try_call!(raw::git_patch_get_hunk(
+                &mut ret, &mut lines, self.raw, hunk_idx
+            ));
+            Ok((Binding::from_raw(ret), lines))
+        }
+    }
+
+    /// Get the number of lines in a hunk.
+    pub fn num_lines_in_hunk(&self, hunk_idx: usize) -> Result<usize, Error> {
+        unsafe { Ok(try_call!(raw::git_patch_num_lines_in_hunk(self.raw, hunk_idx)) as usize) }
+    }
+
+    /// Get a DiffLine from a hunk of the Patch.
+    pub fn line_in_hunk(
+        &self,
+        hunk_idx: usize,
+        line_of_hunk: usize,
+    ) -> Result<DiffLine<'buffers>, Error> {
+        let mut ret = ptr::null();
+        unsafe {
+            try_call!(raw::git_patch_get_line_in_hunk(
+                &mut ret,
+                self.raw,
+                hunk_idx,
+                line_of_hunk
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the size of a Patch's diff data in bytes.
+    pub fn size(
+        &self,
+        include_context: bool,
+        include_hunk_headers: bool,
+        include_file_headers: bool,
+    ) -> usize {
+        unsafe {
+            raw::git_patch_size(
+                self.raw,
+                include_context as c_int,
+                include_hunk_headers as c_int,
+                include_file_headers as c_int,
+            )
+        }
+    }
+
+    /// Print the Patch to text via a callback.
+    pub fn print(&mut self, mut line_cb: &mut LineCb<'_>) -> Result<(), Error> {
+        let ptr = &mut line_cb as *mut _ as *mut c_void;
+        unsafe {
+            let cb: raw::git_diff_line_cb = Some(print_cb);
+            try_call!(raw::git_patch_print(self.raw, cb, ptr));
+            Ok(())
+        }
+    }
+
+    /// Get the Patch text as a Buf.
+    pub fn to_buf(&mut self) -> Result<Buf, Error> {
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_patch_to_buf(buf.raw(), self.raw));
+        }
+        Ok(buf)
+    }
+}
+
+impl<'buffers> std::fmt::Debug for Patch<'buffers> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("Patch");
+        ds.field("delta", &self.delta())
+            .field("num_hunks", &self.num_hunks());
+        if let Ok(line_stats) = &self.line_stats() {
+            ds.field("line_stats", line_stats);
+        }
+        ds.finish()
+    }
+}
diff --git a/git2/src/pathspec.rs b/git2/src/pathspec.rs
new file mode 100644 (file)
index 0000000..16850dc
--- /dev/null
@@ -0,0 +1,368 @@
+use libc::size_t;
+use std::iter::FusedIterator;
+use std::marker;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr;
+
+use crate::util::{path_to_repo_path, Binding};
+use crate::{raw, Diff, DiffDelta, Error, Index, IntoCString, PathspecFlags, Repository, Tree};
+
+/// Structure representing a compiled pathspec used for matching against various
+/// structures.
+pub struct Pathspec {
+    raw: *mut raw::git_pathspec,
+}
+
+/// List of filenames matching a pathspec.
+pub struct PathspecMatchList<'ps> {
+    raw: *mut raw::git_pathspec_match_list,
+    _marker: marker::PhantomData<&'ps Pathspec>,
+}
+
+/// Iterator over the matched paths in a pathspec.
+pub struct PathspecEntries<'list> {
+    range: Range<usize>,
+    list: &'list PathspecMatchList<'list>,
+}
+
+/// Iterator over the matching diff deltas.
+pub struct PathspecDiffEntries<'list> {
+    range: Range<usize>,
+    list: &'list PathspecMatchList<'list>,
+}
+
+/// Iterator over the failed list of pathspec items that did not match.
+pub struct PathspecFailedEntries<'list> {
+    range: Range<usize>,
+    list: &'list PathspecMatchList<'list>,
+}
+
+impl Pathspec {
+    /// Creates a new pathspec from a list of specs to match against.
+    pub fn new<I, T>(specs: I) -> Result<Pathspec, Error>
+    where
+        T: IntoCString,
+        I: IntoIterator<Item = T>,
+    {
+        crate::init();
+        let (_a, _b, arr) = crate::util::iter2cstrs_paths(specs)?;
+        unsafe {
+            let mut ret = ptr::null_mut();
+            try_call!(raw::git_pathspec_new(&mut ret, &arr));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Match a pathspec against files in a diff.
+    ///
+    /// The list returned contains the list of all matched filenames (unless you
+    /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
+    /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
+    /// specified.
+    pub fn match_diff(
+        &self,
+        diff: &Diff<'_>,
+        flags: PathspecFlags,
+    ) -> Result<PathspecMatchList<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_pathspec_match_diff(
+                &mut ret,
+                diff.raw(),
+                flags.bits(),
+                self.raw
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Match a pathspec against files in a tree.
+    ///
+    /// The list returned contains the list of all matched filenames (unless you
+    /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
+    /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
+    /// specified.
+    pub fn match_tree(
+        &self,
+        tree: &Tree<'_>,
+        flags: PathspecFlags,
+    ) -> Result<PathspecMatchList<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_pathspec_match_tree(
+                &mut ret,
+                tree.raw(),
+                flags.bits(),
+                self.raw
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// This matches the pathspec against the files in the repository index.
+    ///
+    /// The list returned contains the list of all matched filenames (unless you
+    /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
+    /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
+    /// specified.
+    pub fn match_index(
+        &self,
+        index: &Index,
+        flags: PathspecFlags,
+    ) -> Result<PathspecMatchList<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_pathspec_match_index(
+                &mut ret,
+                index.raw(),
+                flags.bits(),
+                self.raw
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Match a pathspec against the working directory of a repository.
+    ///
+    /// This matches the pathspec against the current files in the working
+    /// directory of the repository. It is an error to invoke this on a bare
+    /// repo. This handles git ignores (i.e. ignored files will not be
+    /// considered to match the pathspec unless the file is tracked in the
+    /// index).
+    ///
+    /// The list returned contains the list of all matched filenames (unless you
+    /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
+    /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
+    /// specified.
+    pub fn match_workdir(
+        &self,
+        repo: &Repository,
+        flags: PathspecFlags,
+    ) -> Result<PathspecMatchList<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_pathspec_match_workdir(
+                &mut ret,
+                repo.raw(),
+                flags.bits(),
+                self.raw
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Try to match a path against a pathspec
+    ///
+    /// Unlike most of the other pathspec matching functions, this will not fall
+    /// back on the native case-sensitivity for your platform. You must
+    /// explicitly pass flags to control case sensitivity or else this will fall
+    /// back on being case sensitive.
+    pub fn matches_path(&self, path: &Path, flags: PathspecFlags) -> bool {
+        let path = path_to_repo_path(path).unwrap();
+        unsafe { raw::git_pathspec_matches_path(&*self.raw, flags.bits(), path.as_ptr()) == 1 }
+    }
+}
+
+impl Binding for Pathspec {
+    type Raw = *mut raw::git_pathspec;
+
+    unsafe fn from_raw(raw: *mut raw::git_pathspec) -> Pathspec {
+        Pathspec { raw }
+    }
+    fn raw(&self) -> *mut raw::git_pathspec {
+        self.raw
+    }
+}
+
+impl Drop for Pathspec {
+    fn drop(&mut self) {
+        unsafe { raw::git_pathspec_free(self.raw) }
+    }
+}
+
+impl<'ps> PathspecMatchList<'ps> {
+    fn entrycount(&self) -> usize {
+        unsafe { raw::git_pathspec_match_list_entrycount(&*self.raw) as usize }
+    }
+
+    fn failed_entrycount(&self) -> usize {
+        unsafe { raw::git_pathspec_match_list_failed_entrycount(&*self.raw) as usize }
+    }
+
+    /// Returns an iterator over the matching filenames in this list.
+    pub fn entries(&self) -> PathspecEntries<'_> {
+        let n = self.entrycount();
+        let n = if n > 0 && self.entry(0).is_none() {
+            0
+        } else {
+            n
+        };
+        PathspecEntries {
+            range: 0..n,
+            list: self,
+        }
+    }
+
+    /// Get a matching filename by position.
+    ///
+    /// If this list was generated from a diff, then the return value will
+    /// always be `None.
+    pub fn entry(&self, i: usize) -> Option<&[u8]> {
+        unsafe {
+            let ptr = raw::git_pathspec_match_list_entry(&*self.raw, i as size_t);
+            crate::opt_bytes(self, ptr)
+        }
+    }
+
+    /// Returns an iterator over the matching diff entries in this list.
+    pub fn diff_entries(&self) -> PathspecDiffEntries<'_> {
+        let n = self.entrycount();
+        let n = if n > 0 && self.diff_entry(0).is_none() {
+            0
+        } else {
+            n
+        };
+        PathspecDiffEntries {
+            range: 0..n,
+            list: self,
+        }
+    }
+
+    /// Get a matching diff delta by position.
+    ///
+    /// If the list was not generated from a diff, then the return value will
+    /// always be `None`.
+    pub fn diff_entry(&self, i: usize) -> Option<DiffDelta<'_>> {
+        unsafe {
+            let ptr = raw::git_pathspec_match_list_diff_entry(&*self.raw, i as size_t);
+            Binding::from_raw_opt(ptr as *mut _)
+        }
+    }
+
+    /// Returns an iterator over the non-matching entries in this list.
+    pub fn failed_entries(&self) -> PathspecFailedEntries<'_> {
+        let n = self.failed_entrycount();
+        let n = if n > 0 && self.failed_entry(0).is_none() {
+            0
+        } else {
+            n
+        };
+        PathspecFailedEntries {
+            range: 0..n,
+            list: self,
+        }
+    }
+
+    /// Get an original pathspec string that had no matches.
+    pub fn failed_entry(&self, i: usize) -> Option<&[u8]> {
+        unsafe {
+            let ptr = raw::git_pathspec_match_list_failed_entry(&*self.raw, i as size_t);
+            crate::opt_bytes(self, ptr)
+        }
+    }
+}
+
+impl<'ps> Binding for PathspecMatchList<'ps> {
+    type Raw = *mut raw::git_pathspec_match_list;
+
+    unsafe fn from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps> {
+        PathspecMatchList {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_pathspec_match_list {
+        self.raw
+    }
+}
+
+impl<'ps> Drop for PathspecMatchList<'ps> {
+    fn drop(&mut self) {
+        unsafe { raw::git_pathspec_match_list_free(self.raw) }
+    }
+}
+
+impl<'list> Iterator for PathspecEntries<'list> {
+    type Item = &'list [u8];
+    fn next(&mut self) -> Option<&'list [u8]> {
+        self.range.next().and_then(|i| self.list.entry(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'list> DoubleEndedIterator for PathspecEntries<'list> {
+    fn next_back(&mut self) -> Option<&'list [u8]> {
+        self.range.next_back().and_then(|i| self.list.entry(i))
+    }
+}
+impl<'list> FusedIterator for PathspecEntries<'list> {}
+impl<'list> ExactSizeIterator for PathspecEntries<'list> {}
+
+impl<'list> Iterator for PathspecDiffEntries<'list> {
+    type Item = DiffDelta<'list>;
+    fn next(&mut self) -> Option<DiffDelta<'list>> {
+        self.range.next().and_then(|i| self.list.diff_entry(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'list> DoubleEndedIterator for PathspecDiffEntries<'list> {
+    fn next_back(&mut self) -> Option<DiffDelta<'list>> {
+        self.range.next_back().and_then(|i| self.list.diff_entry(i))
+    }
+}
+impl<'list> FusedIterator for PathspecDiffEntries<'list> {}
+impl<'list> ExactSizeIterator for PathspecDiffEntries<'list> {}
+
+impl<'list> Iterator for PathspecFailedEntries<'list> {
+    type Item = &'list [u8];
+    fn next(&mut self) -> Option<&'list [u8]> {
+        self.range.next().and_then(|i| self.list.failed_entry(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'list> DoubleEndedIterator for PathspecFailedEntries<'list> {
+    fn next_back(&mut self) -> Option<&'list [u8]> {
+        self.range
+            .next_back()
+            .and_then(|i| self.list.failed_entry(i))
+    }
+}
+impl<'list> FusedIterator for PathspecFailedEntries<'list> {}
+impl<'list> ExactSizeIterator for PathspecFailedEntries<'list> {}
+
+#[cfg(test)]
+mod tests {
+    use super::Pathspec;
+    use crate::PathspecFlags;
+    use std::fs::File;
+    use std::path::Path;
+
+    #[test]
+    fn smoke() {
+        let ps = Pathspec::new(["a"].iter()).unwrap();
+        assert!(ps.matches_path(Path::new("a"), PathspecFlags::DEFAULT));
+        assert!(ps.matches_path(Path::new("a/b"), PathspecFlags::DEFAULT));
+        assert!(!ps.matches_path(Path::new("b"), PathspecFlags::DEFAULT));
+        assert!(!ps.matches_path(Path::new("ab/c"), PathspecFlags::DEFAULT));
+
+        let (td, repo) = crate::test::repo_init();
+        let list = ps.match_workdir(&repo, PathspecFlags::DEFAULT).unwrap();
+        assert_eq!(list.entries().len(), 0);
+        assert_eq!(list.diff_entries().len(), 0);
+        assert_eq!(list.failed_entries().len(), 0);
+
+        File::create(&td.path().join("a")).unwrap();
+
+        let list = ps
+            .match_workdir(&repo, crate::PathspecFlags::FIND_FAILURES)
+            .unwrap();
+        assert_eq!(list.entries().len(), 1);
+        assert_eq!(list.entries().next(), Some("a".as_bytes()));
+    }
+}
diff --git a/git2/src/proxy_options.rs b/git2/src/proxy_options.rs
new file mode 100644 (file)
index 0000000..b19ba3a
--- /dev/null
@@ -0,0 +1,56 @@
+use std::ffi::CString;
+use std::marker;
+use std::ptr;
+
+use crate::raw;
+use crate::util::Binding;
+
+/// Options which can be specified to various fetch operations.
+#[derive(Default)]
+pub struct ProxyOptions<'a> {
+    url: Option<CString>,
+    proxy_kind: raw::git_proxy_t,
+    _marker: marker::PhantomData<&'a i32>,
+}
+
+impl<'a> ProxyOptions<'a> {
+    /// Creates a new set of proxy options ready to be configured.
+    pub fn new() -> ProxyOptions<'a> {
+        Default::default()
+    }
+
+    /// Try to auto-detect the proxy from the git configuration.
+    ///
+    /// Note that this will override `url` specified before.
+    pub fn auto(&mut self) -> &mut Self {
+        self.proxy_kind = raw::GIT_PROXY_AUTO;
+        self
+    }
+
+    /// Specify the exact URL of the proxy to use.
+    ///
+    /// Note that this will override `auto` specified before.
+    pub fn url(&mut self, url: &str) -> &mut Self {
+        self.proxy_kind = raw::GIT_PROXY_SPECIFIED;
+        self.url = Some(CString::new(url).unwrap());
+        self
+    }
+}
+
+impl<'a> Binding for ProxyOptions<'a> {
+    type Raw = raw::git_proxy_options;
+    unsafe fn from_raw(_raw: raw::git_proxy_options) -> ProxyOptions<'a> {
+        panic!("can't create proxy from raw options")
+    }
+
+    fn raw(&self) -> raw::git_proxy_options {
+        raw::git_proxy_options {
+            version: raw::GIT_PROXY_OPTIONS_VERSION,
+            kind: self.proxy_kind,
+            url: self.url.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()),
+            credentials: None,
+            certificate_check: None,
+            payload: ptr::null_mut(),
+        }
+    }
+}
diff --git a/git2/src/push_update.rs b/git2/src/push_update.rs
new file mode 100644 (file)
index 0000000..97bebb1
--- /dev/null
@@ -0,0 +1,55 @@
+use crate::util::Binding;
+use crate::{raw, Oid};
+use std::marker;
+use std::str;
+
+/// Represents an update which will be performed on the remote during push.
+pub struct PushUpdate<'a> {
+    raw: *const raw::git_push_update,
+    _marker: marker::PhantomData<&'a raw::git_push_update>,
+}
+
+impl<'a> Binding for PushUpdate<'a> {
+    type Raw = *const raw::git_push_update;
+    unsafe fn from_raw(raw: *const raw::git_push_update) -> PushUpdate<'a> {
+        PushUpdate {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> Self::Raw {
+        self.raw
+    }
+}
+
+impl PushUpdate<'_> {
+    /// Returns the source name of the reference as a byte slice.
+    pub fn src_refname_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).src_refname).unwrap() }
+    }
+
+    /// Returns the source name of the reference, or None if it is not valid UTF-8.
+    pub fn src_refname(&self) -> Option<&str> {
+        str::from_utf8(self.src_refname_bytes()).ok()
+    }
+
+    /// Returns the name of the reference to update on the server as a byte slice.
+    pub fn dst_refname_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).dst_refname).unwrap() }
+    }
+
+    /// Returns the name of the reference to update on the server, or None if it is not valid UTF-8.
+    pub fn dst_refname(&self) -> Option<&str> {
+        str::from_utf8(self.dst_refname_bytes()).ok()
+    }
+
+    /// Returns the current target of the reference.
+    pub fn src(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).src as *const _) }
+    }
+
+    /// Returns the new target for the reference.
+    pub fn dst(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).dst as *const _) }
+    }
+}
diff --git a/git2/src/rebase.rs b/git2/src/rebase.rs
new file mode 100644 (file)
index 0000000..2bf8fe3
--- /dev/null
@@ -0,0 +1,441 @@
+use std::ffi::CString;
+use std::{marker, mem, ptr, str};
+
+use crate::build::CheckoutBuilder;
+use crate::util::Binding;
+use crate::{raw, Error, Index, MergeOptions, Oid, Signature};
+
+/// Rebase options
+///
+/// Use to tell the rebase machinery how to operate.
+pub struct RebaseOptions<'cb> {
+    raw: raw::git_rebase_options,
+    rewrite_notes_ref: Option<CString>,
+    merge_options: Option<MergeOptions>,
+    checkout_options: Option<CheckoutBuilder<'cb>>,
+}
+
+impl<'cb> Default for RebaseOptions<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'cb> RebaseOptions<'cb> {
+    /// Creates a new default set of rebase options.
+    pub fn new() -> RebaseOptions<'cb> {
+        let mut opts = RebaseOptions {
+            raw: unsafe { mem::zeroed() },
+            rewrite_notes_ref: None,
+            merge_options: None,
+            checkout_options: None,
+        };
+        assert_eq!(unsafe { raw::git_rebase_init_options(&mut opts.raw, 1) }, 0);
+        opts
+    }
+
+    /// Used by `Repository::rebase`, this will instruct other clients working on this
+    /// rebase that you want a quiet rebase experience, which they may choose to
+    /// provide in an application-specific manner. This has no effect upon
+    /// libgit2 directly, but is provided for interoperability between Git
+    /// tools.
+    pub fn quiet(&mut self, quiet: bool) -> &mut RebaseOptions<'cb> {
+        self.raw.quiet = quiet as i32;
+        self
+    }
+
+    /// Used by `Repository::rebase`, this will begin an in-memory rebase,
+    /// which will allow callers to step through the rebase operations and
+    /// commit the rebased changes, but will not rewind HEAD or update the
+    /// repository to be in a rebasing state.  This will not interfere with
+    /// the working directory (if there is one).
+    pub fn inmemory(&mut self, inmemory: bool) -> &mut RebaseOptions<'cb> {
+        self.raw.inmemory = inmemory as i32;
+        self
+    }
+
+    /// Used by `finish()`, this is the name of the notes reference
+    /// used to rewrite notes for rebased commits when finishing the rebase;
+    /// if NULL, the contents of the configuration option `notes.rewriteRef`
+    /// is examined, unless the configuration option `notes.rewrite.rebase`
+    /// is set to false.  If `notes.rewriteRef` is also NULL, notes will
+    /// not be rewritten.
+    pub fn rewrite_notes_ref(&mut self, rewrite_notes_ref: &str) -> &mut RebaseOptions<'cb> {
+        self.rewrite_notes_ref = Some(CString::new(rewrite_notes_ref).unwrap());
+        self
+    }
+
+    /// Options to control how trees are merged during `next()`.
+    pub fn merge_options(&mut self, opts: MergeOptions) -> &mut RebaseOptions<'cb> {
+        self.merge_options = Some(opts);
+        self
+    }
+
+    /// Options to control how files are written during `Repository::rebase`,
+    /// `next()` and `abort()`. Note that a minimum strategy of
+    /// `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, and a minimum
+    /// strategy of `GIT_CHECKOUT_FORCE` is defaulted in `abort` to match git
+    /// semantics.
+    pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut RebaseOptions<'cb> {
+        self.checkout_options = Some(opts);
+        self
+    }
+
+    /// Acquire a pointer to the underlying raw options.
+    pub fn raw(&mut self) -> *const raw::git_rebase_options {
+        unsafe {
+            if let Some(opts) = self.merge_options.as_mut().take() {
+                ptr::copy_nonoverlapping(opts.raw(), &mut self.raw.merge_options, 1);
+            }
+            if let Some(opts) = self.checkout_options.as_mut() {
+                opts.configure(&mut self.raw.checkout_options);
+            }
+            self.raw.rewrite_notes_ref = self
+                .rewrite_notes_ref
+                .as_ref()
+                .map(|s| s.as_ptr())
+                .unwrap_or(ptr::null());
+        }
+        &self.raw
+    }
+}
+
+/// Representation of a rebase
+pub struct Rebase<'repo> {
+    raw: *mut raw::git_rebase,
+    _marker: marker::PhantomData<&'repo raw::git_rebase>,
+}
+
+impl<'repo> Rebase<'repo> {
+    /// Gets the count of rebase operations that are to be applied.
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_rebase_operation_entrycount(self.raw) }
+    }
+
+    /// Gets the original `HEAD` ref name for merge rebases.
+    pub fn orig_head_name(&self) -> Option<&str> {
+        let name_bytes =
+            unsafe { crate::opt_bytes(self, raw::git_rebase_orig_head_name(self.raw)) };
+        name_bytes.and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Gets the original HEAD id for merge rebases.
+    pub fn orig_head_id(&self) -> Option<Oid> {
+        unsafe { Oid::from_raw_opt(raw::git_rebase_orig_head_id(self.raw)) }
+    }
+
+    ///  Gets the rebase operation specified by the given index.
+    pub fn nth(&mut self, n: usize) -> Option<RebaseOperation<'_>> {
+        unsafe {
+            let op = raw::git_rebase_operation_byindex(self.raw, n);
+            if op.is_null() {
+                None
+            } else {
+                Some(RebaseOperation::from_raw(op))
+            }
+        }
+    }
+
+    /// Gets the index of the rebase operation that is currently being applied.
+    /// If the first operation has not yet been applied (because you have called
+    /// `init` but not yet `next`) then this returns None.
+    pub fn operation_current(&mut self) -> Option<usize> {
+        let cur = unsafe { raw::git_rebase_operation_current(self.raw) };
+        if cur == raw::GIT_REBASE_NO_OPERATION {
+            None
+        } else {
+            Some(cur)
+        }
+    }
+
+    /// Gets the index produced by the last operation, which is the result of
+    /// `next()` and which will be committed by the next invocation of
+    /// `commit()`. This is useful for resolving conflicts in an in-memory
+    /// rebase before committing them.
+    ///
+    /// This is only applicable for in-memory rebases; for rebases within a
+    /// working directory, the changes were applied to the repository's index.
+    pub fn inmemory_index(&mut self) -> Result<Index, Error> {
+        let mut idx = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_rebase_inmemory_index(&mut idx, self.raw));
+            Ok(Binding::from_raw(idx))
+        }
+    }
+
+    /// Commits the current patch.  You must have resolved any conflicts that
+    /// were introduced during the patch application from the `git_rebase_next`
+    /// invocation. To keep the author and message from the original commit leave
+    /// them as None
+    pub fn commit(
+        &mut self,
+        author: Option<&Signature<'_>>,
+        committer: &Signature<'_>,
+        message: Option<&str>,
+    ) -> Result<Oid, Error> {
+        let mut id: raw::git_oid = unsafe { mem::zeroed() };
+        let message = crate::opt_cstr(message)?;
+        unsafe {
+            try_call!(raw::git_rebase_commit(
+                &mut id,
+                self.raw,
+                author.map(|a| a.raw()),
+                committer.raw(),
+                ptr::null(),
+                message
+            ));
+            Ok(Binding::from_raw(&id as *const _))
+        }
+    }
+
+    /// Aborts a rebase that is currently in progress, resetting the repository
+    /// and working directory to their state before rebase began.
+    pub fn abort(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_rebase_abort(self.raw));
+        }
+
+        Ok(())
+    }
+
+    /// Finishes a rebase that is currently in progress once all patches have
+    /// been applied.
+    pub fn finish(&mut self, signature: Option<&Signature<'_>>) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_rebase_finish(self.raw, signature.map(|s| s.raw())));
+        }
+
+        Ok(())
+    }
+}
+
+impl<'rebase> Iterator for Rebase<'rebase> {
+    type Item = Result<RebaseOperation<'rebase>, Error>;
+
+    /// Performs the next rebase operation and returns the information about it.
+    /// If the operation is one that applies a patch (which is any operation except
+    /// GitRebaseOperation::Exec) then the patch will be applied and the index and
+    /// working directory will be updated with the changes.  If there are conflicts,
+    /// you will need to address those before committing the changes.
+    fn next(&mut self) -> Option<Result<RebaseOperation<'rebase>, Error>> {
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call_iter!(raw::git_rebase_next(&mut out, self.raw));
+            Some(Ok(RebaseOperation::from_raw(out)))
+        }
+    }
+}
+
+impl<'repo> Binding for Rebase<'repo> {
+    type Raw = *mut raw::git_rebase;
+    unsafe fn from_raw(raw: *mut raw::git_rebase) -> Rebase<'repo> {
+        Rebase {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_rebase {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Rebase<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_rebase_free(self.raw) }
+    }
+}
+
+/// A rebase operation
+///
+/// Describes a single instruction/operation to be performed during the
+/// rebase.
+#[derive(Debug, PartialEq)]
+pub enum RebaseOperationType {
+    /// The given commit is to be cherry-picked. The client should commit the
+    /// changes and continue if there are no conflicts.
+    Pick,
+
+    /// The given commit is to be cherry-picked, but the client should prompt
+    /// the user to provide an updated commit message.
+    Reword,
+
+    /// The given commit is to be cherry-picked, but the client should stop to
+    /// allow the user to edit the changes before committing them.
+    Edit,
+
+    /// The given commit is to be squashed into the previous commit. The commit
+    /// message will be merged with the previous message.
+    Squash,
+
+    /// The given commit is to be squashed into the previous commit. The commit
+    /// message from this commit will be discarded.
+    Fixup,
+
+    /// No commit will be cherry-picked. The client should run the given command
+    /// and (if successful) continue.
+    Exec,
+}
+
+impl RebaseOperationType {
+    /// Convert from the int into an enum. Returns None if invalid.
+    pub fn from_raw(raw: raw::git_rebase_operation_t) -> Option<RebaseOperationType> {
+        match raw {
+            raw::GIT_REBASE_OPERATION_PICK => Some(RebaseOperationType::Pick),
+            raw::GIT_REBASE_OPERATION_REWORD => Some(RebaseOperationType::Reword),
+            raw::GIT_REBASE_OPERATION_EDIT => Some(RebaseOperationType::Edit),
+            raw::GIT_REBASE_OPERATION_SQUASH => Some(RebaseOperationType::Squash),
+            raw::GIT_REBASE_OPERATION_FIXUP => Some(RebaseOperationType::Fixup),
+            raw::GIT_REBASE_OPERATION_EXEC => Some(RebaseOperationType::Exec),
+            _ => None,
+        }
+    }
+}
+
+/// A rebase operation
+///
+/// Describes a single instruction/operation to be performed during the
+/// rebase.
+#[derive(Debug)]
+pub struct RebaseOperation<'rebase> {
+    raw: *const raw::git_rebase_operation,
+    _marker: marker::PhantomData<Rebase<'rebase>>,
+}
+
+impl<'rebase> RebaseOperation<'rebase> {
+    /// The type of rebase operation
+    pub fn kind(&self) -> Option<RebaseOperationType> {
+        unsafe { RebaseOperationType::from_raw((*self.raw).kind) }
+    }
+
+    /// The commit ID being cherry-picked. This will be populated for all
+    /// operations except those of type `GIT_REBASE_OPERATION_EXEC`.
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).id as *const _) }
+    }
+
+    ///The executable the user has requested be run.  This will only
+    /// be populated for operations of type RebaseOperationType::Exec
+    pub fn exec(&self) -> Option<&str> {
+        unsafe { str::from_utf8(crate::opt_bytes(self, (*self.raw).exec).unwrap()).ok() }
+    }
+}
+
+impl<'rebase> Binding for RebaseOperation<'rebase> {
+    type Raw = *const raw::git_rebase_operation;
+    unsafe fn from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase> {
+        RebaseOperation {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_rebase_operation {
+        self.raw
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{RebaseOperationType, RebaseOptions, Signature};
+    use std::{fs, path};
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head_target = repo.head().unwrap().target().unwrap();
+        let tip = repo.find_commit(head_target).unwrap();
+        let sig = tip.author();
+        let tree = tip.tree().unwrap();
+
+        // We just want to see the iteration work so we can create commits with
+        // no changes
+        let c1 = repo
+            .commit(Some("refs/heads/main"), &sig, &sig, "foo", &tree, &[&tip])
+            .unwrap();
+        let c1 = repo.find_commit(c1).unwrap();
+        let c2 = repo
+            .commit(Some("refs/heads/main"), &sig, &sig, "foo", &tree, &[&c1])
+            .unwrap();
+
+        let head = repo.find_reference("refs/heads/main").unwrap();
+        let branch = repo.reference_to_annotated_commit(&head).unwrap();
+        let upstream = repo.find_annotated_commit(tip.id()).unwrap();
+        let mut rebase = repo
+            .rebase(Some(&branch), Some(&upstream), None, None)
+            .unwrap();
+
+        assert_eq!(Some("refs/heads/main"), rebase.orig_head_name());
+        assert_eq!(Some(c2), rebase.orig_head_id());
+
+        assert_eq!(rebase.len(), 2);
+        {
+            let op = rebase.next().unwrap().unwrap();
+            assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
+            assert_eq!(op.id(), c1.id());
+        }
+        {
+            let op = rebase.next().unwrap().unwrap();
+            assert_eq!(op.kind(), Some(RebaseOperationType::Pick));
+            assert_eq!(op.id(), c2);
+        }
+        {
+            let op = rebase.next();
+            assert!(op.is_none());
+        }
+    }
+
+    #[test]
+    fn keeping_original_author_msg() {
+        let (td, repo) = crate::test::repo_init();
+        let head_target = repo.head().unwrap().target().unwrap();
+        let tip = repo.find_commit(head_target).unwrap();
+        let sig = Signature::now("testname", "testemail").unwrap();
+        let mut index = repo.index().unwrap();
+
+        fs::File::create(td.path().join("file_a")).unwrap();
+        index.add_path(path::Path::new("file_a")).unwrap();
+        index.write().unwrap();
+        let tree_id_a = index.write_tree().unwrap();
+        let tree_a = repo.find_tree(tree_id_a).unwrap();
+        let c1 = repo
+            .commit(Some("refs/heads/main"), &sig, &sig, "A", &tree_a, &[&tip])
+            .unwrap();
+        let c1 = repo.find_commit(c1).unwrap();
+
+        fs::File::create(td.path().join("file_b")).unwrap();
+        index.add_path(path::Path::new("file_b")).unwrap();
+        index.write().unwrap();
+        let tree_id_b = index.write_tree().unwrap();
+        let tree_b = repo.find_tree(tree_id_b).unwrap();
+        let c2 = repo
+            .commit(Some("refs/heads/main"), &sig, &sig, "B", &tree_b, &[&c1])
+            .unwrap();
+
+        let branch = repo.find_annotated_commit(c2).unwrap();
+        let upstream = repo.find_annotated_commit(tip.id()).unwrap();
+        let mut opts: RebaseOptions<'_> = Default::default();
+        let mut rebase = repo
+            .rebase(Some(&branch), Some(&upstream), None, Some(&mut opts))
+            .unwrap();
+
+        assert_eq!(rebase.len(), 2);
+
+        {
+            rebase.next().unwrap().unwrap();
+            let id = rebase.commit(None, &sig, None).unwrap();
+            let commit = repo.find_commit(id).unwrap();
+            assert_eq!(commit.message(), Some("A"));
+            assert_eq!(commit.author().name(), Some("testname"));
+            assert_eq!(commit.author().email(), Some("testemail"));
+        }
+
+        {
+            rebase.next().unwrap().unwrap();
+            let id = rebase.commit(None, &sig, None).unwrap();
+            let commit = repo.find_commit(id).unwrap();
+            assert_eq!(commit.message(), Some("B"));
+            assert_eq!(commit.author().name(), Some("testname"));
+            assert_eq!(commit.author().email(), Some("testemail"));
+        }
+        rebase.finish(None).unwrap();
+    }
+}
diff --git a/git2/src/reference.rs b/git2/src/reference.rs
new file mode 100644 (file)
index 0000000..0af845d
--- /dev/null
@@ -0,0 +1,592 @@
+use std::cmp::Ordering;
+use std::ffi::CString;
+use std::marker;
+use std::mem;
+use std::ptr;
+use std::str;
+
+use crate::object::CastOrPanic;
+use crate::util::{c_cmp_to_ordering, Binding};
+use crate::{
+    call, raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceFormat, ReferenceType,
+    Repository, Tag, Tree,
+};
+
+// Not in the public header files (yet?), but a hard limit used by libgit2
+// internally
+const GIT_REFNAME_MAX: usize = 1024;
+
+/// This is used to logically indicate that a [`raw::git_reference`] or
+/// [`raw::git_reference_iterator`] holds a reference to [`raw::git_refdb`].
+/// It is not necessary to have a wrapper like this in the
+/// [`marker::PhantomData`], since all that matters is that it is tied to the
+/// lifetime of the [`Repository`], but this helps distinguish the actual
+/// references involved.
+struct Refdb<'repo>(#[allow(dead_code)] &'repo Repository);
+
+/// A structure to represent a git [reference][1].
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-References
+pub struct Reference<'repo> {
+    raw: *mut raw::git_reference,
+    _marker: marker::PhantomData<Refdb<'repo>>,
+}
+
+/// An iterator over the references in a repository.
+pub struct References<'repo> {
+    raw: *mut raw::git_reference_iterator,
+    _marker: marker::PhantomData<Refdb<'repo>>,
+}
+
+/// An iterator over the names of references in a repository.
+pub struct ReferenceNames<'repo, 'references> {
+    inner: &'references mut References<'repo>,
+}
+
+impl<'repo> Reference<'repo> {
+    /// Ensure the reference name is well-formed.
+    ///
+    /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`]
+    /// was given to [`Reference::normalize_name`]. No normalization is
+    /// performed, however.
+    ///
+    /// ```rust
+    /// use git2::Reference;
+    ///
+    /// assert!(Reference::is_valid_name("HEAD"));
+    /// assert!(Reference::is_valid_name("refs/heads/main"));
+    ///
+    /// // But:
+    /// assert!(!Reference::is_valid_name("main"));
+    /// assert!(!Reference::is_valid_name("refs/heads/*"));
+    /// assert!(!Reference::is_valid_name("foo//bar"));
+    /// ```
+    ///
+    /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
+    ///     struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
+    /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name
+    pub fn is_valid_name(refname: &str) -> bool {
+        crate::init();
+        let refname = CString::new(refname).unwrap();
+        let mut valid: libc::c_int = 0;
+        unsafe {
+            call::c_try(raw::git_reference_name_is_valid(
+                &mut valid,
+                refname.as_ptr(),
+            ))
+            .unwrap();
+        }
+        valid == 1
+    }
+
+    /// Normalize reference name and check validity.
+    ///
+    /// This will normalize the reference name by collapsing runs of adjacent
+    /// slashes between name components into a single slash. It also validates
+    /// the name according to the following rules:
+    ///
+    /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may
+    ///    contain only capital letters and underscores, and must begin and end
+    ///    with a letter. (e.g. "HEAD", "ORIG_HEAD").
+    /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect
+    ///    only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If
+    ///    it is given, "shorthand" branch names (i.e. those not prefixed by
+    ///    `refs/`, but consisting of a single word without `/` separators)
+    ///    become valid. For example, "main" would be accepted.
+    /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may
+    ///    contain a single `*` in place of a full pathname component (e.g.
+    ///    `foo/*/bar`, `foo/bar*`).
+    /// 4. Names prefixed with "refs/" can be almost anything. You must avoid
+    ///    the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+    ///    sequences ".." and "@{" which have special meaning to revparse.
+    ///
+    /// If the reference passes validation, it is returned in normalized form,
+    /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned.
+    ///
+    /// ```rust
+    /// use git2::{Reference, ReferenceFormat};
+    ///
+    /// assert_eq!(
+    ///     Reference::normalize_name(
+    ///         "foo//bar",
+    ///         ReferenceFormat::NORMAL
+    ///     )
+    ///     .unwrap(),
+    ///     "foo/bar".to_owned()
+    /// );
+    ///
+    /// assert_eq!(
+    ///     Reference::normalize_name(
+    ///         "HEAD",
+    ///         ReferenceFormat::ALLOW_ONELEVEL
+    ///     )
+    ///     .unwrap(),
+    ///     "HEAD".to_owned()
+    /// );
+    ///
+    /// assert_eq!(
+    ///     Reference::normalize_name(
+    ///         "refs/heads/*",
+    ///         ReferenceFormat::REFSPEC_PATTERN
+    ///     )
+    ///     .unwrap(),
+    ///     "refs/heads/*".to_owned()
+    /// );
+    ///
+    /// assert_eq!(
+    ///     Reference::normalize_name(
+    ///         "main",
+    ///         ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND
+    ///     )
+    ///     .unwrap(),
+    ///     "main".to_owned()
+    /// );
+    /// ```
+    ///
+    /// [`ReferenceFormat::ALLOW_ONELEVEL`]:
+    ///     struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL
+    /// [`ReferenceFormat::REFSPEC_SHORTHAND`]:
+    ///     struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND
+    /// [`ReferenceFormat::REFSPEC_PATTERN`]:
+    ///     struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN
+    /// [`Error`]: struct.Error
+    /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec
+    pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> {
+        crate::init();
+        let mut dst = [0u8; GIT_REFNAME_MAX];
+        let refname = CString::new(refname)?;
+        unsafe {
+            try_call!(raw::git_reference_normalize_name(
+                dst.as_mut_ptr() as *mut libc::c_char,
+                dst.len() as libc::size_t,
+                refname,
+                flags.bits()
+            ));
+            let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()];
+            Ok(str::from_utf8(s).unwrap().to_owned())
+        }
+    }
+
+    /// Get access to the underlying raw pointer.
+    pub fn raw(&self) -> *mut raw::git_reference {
+        self.raw
+    }
+
+    /// Delete an existing reference.
+    ///
+    /// This method works for both direct and symbolic references. The reference
+    /// will be immediately removed on disk.
+    ///
+    /// This function will return an error if the reference has changed from the
+    /// time it was looked up.
+    pub fn delete(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_reference_delete(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Check if a reference is a local branch.
+    pub fn is_branch(&self) -> bool {
+        unsafe { raw::git_reference_is_branch(&*self.raw) == 1 }
+    }
+
+    /// Check if a reference is a note.
+    pub fn is_note(&self) -> bool {
+        unsafe { raw::git_reference_is_note(&*self.raw) == 1 }
+    }
+
+    /// Check if a reference is a remote tracking branch
+    pub fn is_remote(&self) -> bool {
+        unsafe { raw::git_reference_is_remote(&*self.raw) == 1 }
+    }
+
+    /// Check if a reference is a tag
+    pub fn is_tag(&self) -> bool {
+        unsafe { raw::git_reference_is_tag(&*self.raw) == 1 }
+    }
+
+    /// Get the reference type of a reference.
+    ///
+    /// If the type is unknown, then `None` is returned.
+    pub fn kind(&self) -> Option<ReferenceType> {
+        ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) })
+    }
+
+    /// Get the full name of a reference.
+    ///
+    /// Returns `None` if the name is not valid utf-8.
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Get the full name of a reference.
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() }
+    }
+
+    /// Get the full shorthand of a reference.
+    ///
+    /// This will transform the reference name into a name "human-readable"
+    /// version. If no shortname is appropriate, it will return the full name.
+    ///
+    /// Returns `None` if the shorthand is not valid utf-8.
+    pub fn shorthand(&self) -> Option<&str> {
+        str::from_utf8(self.shorthand_bytes()).ok()
+    }
+
+    /// Get the full shorthand of a reference.
+    pub fn shorthand_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() }
+    }
+
+    /// Get the OID pointed to by a direct reference.
+    ///
+    /// Only available if the reference is direct (i.e. an object id reference,
+    /// not a symbolic one).
+    pub fn target(&self) -> Option<Oid> {
+        unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) }
+    }
+
+    /// Return the peeled OID target of this reference.
+    ///
+    /// This peeled OID only applies to direct references that point to a hard
+    /// Tag object: it is the result of peeling such Tag.
+    pub fn target_peel(&self) -> Option<Oid> {
+        unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) }
+    }
+
+    /// Get full name to the reference pointed to by a symbolic reference.
+    ///
+    /// May return `None` if the reference is either not symbolic or not a
+    /// valid utf-8 string.
+    pub fn symbolic_target(&self) -> Option<&str> {
+        self.symbolic_target_bytes()
+            .and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get full name to the reference pointed to by a symbolic reference.
+    ///
+    /// Only available if the reference is symbolic.
+    pub fn symbolic_target_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) }
+    }
+
+    /// Resolve a symbolic reference to a direct reference.
+    ///
+    /// This method iteratively peels a symbolic reference until it resolves to
+    /// a direct reference to an OID.
+    ///
+    /// If a direct reference is passed as an argument, a copy of that
+    /// reference is returned.
+    pub fn resolve(&self) -> Result<Reference<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_resolve(&mut raw, &*self.raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Peel a reference to an object
+    ///
+    /// This method recursively peels the reference until it reaches
+    /// an object of the specified type.
+    pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_peel(&mut raw, self.raw, kind));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Peel a reference to a blob
+    ///
+    /// This method recursively peels the reference until it reaches
+    /// a blob.
+    pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> {
+        Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob))
+    }
+
+    /// Peel a reference to a commit
+    ///
+    /// This method recursively peels the reference until it reaches
+    /// a commit.
+    pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> {
+        Ok(self
+            .peel(ObjectType::Commit)?
+            .cast_or_panic(ObjectType::Commit))
+    }
+
+    /// Peel a reference to a tree
+    ///
+    /// This method recursively peels the reference until it reaches
+    /// a tree.
+    pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> {
+        Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree))
+    }
+
+    /// Peel a reference to a tag
+    ///
+    /// This method recursively peels the reference until it reaches
+    /// a tag.
+    pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> {
+        Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag))
+    }
+
+    /// Rename an existing reference.
+    ///
+    /// This method works for both direct and symbolic references.
+    ///
+    /// If the force flag is not enabled, and there's already a reference with
+    /// the given name, the renaming will fail.
+    pub fn rename(
+        &mut self,
+        new_name: &str,
+        force: bool,
+        msg: &str,
+    ) -> Result<Reference<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        let new_name = CString::new(new_name)?;
+        let msg = CString::new(msg)?;
+        unsafe {
+            try_call!(raw::git_reference_rename(
+                &mut raw, self.raw, new_name, force, msg
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Conditionally create a new reference with the same name as the given
+    /// reference but a different OID target. The reference must be a direct
+    /// reference, otherwise this will fail.
+    ///
+    /// The new reference will be written to disk, overwriting the given
+    /// reference.
+    pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        let msg = CString::new(reflog_msg)?;
+        unsafe {
+            try_call!(raw::git_reference_set_target(
+                &mut raw,
+                self.raw,
+                id.raw(),
+                msg
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new reference with the same name as the given reference but a
+    /// different symbolic target. The reference must be a symbolic reference,
+    /// otherwise this will fail.
+    ///
+    /// The new reference will be written to disk, overwriting the given
+    /// reference.
+    ///
+    /// The target name will be checked for validity. See
+    /// [`Repository::reference_symbolic`] for rules about valid names.
+    ///
+    /// The message for the reflog will be ignored if the reference does not
+    /// belong in the standard set (HEAD, branches and remote-tracking
+    /// branches) and it does not have a reflog.
+    pub fn symbolic_set_target(
+        &mut self,
+        target: &str,
+        reflog_msg: &str,
+    ) -> Result<Reference<'repo>, Error> {
+        let mut raw = ptr::null_mut();
+        let target = CString::new(target)?;
+        let msg = CString::new(reflog_msg)?;
+        unsafe {
+            try_call!(raw::git_reference_symbolic_set_target(
+                &mut raw, self.raw, target, msg
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+}
+
+impl<'repo> PartialOrd for Reference<'repo> {
+    fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl<'repo> Ord for Reference<'repo> {
+    fn cmp(&self, other: &Reference<'repo>) -> Ordering {
+        c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) })
+    }
+}
+
+impl<'repo> PartialEq for Reference<'repo> {
+    fn eq(&self, other: &Reference<'repo>) -> bool {
+        self.cmp(other) == Ordering::Equal
+    }
+}
+
+impl<'repo> Eq for Reference<'repo> {}
+
+impl<'repo> Binding for Reference<'repo> {
+    type Raw = *mut raw::git_reference;
+    unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> {
+        Reference {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_reference {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Reference<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_reference_free(self.raw) }
+    }
+}
+
+impl<'repo> References<'repo> {
+    /// Consumes a `References` iterator to create an iterator over just the
+    /// name of some references.
+    ///
+    /// This is more efficient if only the names are desired of references as
+    /// the references themselves don't have to be allocated and deallocated.
+    ///
+    /// The returned iterator will yield strings as opposed to a `Reference`.
+    pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> {
+        ReferenceNames { inner: self }
+    }
+}
+
+impl<'repo> Binding for References<'repo> {
+    type Raw = *mut raw::git_reference_iterator;
+    unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> {
+        References {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_reference_iterator {
+        self.raw
+    }
+}
+
+impl<'repo> Iterator for References<'repo> {
+    type Item = Result<Reference<'repo>, Error>;
+    fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> {
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call_iter!(raw::git_reference_next(&mut out, self.raw));
+            Some(Ok(Binding::from_raw(out)))
+        }
+    }
+}
+
+impl<'repo> Drop for References<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_reference_iterator_free(self.raw) }
+    }
+}
+
+impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> {
+    type Item = Result<&'references str, Error>;
+    fn next(&mut self) -> Option<Result<&'references str, Error>> {
+        let mut out = ptr::null();
+        unsafe {
+            try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw));
+            let bytes = crate::opt_bytes(self, out).unwrap();
+            let s = str::from_utf8(bytes).unwrap();
+            Some(Ok(mem::transmute::<&str, &'references str>(s)))
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{ObjectType, Reference, ReferenceType};
+
+    #[test]
+    fn is_valid_name() {
+        assert!(Reference::is_valid_name("refs/foo"));
+        assert!(!Reference::is_valid_name("foo"));
+        assert!(Reference::is_valid_name("FOO_BAR"));
+
+        assert!(!Reference::is_valid_name("foo"));
+        assert!(!Reference::is_valid_name("_FOO_BAR"));
+    }
+
+    #[test]
+    #[should_panic]
+    fn is_valid_name_for_invalid_ref() {
+        Reference::is_valid_name("ab\012");
+    }
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut head = repo.head().unwrap();
+        assert!(head.is_branch());
+        assert!(!head.is_remote());
+        assert!(!head.is_tag());
+        assert!(!head.is_note());
+
+        // HEAD is a symbolic reference but git_repository_head resolves it
+        // so it is a GIT_REFERENCE_DIRECT.
+        assert_eq!(head.kind().unwrap(), ReferenceType::Direct);
+
+        assert!(head == repo.head().unwrap());
+        assert_eq!(head.name(), Some("refs/heads/main"));
+
+        assert!(head == repo.find_reference("refs/heads/main").unwrap());
+        assert_eq!(
+            repo.refname_to_id("refs/heads/main").unwrap(),
+            head.target().unwrap()
+        );
+
+        assert!(head.symbolic_target().is_none());
+        assert!(head.target_peel().is_none());
+
+        assert_eq!(head.shorthand(), Some("main"));
+        assert!(head.resolve().unwrap() == head);
+
+        let mut tag1 = repo
+            .reference("refs/tags/tag1", head.target().unwrap(), false, "test")
+            .unwrap();
+        assert!(tag1.is_tag());
+        assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct);
+
+        let peeled_commit = tag1.peel(ObjectType::Commit).unwrap();
+        assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap());
+        assert_eq!(tag1.target().unwrap(), peeled_commit.id());
+
+        tag1.delete().unwrap();
+
+        let mut sym1 = repo
+            .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test")
+            .unwrap();
+        assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic);
+        let mut sym2 = repo
+            .reference_symbolic("refs/tags/tag2", "refs/heads/main", false, "test")
+            .unwrap()
+            .symbolic_set_target("refs/tags/tag1", "test")
+            .unwrap();
+        assert_eq!(sym2.kind().unwrap(), ReferenceType::Symbolic);
+        assert_eq!(sym2.symbolic_target().unwrap(), "refs/tags/tag1");
+        sym2.delete().unwrap();
+        sym1.delete().unwrap();
+
+        {
+            assert!(repo.references().unwrap().count() == 1);
+            assert!(repo.references().unwrap().next().unwrap().unwrap() == head);
+            let mut names = repo.references().unwrap();
+            let mut names = names.names();
+            assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main");
+            assert!(names.next().is_none());
+            assert!(repo.references_glob("foo").unwrap().count() == 0);
+            assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1);
+        }
+
+        let mut head = head.rename("refs/foo", true, "test").unwrap();
+        head.delete().unwrap();
+    }
+}
diff --git a/git2/src/reflog.rs b/git2/src/reflog.rs
new file mode 100644 (file)
index 0000000..bbd2140
--- /dev/null
@@ -0,0 +1,196 @@
+use libc::size_t;
+use std::iter::FusedIterator;
+use std::marker;
+use std::ops::Range;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, signature, Error, Oid, Signature};
+
+/// A reference log of a git repository.
+pub struct Reflog {
+    raw: *mut raw::git_reflog,
+}
+
+/// An entry inside the reflog of a repository
+pub struct ReflogEntry<'reflog> {
+    raw: *const raw::git_reflog_entry,
+    _marker: marker::PhantomData<&'reflog Reflog>,
+}
+
+/// An iterator over the entries inside of a reflog.
+pub struct ReflogIter<'reflog> {
+    range: Range<usize>,
+    reflog: &'reflog Reflog,
+}
+
+impl Reflog {
+    /// Add a new entry to the in-memory reflog.
+    pub fn append(
+        &mut self,
+        new_oid: Oid,
+        committer: &Signature<'_>,
+        msg: Option<&str>,
+    ) -> Result<(), Error> {
+        let msg = crate::opt_cstr(msg)?;
+        unsafe {
+            try_call!(raw::git_reflog_append(
+                self.raw,
+                new_oid.raw(),
+                committer.raw(),
+                msg
+            ));
+        }
+        Ok(())
+    }
+
+    /// Remove an entry from the reflog by its index
+    ///
+    /// To ensure there's no gap in the log history, set rewrite_previous_entry
+    /// param value to `true`. When deleting entry n, member old_oid of entry
+    /// n-1 (if any) will be updated with the value of member new_oid of entry
+    /// n+1.
+    pub fn remove(&mut self, i: usize, rewrite_previous_entry: bool) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_reflog_drop(
+                self.raw,
+                i as size_t,
+                rewrite_previous_entry
+            ));
+        }
+        Ok(())
+    }
+
+    /// Lookup an entry by its index
+    ///
+    /// Requesting the reflog entry with an index of 0 (zero) will return the
+    /// most recently created entry.
+    pub fn get(&self, i: usize) -> Option<ReflogEntry<'_>> {
+        unsafe {
+            let ptr = raw::git_reflog_entry_byindex(self.raw, i as size_t);
+            Binding::from_raw_opt(ptr)
+        }
+    }
+
+    /// Get the number of log entries in a reflog
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_reflog_entrycount(self.raw) as usize }
+    }
+
+    /// Return `true ` is there is no log entry in a reflog
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Get an iterator to all entries inside of this reflog
+    pub fn iter(&self) -> ReflogIter<'_> {
+        ReflogIter {
+            range: 0..self.len(),
+            reflog: self,
+        }
+    }
+
+    /// Write an existing in-memory reflog object back to disk using an atomic
+    /// file lock.
+    pub fn write(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_reflog_write(self.raw));
+        }
+        Ok(())
+    }
+}
+
+impl Binding for Reflog {
+    type Raw = *mut raw::git_reflog;
+
+    unsafe fn from_raw(raw: *mut raw::git_reflog) -> Reflog {
+        Reflog { raw }
+    }
+    fn raw(&self) -> *mut raw::git_reflog {
+        self.raw
+    }
+}
+
+impl Drop for Reflog {
+    fn drop(&mut self) {
+        unsafe { raw::git_reflog_free(self.raw) }
+    }
+}
+
+impl<'reflog> ReflogEntry<'reflog> {
+    /// Get the committer of this entry
+    pub fn committer(&self) -> Signature<'_> {
+        unsafe {
+            let ptr = raw::git_reflog_entry_committer(self.raw);
+            signature::from_raw_const(self, ptr)
+        }
+    }
+
+    /// Get the new oid
+    pub fn id_new(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_reflog_entry_id_new(self.raw)) }
+    }
+
+    /// Get the old oid
+    pub fn id_old(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_reflog_entry_id_old(self.raw)) }
+    }
+
+    /// Get the log message, returning `None` on invalid UTF-8.
+    pub fn message(&self) -> Option<&str> {
+        self.message_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the log message as a byte array.
+    pub fn message_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_reflog_entry_message(self.raw)) }
+    }
+}
+
+impl<'reflog> Binding for ReflogEntry<'reflog> {
+    type Raw = *const raw::git_reflog_entry;
+
+    unsafe fn from_raw(raw: *const raw::git_reflog_entry) -> ReflogEntry<'reflog> {
+        ReflogEntry {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_reflog_entry {
+        self.raw
+    }
+}
+
+impl<'reflog> Iterator for ReflogIter<'reflog> {
+    type Item = ReflogEntry<'reflog>;
+    fn next(&mut self) -> Option<ReflogEntry<'reflog>> {
+        self.range.next().and_then(|i| self.reflog.get(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'reflog> DoubleEndedIterator for ReflogIter<'reflog> {
+    fn next_back(&mut self) -> Option<ReflogEntry<'reflog>> {
+        self.range.next_back().and_then(|i| self.reflog.get(i))
+    }
+}
+impl<'reflog> FusedIterator for ReflogIter<'reflog> {}
+impl<'reflog> ExactSizeIterator for ReflogIter<'reflog> {}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let mut reflog = repo.reflog("HEAD").unwrap();
+        assert_eq!(reflog.iter().len(), 1);
+        reflog.write().unwrap();
+
+        let entry = reflog.iter().next().unwrap();
+        assert!(entry.message().is_some());
+
+        repo.reflog_rename("HEAD", "refs/heads/foo").unwrap();
+        repo.reflog_delete("refs/heads/foo").unwrap();
+    }
+}
diff --git a/git2/src/refspec.rs b/git2/src/refspec.rs
new file mode 100644 (file)
index 0000000..3f62e99
--- /dev/null
@@ -0,0 +1,122 @@
+use std::ffi::CString;
+use std::marker;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, Buf, Direction, Error};
+
+/// A structure to represent a git [refspec][1].
+///
+/// Refspecs are currently mainly accessed/created through a `Remote`.
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-The-Refspec
+pub struct Refspec<'remote> {
+    raw: *const raw::git_refspec,
+    _marker: marker::PhantomData<&'remote raw::git_remote>,
+}
+
+impl<'remote> Refspec<'remote> {
+    /// Get the refspec's direction.
+    pub fn direction(&self) -> Direction {
+        match unsafe { raw::git_refspec_direction(self.raw) } {
+            raw::GIT_DIRECTION_FETCH => Direction::Fetch,
+            raw::GIT_DIRECTION_PUSH => Direction::Push,
+            n => panic!("unknown refspec direction: {}", n),
+        }
+    }
+
+    /// Get the destination specifier.
+    ///
+    /// If the destination is not utf-8, None is returned.
+    pub fn dst(&self) -> Option<&str> {
+        str::from_utf8(self.dst_bytes()).ok()
+    }
+
+    /// Get the destination specifier, in bytes.
+    pub fn dst_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_refspec_dst(self.raw)).unwrap() }
+    }
+
+    /// Check if a refspec's destination descriptor matches a reference
+    pub fn dst_matches(&self, refname: &str) -> bool {
+        let refname = CString::new(refname).unwrap();
+        unsafe { raw::git_refspec_dst_matches(self.raw, refname.as_ptr()) == 1 }
+    }
+
+    /// Get the source specifier.
+    ///
+    /// If the source is not utf-8, None is returned.
+    pub fn src(&self) -> Option<&str> {
+        str::from_utf8(self.src_bytes()).ok()
+    }
+
+    /// Get the source specifier, in bytes.
+    pub fn src_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_refspec_src(self.raw)).unwrap() }
+    }
+
+    /// Check if a refspec's source descriptor matches a reference
+    pub fn src_matches(&self, refname: &str) -> bool {
+        let refname = CString::new(refname).unwrap();
+        unsafe { raw::git_refspec_src_matches(self.raw, refname.as_ptr()) == 1 }
+    }
+
+    /// Get the force update setting.
+    pub fn is_force(&self) -> bool {
+        unsafe { raw::git_refspec_force(self.raw) == 1 }
+    }
+
+    /// Get the refspec's string.
+    ///
+    /// Returns None if the string is not valid utf8.
+    pub fn str(&self) -> Option<&str> {
+        str::from_utf8(self.bytes()).ok()
+    }
+
+    /// Get the refspec's string as a byte array
+    pub fn bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_refspec_string(self.raw)).unwrap() }
+    }
+
+    /// Transform a reference to its target following the refspec's rules
+    pub fn transform(&self, name: &str) -> Result<Buf, Error> {
+        let name = CString::new(name).unwrap();
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_refspec_transform(
+                buf.raw(),
+                self.raw,
+                name.as_ptr()
+            ));
+            Ok(buf)
+        }
+    }
+
+    /// Transform a target reference to its source reference following the refspec's rules
+    pub fn rtransform(&self, name: &str) -> Result<Buf, Error> {
+        let name = CString::new(name).unwrap();
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_refspec_rtransform(
+                buf.raw(),
+                self.raw,
+                name.as_ptr()
+            ));
+            Ok(buf)
+        }
+    }
+}
+
+impl<'remote> Binding for Refspec<'remote> {
+    type Raw = *const raw::git_refspec;
+
+    unsafe fn from_raw(raw: *const raw::git_refspec) -> Refspec<'remote> {
+        Refspec {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_refspec {
+        self.raw
+    }
+}
diff --git a/git2/src/remote.rs b/git2/src/remote.rs
new file mode 100644 (file)
index 0000000..13c275a
--- /dev/null
@@ -0,0 +1,1168 @@
+use raw::git_strarray;
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::os::raw::c_uint;
+use std::ptr;
+use std::slice;
+use std::str;
+use std::{ffi::CString, os::raw::c_char};
+
+use crate::string_array::StringArray;
+use crate::util::Binding;
+use crate::{call, raw, Buf, Direction, Error, FetchPrune, Oid, ProxyOptions, Refspec};
+use crate::{AutotagOption, Progress, RemoteCallbacks, RemoteUpdateFlags, Repository};
+
+/// A structure representing a [remote][1] of a git repository.
+///
+/// [1]: http://git-scm.com/book/en/Git-Basics-Working-with-Remotes
+///
+/// The lifetime is the lifetime of the repository that it is attached to. The
+/// remote is used to manage fetches and pushes as well as refspecs.
+pub struct Remote<'repo> {
+    raw: *mut raw::git_remote,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// An iterator over the refspecs that a remote contains.
+pub struct Refspecs<'remote> {
+    range: Range<usize>,
+    remote: &'remote Remote<'remote>,
+}
+
+/// Description of a reference advertised by a remote server, given out on calls
+/// to `list`.
+pub struct RemoteHead<'remote> {
+    raw: *const raw::git_remote_head,
+    _marker: marker::PhantomData<&'remote str>,
+}
+
+/// Options which can be specified to various fetch operations.
+pub struct FetchOptions<'cb> {
+    callbacks: Option<RemoteCallbacks<'cb>>,
+    depth: i32,
+    proxy: Option<ProxyOptions<'cb>>,
+    prune: FetchPrune,
+    update_flags: RemoteUpdateFlags,
+    download_tags: AutotagOption,
+    follow_redirects: RemoteRedirect,
+    custom_headers: Vec<CString>,
+    custom_headers_ptrs: Vec<*const c_char>,
+}
+
+/// Options to control the behavior of a git push.
+pub struct PushOptions<'cb> {
+    callbacks: Option<RemoteCallbacks<'cb>>,
+    proxy: Option<ProxyOptions<'cb>>,
+    pb_parallelism: u32,
+    follow_redirects: RemoteRedirect,
+    custom_headers: Vec<CString>,
+    custom_headers_ptrs: Vec<*const c_char>,
+    remote_push_options: Vec<CString>,
+    remote_push_options_ptrs: Vec<*const c_char>,
+}
+
+/// Holds callbacks for a connection to a `Remote`. Disconnects when dropped
+pub struct RemoteConnection<'repo, 'connection, 'cb> {
+    _callbacks: Box<RemoteCallbacks<'cb>>,
+    _proxy: ProxyOptions<'cb>,
+    remote: &'connection mut Remote<'repo>,
+}
+
+/// Remote redirection settings; whether redirects to another host are
+/// permitted.
+///
+/// By default, git will follow a redirect on the initial request
+/// (`/info/refs`), but not subsequent requests.
+pub enum RemoteRedirect {
+    /// Do not follow any off-site redirects at any stage of the fetch or push.
+    None,
+    /// Allow off-site redirects only upon the initial request. This is the
+    /// default.
+    Initial,
+    /// Allow redirects at any stage in the fetch or push.
+    All,
+}
+
+pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote {
+    let ret = remote.raw;
+    mem::forget(remote);
+    ret
+}
+
+impl<'repo> Remote<'repo> {
+    /// Ensure the remote name is well-formed.
+    pub fn is_valid_name(remote_name: &str) -> bool {
+        crate::init();
+        let remote_name = CString::new(remote_name).unwrap();
+        let mut valid: libc::c_int = 0;
+        unsafe {
+            call::c_try(raw::git_remote_name_is_valid(
+                &mut valid,
+                remote_name.as_ptr(),
+            ))
+            .unwrap();
+        }
+        valid == 1
+    }
+
+    /// Create a detached remote
+    ///
+    /// Create a remote with the given URL in-memory. You can use this
+    /// when you have a URL instead of a remote's name.
+    /// Contrasted with an anonymous remote, a detached remote will not
+    /// consider any repo configuration values.
+    pub fn create_detached<S: Into<Vec<u8>>>(url: S) -> Result<Remote<'repo>, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let url = CString::new(url)?;
+        unsafe {
+            try_call!(raw::git_remote_create_detached(&mut ret, url));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the remote's name.
+    ///
+    /// Returns `None` if this remote has not yet been named or if the name is
+    /// not valid utf-8
+    pub fn name(&self) -> Option<&str> {
+        self.name_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the remote's name, in bytes.
+    ///
+    /// Returns `None` if this remote has not yet been named
+    pub fn name_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
+    }
+
+    /// Get the remote's URL.
+    ///
+    /// Returns `None` if the URL is not valid utf-8
+    pub fn url(&self) -> Option<&str> {
+        str::from_utf8(self.url_bytes()).ok()
+    }
+
+    /// Get the remote's URL as a byte array.
+    pub fn url_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
+    }
+
+    /// Get the remote's pushurl.
+    ///
+    /// Returns `None` if the pushurl is not valid utf-8
+    pub fn pushurl(&self) -> Option<&str> {
+        self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the remote's pushurl as a byte array.
+    pub fn pushurl_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
+    }
+
+    /// Get the remote's default branch.
+    ///
+    /// The remote (or more exactly its transport) must have connected to the
+    /// remote repository. This default branch is available as soon as the
+    /// connection to the remote is initiated and it remains available after
+    /// disconnecting.
+    pub fn default_branch(&self) -> Result<Buf, Error> {
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_remote_default_branch(buf.raw(), self.raw));
+            Ok(buf)
+        }
+    }
+
+    /// Open a connection to a remote.
+    pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
+        // TODO: can callbacks be exposed safely?
+        unsafe {
+            try_call!(raw::git_remote_connect(
+                self.raw,
+                dir,
+                ptr::null(),
+                ptr::null(),
+                ptr::null()
+            ));
+        }
+        Ok(())
+    }
+
+    /// Open a connection to a remote with callbacks and proxy settings
+    ///
+    /// Returns a `RemoteConnection` that will disconnect once dropped
+    pub fn connect_auth<'connection, 'cb>(
+        &'connection mut self,
+        dir: Direction,
+        cb: Option<RemoteCallbacks<'cb>>,
+        proxy_options: Option<ProxyOptions<'cb>>,
+    ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> {
+        let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new));
+        let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new);
+        unsafe {
+            try_call!(raw::git_remote_connect(
+                self.raw,
+                dir,
+                &cb.raw(),
+                &proxy_options.raw(),
+                ptr::null()
+            ));
+        }
+
+        Ok(RemoteConnection {
+            _callbacks: cb,
+            _proxy: proxy_options,
+            remote: self,
+        })
+    }
+
+    /// Check whether the remote is connected
+    pub fn connected(&mut self) -> bool {
+        unsafe { raw::git_remote_connected(self.raw) == 1 }
+    }
+
+    /// Disconnect from the remote
+    pub fn disconnect(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_remote_disconnect(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Download and index the packfile
+    ///
+    /// Connect to the remote if it hasn't been done yet, negotiate with the
+    /// remote git which objects are missing, download and index the packfile.
+    ///
+    /// The .idx file will be created and both it and the packfile with be
+    /// renamed to their final name.
+    ///
+    /// The `specs` argument is a list of refspecs to use for this negotiation
+    /// and download. Use an empty array to use the base refspecs.
+    pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>(
+        &mut self,
+        specs: &[Str],
+        opts: Option<&mut FetchOptions<'_>>,
+    ) -> Result<(), Error> {
+        let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?;
+        let raw = opts.map(|o| o.raw());
+        unsafe {
+            try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref()));
+        }
+        Ok(())
+    }
+
+    /// Cancel the operation
+    ///
+    /// At certain points in its operation, the network code checks whether the
+    /// operation has been canceled and if so stops the operation.
+    pub fn stop(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_remote_stop(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Get the number of refspecs for a remote
+    pub fn refspecs(&self) -> Refspecs<'_> {
+        let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize };
+        Refspecs {
+            range: 0..cnt,
+            remote: self,
+        }
+    }
+
+    /// Get the `nth` refspec from this remote.
+    ///
+    /// The `refspecs` iterator can be used to iterate over all refspecs.
+    pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> {
+        unsafe {
+            let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t);
+            Binding::from_raw_opt(ptr)
+        }
+    }
+
+    /// Download new data and update tips
+    ///
+    /// Convenience function to connect to a remote, download the data,
+    /// disconnect and update the remote-tracking branches.
+    ///
+    /// # Examples
+    ///
+    /// Example of functionality similar to `git fetch origin main`:
+    ///
+    /// ```no_run
+    /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> {
+    ///     repo.find_remote("origin")?.fetch(&["main"], None, None)
+    /// }
+    ///
+    /// let repo = git2::Repository::discover("rust").unwrap();
+    /// fetch_origin_main(repo).unwrap();
+    /// ```
+    pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>(
+        &mut self,
+        refspecs: &[Str],
+        opts: Option<&mut FetchOptions<'_>>,
+        reflog_msg: Option<&str>,
+    ) -> Result<(), Error> {
+        let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
+        let msg = crate::opt_cstr(reflog_msg)?;
+        let raw = opts.map(|o| o.raw());
+        unsafe {
+            try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg));
+        }
+        Ok(())
+    }
+
+    /// Update the tips to the new state
+    pub fn update_tips(
+        &mut self,
+        callbacks: Option<&mut RemoteCallbacks<'_>>,
+        update_flags: RemoteUpdateFlags,
+        download_tags: AutotagOption,
+        msg: Option<&str>,
+    ) -> Result<(), Error> {
+        let msg = crate::opt_cstr(msg)?;
+        let cbs = callbacks.map(|cb| cb.raw());
+        unsafe {
+            try_call!(raw::git_remote_update_tips(
+                self.raw,
+                cbs.as_ref(),
+                update_flags.bits() as c_uint,
+                download_tags,
+                msg
+            ));
+        }
+        Ok(())
+    }
+
+    /// Perform a push
+    ///
+    /// Perform all the steps for a push. If no refspecs are passed then the
+    /// configured refspecs will be used.
+    ///
+    /// Note that you'll likely want to use `RemoteCallbacks` and set
+    /// `push_update_reference` to test whether all the references were pushed
+    /// successfully.
+    pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>(
+        &mut self,
+        refspecs: &[Str],
+        opts: Option<&mut PushOptions<'_>>,
+    ) -> Result<(), Error> {
+        let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?;
+        let raw = opts.map(|o| o.raw());
+        unsafe {
+            try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref()));
+        }
+        Ok(())
+    }
+
+    /// Get the statistics structure that is filled in by the fetch operation.
+    pub fn stats(&self) -> Progress<'_> {
+        unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) }
+    }
+
+    /// Get the remote repository's reference advertisement list.
+    ///
+    /// Get the list of references with which the server responds to a new
+    /// connection.
+    ///
+    /// The remote (or more exactly its transport) must have connected to the
+    /// remote repository. This list is available as soon as the connection to
+    /// the remote is initiated and it remains available after disconnecting.
+    pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
+        let mut size = 0;
+        let mut base = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
+            assert_eq!(
+                mem::size_of::<RemoteHead<'_>>(),
+                mem::size_of::<*const raw::git_remote_head>()
+            );
+            let slice = slice::from_raw_parts(base as *const _, size as usize);
+            Ok(mem::transmute::<
+                &[*const raw::git_remote_head],
+                &[RemoteHead<'_>],
+            >(slice))
+        }
+    }
+
+    /// Prune tracking refs that are no longer present on remote
+    pub fn prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error> {
+        let cbs = Box::new(callbacks.unwrap_or_else(RemoteCallbacks::new));
+        unsafe {
+            try_call!(raw::git_remote_prune(self.raw, &cbs.raw()));
+        }
+        Ok(())
+    }
+
+    /// Get the remote's list of fetch refspecs
+    pub fn fetch_refspecs(&self) -> Result<StringArray, Error> {
+        unsafe {
+            let mut raw: raw::git_strarray = mem::zeroed();
+            try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw));
+            Ok(StringArray::from_raw(raw))
+        }
+    }
+
+    /// Get the remote's list of push refspecs
+    pub fn push_refspecs(&self) -> Result<StringArray, Error> {
+        unsafe {
+            let mut raw: raw::git_strarray = mem::zeroed();
+            try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw));
+            Ok(StringArray::from_raw(raw))
+        }
+    }
+}
+
+impl<'repo> Clone for Remote<'repo> {
+    fn clone(&self) -> Remote<'repo> {
+        let mut ret = ptr::null_mut();
+        let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
+        assert_eq!(rc, 0);
+        Remote {
+            raw: ret,
+            _marker: marker::PhantomData,
+        }
+    }
+}
+
+impl<'repo> Binding for Remote<'repo> {
+    type Raw = *mut raw::git_remote;
+
+    unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> {
+        Remote {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_remote {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Remote<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_remote_free(self.raw) }
+    }
+}
+
+impl<'repo> Iterator for Refspecs<'repo> {
+    type Item = Refspec<'repo>;
+    fn next(&mut self) -> Option<Refspec<'repo>> {
+        self.range.next().and_then(|i| self.remote.get_refspec(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'repo> DoubleEndedIterator for Refspecs<'repo> {
+    fn next_back(&mut self) -> Option<Refspec<'repo>> {
+        self.range
+            .next_back()
+            .and_then(|i| self.remote.get_refspec(i))
+    }
+}
+impl<'repo> FusedIterator for Refspecs<'repo> {}
+impl<'repo> ExactSizeIterator for Refspecs<'repo> {}
+
+#[allow(missing_docs)] // not documented in libgit2 :(
+impl<'remote> RemoteHead<'remote> {
+    /// Flag if this is available locally.
+    pub fn is_local(&self) -> bool {
+        unsafe { (*self.raw).local != 0 }
+    }
+
+    pub fn oid(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).oid as *const _) }
+    }
+    pub fn loid(&self) -> Oid {
+        unsafe { Binding::from_raw(&(*self.raw).loid as *const _) }
+    }
+
+    pub fn name(&self) -> &str {
+        let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() };
+        str::from_utf8(b).unwrap()
+    }
+
+    pub fn symref_target(&self) -> Option<&str> {
+        let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) };
+        b.map(|b| str::from_utf8(b).unwrap())
+    }
+}
+
+impl<'cb> Default for FetchOptions<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'cb> FetchOptions<'cb> {
+    /// Creates a new blank set of fetch options
+    pub fn new() -> FetchOptions<'cb> {
+        FetchOptions {
+            callbacks: None,
+            proxy: None,
+            prune: FetchPrune::Unspecified,
+            update_flags: RemoteUpdateFlags::UPDATE_FETCHHEAD,
+            download_tags: AutotagOption::Unspecified,
+            follow_redirects: RemoteRedirect::Initial,
+            custom_headers: Vec::new(),
+            custom_headers_ptrs: Vec::new(),
+            depth: 0, // Not limited depth
+        }
+    }
+
+    /// Set the callbacks to use for the fetch operation.
+    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
+        self.callbacks = Some(cbs);
+        self
+    }
+
+    /// Set the proxy options to use for the fetch operation.
+    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
+        self.proxy = Some(opts);
+        self
+    }
+
+    /// Set whether to perform a prune after the fetch.
+    pub fn prune(&mut self, prune: FetchPrune) -> &mut Self {
+        self.prune = prune;
+        self
+    }
+
+    /// Set whether to write the results to FETCH_HEAD.
+    ///
+    /// Defaults to `true`.
+    pub fn update_fetchhead(&mut self, update: bool) -> &mut Self {
+        self.update_flags
+            .set(RemoteUpdateFlags::UPDATE_FETCHHEAD, update);
+        self
+    }
+
+    /// Set whether to report unchanged tips in the update_tips callback.
+    ///
+    /// Defaults to `false`.
+    pub fn report_unchanged(&mut self, update: bool) -> &mut Self {
+        self.update_flags
+            .set(RemoteUpdateFlags::REPORT_UNCHANGED, update);
+        self
+    }
+
+    /// Set fetch depth, a value less or equal to 0 is interpreted as pull
+    /// everything (effectively the same as not declaring a limit depth).
+
+    // FIXME(blyxyas): We currently don't have a test for shallow functions
+    // because libgit2 doesn't support local shallow clones.
+    // https://github.com/rust-lang/git2-rs/pull/979#issuecomment-1716299900
+    pub fn depth(&mut self, depth: i32) -> &mut Self {
+        self.depth = depth.max(0);
+        self
+    }
+
+    /// Set how to behave regarding tags on the remote, such as auto-downloading
+    /// tags for objects we're downloading or downloading all of them.
+    ///
+    /// The default is to auto-follow tags.
+    pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self {
+        self.download_tags = opt;
+        self
+    }
+
+    /// Set remote redirection settings; whether redirects to another host are
+    /// permitted.
+    ///
+    /// By default, git will follow a redirect on the initial request
+    /// (`/info/refs`), but not subsequent requests.
+    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
+        self.follow_redirects = redirect;
+        self
+    }
+
+    /// Set extra headers for this fetch operation.
+    pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
+        self.custom_headers = custom_headers
+            .iter()
+            .map(|&s| CString::new(s).unwrap())
+            .collect();
+        self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
+        self
+    }
+}
+
+impl<'cb> Binding for FetchOptions<'cb> {
+    type Raw = raw::git_fetch_options;
+
+    unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> {
+        panic!("unimplemented");
+    }
+    fn raw(&self) -> raw::git_fetch_options {
+        raw::git_fetch_options {
+            version: 1,
+            callbacks: self
+                .callbacks
+                .as_ref()
+                .map(|m| m.raw())
+                .unwrap_or_else(|| RemoteCallbacks::new().raw()),
+            proxy_opts: self
+                .proxy
+                .as_ref()
+                .map(|m| m.raw())
+                .unwrap_or_else(|| ProxyOptions::new().raw()),
+            prune: crate::call::convert(&self.prune),
+            // `update_fetchhead` is an incorrectly named option which contains both
+            // the `UPDATE_FETCHHEAD` and `REPORT_UNCHANGED` flags.
+            // See https://github.com/libgit2/libgit2/pull/6806
+            update_fetchhead: self.update_flags.bits() as c_uint,
+            download_tags: crate::call::convert(&self.download_tags),
+            depth: self.depth,
+            follow_redirects: self.follow_redirects.raw(),
+            custom_headers: git_strarray {
+                count: self.custom_headers_ptrs.len(),
+                strings: self.custom_headers_ptrs.as_ptr() as *mut _,
+            },
+        }
+    }
+}
+
+impl<'cb> Default for PushOptions<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'cb> PushOptions<'cb> {
+    /// Creates a new blank set of push options
+    pub fn new() -> PushOptions<'cb> {
+        PushOptions {
+            callbacks: None,
+            proxy: None,
+            pb_parallelism: 1,
+            follow_redirects: RemoteRedirect::Initial,
+            custom_headers: Vec::new(),
+            custom_headers_ptrs: Vec::new(),
+            remote_push_options: Vec::new(),
+            remote_push_options_ptrs: Vec::new(),
+        }
+    }
+
+    /// Set the callbacks to use for the push operation.
+    pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self {
+        self.callbacks = Some(cbs);
+        self
+    }
+
+    /// Set the proxy options to use for the push operation.
+    pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self {
+        self.proxy = Some(opts);
+        self
+    }
+
+    /// If the transport being used to push to the remote requires the creation
+    /// of a pack file, this controls the number of worker threads used by the
+    /// packbuilder when creating that pack file to be sent to the remote.
+    ///
+    /// if set to 0 the packbuilder will auto-detect the number of threads to
+    /// create, and the default value is 1.
+    pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self {
+        self.pb_parallelism = parallel;
+        self
+    }
+
+    /// Set remote redirection settings; whether redirects to another host are
+    /// permitted.
+    ///
+    /// By default, git will follow a redirect on the initial request
+    /// (`/info/refs`), but not subsequent requests.
+    pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self {
+        self.follow_redirects = redirect;
+        self
+    }
+
+    /// Set extra headers for this push operation.
+    pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self {
+        self.custom_headers = custom_headers
+            .iter()
+            .map(|&s| CString::new(s).unwrap())
+            .collect();
+        self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect();
+        self
+    }
+
+    /// Set "push options" to deliver to the remote.
+    pub fn remote_push_options(&mut self, remote_push_options: &[&str]) -> &mut Self {
+        self.remote_push_options = remote_push_options
+            .iter()
+            .map(|&s| CString::new(s).unwrap())
+            .collect();
+        self.remote_push_options_ptrs = self
+            .remote_push_options
+            .iter()
+            .map(|s| s.as_ptr())
+            .collect();
+        self
+    }
+}
+
+impl<'cb> Binding for PushOptions<'cb> {
+    type Raw = raw::git_push_options;
+
+    unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> {
+        panic!("unimplemented");
+    }
+    fn raw(&self) -> raw::git_push_options {
+        raw::git_push_options {
+            version: 1,
+            callbacks: self
+                .callbacks
+                .as_ref()
+                .map(|m| m.raw())
+                .unwrap_or_else(|| RemoteCallbacks::new().raw()),
+            proxy_opts: self
+                .proxy
+                .as_ref()
+                .map(|m| m.raw())
+                .unwrap_or_else(|| ProxyOptions::new().raw()),
+            pb_parallelism: self.pb_parallelism as libc::c_uint,
+            follow_redirects: self.follow_redirects.raw(),
+            custom_headers: git_strarray {
+                count: self.custom_headers_ptrs.len(),
+                strings: self.custom_headers_ptrs.as_ptr() as *mut _,
+            },
+            remote_push_options: git_strarray {
+                count: self.remote_push_options.len(),
+                strings: self.remote_push_options_ptrs.as_ptr() as *mut _,
+            },
+        }
+    }
+}
+
+impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> {
+    /// Check whether the remote is (still) connected
+    pub fn connected(&mut self) -> bool {
+        self.remote.connected()
+    }
+
+    /// Get the remote repository's reference advertisement list.
+    ///
+    /// This list is available as soon as the connection to
+    /// the remote is initiated and it remains available after disconnecting.
+    pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> {
+        self.remote.list()
+    }
+
+    /// Get the remote's default branch.
+    ///
+    /// This default branch is available as soon as the connection to the remote
+    /// is initiated and it remains available after disconnecting.
+    pub fn default_branch(&self) -> Result<Buf, Error> {
+        self.remote.default_branch()
+    }
+
+    /// access remote bound to this connection
+    pub fn remote(&mut self) -> &mut Remote<'repo> {
+        self.remote
+    }
+}
+
+impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> {
+    fn drop(&mut self) {
+        drop(self.remote.disconnect());
+    }
+}
+
+impl Default for RemoteRedirect {
+    fn default() -> Self {
+        RemoteRedirect::Initial
+    }
+}
+
+impl RemoteRedirect {
+    fn raw(&self) -> raw::git_remote_redirect_t {
+        match self {
+            RemoteRedirect::None => raw::GIT_REMOTE_REDIRECT_NONE,
+            RemoteRedirect::Initial => raw::GIT_REMOTE_REDIRECT_INITIAL,
+            RemoteRedirect::All => raw::GIT_REMOTE_REDIRECT_ALL,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{AutotagOption, PushOptions, RemoteUpdateFlags};
+    use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository};
+    use std::cell::Cell;
+    use tempfile::TempDir;
+
+    #[test]
+    fn smoke() {
+        let (td, repo) = crate::test::repo_init();
+        t!(repo.remote("origin", "/path/to/nowhere"));
+        drop(repo);
+
+        let repo = t!(Repository::init(td.path()));
+        let mut origin = t!(repo.find_remote("origin"));
+        assert_eq!(origin.name(), Some("origin"));
+        assert_eq!(origin.url(), Some("/path/to/nowhere"));
+        assert_eq!(origin.pushurl(), None);
+
+        t!(repo.remote_set_url("origin", "/path/to/elsewhere"));
+        t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere")));
+
+        let stats = origin.stats();
+        assert_eq!(stats.total_objects(), 0);
+
+        t!(origin.stop());
+    }
+
+    #[test]
+    fn create_remote() {
+        let td = TempDir::new().unwrap();
+        let remote = td.path().join("remote");
+        Repository::init_bare(&remote).unwrap();
+
+        let (_td, repo) = crate::test::repo_init();
+        let url = if cfg!(unix) {
+            format!("file://{}", remote.display())
+        } else {
+            format!(
+                "file:///{}",
+                remote.display().to_string().replace("\\", "/")
+            )
+        };
+
+        let mut origin = repo.remote("origin", &url).unwrap();
+        assert_eq!(origin.name(), Some("origin"));
+        assert_eq!(origin.url(), Some(&url[..]));
+        assert_eq!(origin.pushurl(), None);
+
+        {
+            let mut specs = origin.refspecs();
+            let spec = specs.next().unwrap();
+            assert!(specs.next().is_none());
+            assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
+            assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
+            assert_eq!(spec.src(), Some("refs/heads/*"));
+            assert!(spec.is_force());
+        }
+        assert!(origin.refspecs().next_back().is_some());
+        {
+            let remotes = repo.remotes().unwrap();
+            assert_eq!(remotes.len(), 1);
+            assert_eq!(remotes.get(0), Some("origin"));
+            assert_eq!(remotes.iter().count(), 1);
+            assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
+        }
+
+        origin.connect(Direction::Push).unwrap();
+        assert!(origin.connected());
+        origin.disconnect().unwrap();
+
+        origin.connect(Direction::Fetch).unwrap();
+        assert!(origin.connected());
+        origin.download(&[] as &[&str], None).unwrap();
+        origin.disconnect().unwrap();
+
+        {
+            let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap();
+            assert!(connection.connected());
+        }
+        assert!(!origin.connected());
+
+        {
+            let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap();
+            assert!(connection.connected());
+        }
+        assert!(!origin.connected());
+
+        origin.fetch(&[] as &[&str], None, None).unwrap();
+        origin.fetch(&[] as &[&str], None, Some("foo")).unwrap();
+        origin
+            .update_tips(
+                None,
+                RemoteUpdateFlags::UPDATE_FETCHHEAD,
+                AutotagOption::Unspecified,
+                None,
+            )
+            .unwrap();
+        origin
+            .update_tips(
+                None,
+                RemoteUpdateFlags::UPDATE_FETCHHEAD,
+                AutotagOption::All,
+                Some("foo"),
+            )
+            .unwrap();
+
+        t!(repo.remote_add_fetch("origin", "foo"));
+        t!(repo.remote_add_fetch("origin", "bar"));
+    }
+
+    #[test]
+    fn rename_remote() {
+        let (_td, repo) = crate::test::repo_init();
+        repo.remote("origin", "foo").unwrap();
+        drop(repo.remote_rename("origin", "foo"));
+        drop(repo.remote_delete("foo"));
+    }
+
+    #[test]
+    fn create_remote_anonymous() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+
+        let origin = repo.remote_anonymous("/path/to/nowhere").unwrap();
+        assert_eq!(origin.name(), None);
+        drop(origin.clone());
+    }
+
+    #[test]
+    fn is_valid_name() {
+        assert!(Remote::is_valid_name("foobar"));
+        assert!(!Remote::is_valid_name("\x01"));
+    }
+
+    #[test]
+    #[should_panic]
+    fn is_valid_name_for_invalid_remote() {
+        Remote::is_valid_name("ab\012");
+    }
+
+    #[test]
+    fn transfer_cb() {
+        let (td, _repo) = crate::test::repo_init();
+        let td2 = TempDir::new().unwrap();
+        let url = crate::test::path2url(&td.path());
+
+        let repo = Repository::init(td2.path()).unwrap();
+        let progress_hit = Cell::new(false);
+        {
+            let mut callbacks = RemoteCallbacks::new();
+            let mut origin = repo.remote("origin", &url).unwrap();
+
+            callbacks.transfer_progress(|_progress| {
+                progress_hit.set(true);
+                true
+            });
+            origin
+                .fetch(
+                    &[] as &[&str],
+                    Some(FetchOptions::new().remote_callbacks(callbacks)),
+                    None,
+                )
+                .unwrap();
+
+            let list = t!(origin.list());
+            assert_eq!(list.len(), 2);
+            assert_eq!(list[0].name(), "HEAD");
+            assert!(!list[0].is_local());
+            assert_eq!(list[1].name(), "refs/heads/main");
+            assert!(!list[1].is_local());
+        }
+        assert!(progress_hit.get());
+    }
+
+    /// This test is meant to assure that the callbacks provided to connect will not cause
+    /// segfaults
+    #[test]
+    fn connect_list() {
+        let (td, _repo) = crate::test::repo_init();
+        let td2 = TempDir::new().unwrap();
+        let url = crate::test::path2url(&td.path());
+
+        let repo = Repository::init(td2.path()).unwrap();
+        let mut callbacks = RemoteCallbacks::new();
+        callbacks.sideband_progress(|_progress| {
+            // no-op
+            true
+        });
+
+        let mut origin = repo.remote("origin", &url).unwrap();
+
+        {
+            let mut connection = origin
+                .connect_auth(Direction::Fetch, Some(callbacks), None)
+                .unwrap();
+            assert!(connection.connected());
+
+            let list = t!(connection.list());
+            assert_eq!(list.len(), 2);
+            assert_eq!(list[0].name(), "HEAD");
+            assert!(!list[0].is_local());
+            assert_eq!(list[1].name(), "refs/heads/main");
+            assert!(!list[1].is_local());
+        }
+        assert!(!origin.connected());
+    }
+
+    #[test]
+    fn push() {
+        let (_td, repo) = crate::test::repo_init();
+        let td2 = TempDir::new().unwrap();
+        let td3 = TempDir::new().unwrap();
+        let url = crate::test::path2url(&td2.path());
+
+        let mut opts = crate::RepositoryInitOptions::new();
+        opts.bare(true);
+        opts.initial_head("main");
+        Repository::init_opts(td2.path(), &opts).unwrap();
+        // git push
+        let mut remote = repo.remote("origin", &url).unwrap();
+        let mut updated = false;
+        {
+            let mut callbacks = RemoteCallbacks::new();
+            callbacks.push_update_reference(|refname, status| {
+                updated = true;
+                assert_eq!(refname, "refs/heads/main");
+                assert_eq!(status, None);
+                Ok(())
+            });
+            let mut options = PushOptions::new();
+            options.remote_callbacks(callbacks);
+            remote
+                .push(&["refs/heads/main"], Some(&mut options))
+                .unwrap();
+        }
+        assert!(updated);
+
+        let repo = Repository::clone(&url, td3.path()).unwrap();
+        let commit = repo.head().unwrap().target().unwrap();
+        let commit = repo.find_commit(commit).unwrap();
+        assert_eq!(commit.message(), Some("initial\n\nbody"));
+    }
+
+    #[test]
+    fn prune() {
+        let (td, remote_repo) = crate::test::repo_init();
+        let oid = remote_repo.head().unwrap().target().unwrap();
+        let commit = remote_repo.find_commit(oid).unwrap();
+        remote_repo.branch("stale", &commit, true).unwrap();
+
+        let td2 = TempDir::new().unwrap();
+        let url = crate::test::path2url(&td.path());
+        let repo = Repository::clone(&url, &td2).unwrap();
+
+        fn assert_branch_count(repo: &Repository, count: usize) {
+            assert_eq!(
+                repo.branches(Some(crate::BranchType::Remote))
+                    .unwrap()
+                    .filter(|b| b.as_ref().unwrap().0.name().unwrap() == Some("origin/stale"))
+                    .count(),
+                count,
+            );
+        }
+
+        assert_branch_count(&repo, 1);
+
+        // delete `stale` branch on remote repo
+        let mut stale_branch = remote_repo
+            .find_branch("stale", crate::BranchType::Local)
+            .unwrap();
+        stale_branch.delete().unwrap();
+
+        // prune
+        let mut remote = repo.find_remote("origin").unwrap();
+        remote.connect(Direction::Push).unwrap();
+        let mut callbacks = RemoteCallbacks::new();
+        callbacks.update_tips(|refname, _a, b| {
+            assert_eq!(refname, "refs/remotes/origin/stale");
+            assert!(b.is_zero());
+            true
+        });
+        remote.prune(Some(callbacks)).unwrap();
+        assert_branch_count(&repo, 0);
+    }
+
+    #[test]
+    fn push_negotiation() {
+        let (_td, repo) = crate::test::repo_init();
+        let oid = repo.head().unwrap().target().unwrap();
+
+        let td2 = TempDir::new().unwrap();
+        let url = crate::test::path2url(td2.path());
+        let mut opts = crate::RepositoryInitOptions::new();
+        opts.bare(true);
+        opts.initial_head("main");
+        let remote_repo = Repository::init_opts(td2.path(), &opts).unwrap();
+
+        // reject pushing a branch
+        let mut remote = repo.remote("origin", &url).unwrap();
+        let mut updated = false;
+        {
+            let mut callbacks = RemoteCallbacks::new();
+            callbacks.push_negotiation(|updates| {
+                assert!(!updated);
+                updated = true;
+                assert_eq!(updates.len(), 1);
+                let u = &updates[0];
+                assert_eq!(u.src_refname().unwrap(), "refs/heads/main");
+                assert!(u.src().is_zero());
+                assert_eq!(u.dst_refname().unwrap(), "refs/heads/main");
+                assert_eq!(u.dst(), oid);
+                Err(crate::Error::from_str("rejected"))
+            });
+            let mut options = PushOptions::new();
+            options.remote_callbacks(callbacks);
+            assert!(remote
+                .push(&["refs/heads/main"], Some(&mut options))
+                .is_err());
+        }
+        assert!(updated);
+        assert_eq!(remote_repo.branches(None).unwrap().count(), 0);
+
+        // push 3 branches
+        let commit = repo.find_commit(oid).unwrap();
+        repo.branch("new1", &commit, true).unwrap();
+        repo.branch("new2", &commit, true).unwrap();
+        let mut flag = 0;
+        updated = false;
+        {
+            let mut callbacks = RemoteCallbacks::new();
+            callbacks.push_negotiation(|updates| {
+                assert!(!updated);
+                updated = true;
+                assert_eq!(updates.len(), 3);
+                for u in updates {
+                    assert!(u.src().is_zero());
+                    assert_eq!(u.dst(), oid);
+                    let src_name = u.src_refname().unwrap();
+                    let dst_name = u.dst_refname().unwrap();
+                    match src_name {
+                        "refs/heads/main" => {
+                            assert_eq!(dst_name, src_name);
+                            flag |= 1;
+                        }
+                        "refs/heads/new1" => {
+                            assert_eq!(dst_name, "refs/heads/dev1");
+                            flag |= 2;
+                        }
+                        "refs/heads/new2" => {
+                            assert_eq!(dst_name, "refs/heads/dev2");
+                            flag |= 4;
+                        }
+                        _ => panic!("unexpected refname: {}", src_name),
+                    }
+                }
+                Ok(())
+            });
+            let mut options = PushOptions::new();
+            options.remote_callbacks(callbacks);
+            remote
+                .push(
+                    &[
+                        "refs/heads/main",
+                        "refs/heads/new1:refs/heads/dev1",
+                        "refs/heads/new2:refs/heads/dev2",
+                    ],
+                    Some(&mut options),
+                )
+                .unwrap();
+        }
+        assert!(updated);
+        assert_eq!(flag, 7);
+        assert_eq!(remote_repo.branches(None).unwrap().count(), 3);
+    }
+}
diff --git a/git2/src/remote_callbacks.rs b/git2/src/remote_callbacks.rs
new file mode 100644 (file)
index 0000000..2df2e7b
--- /dev/null
@@ -0,0 +1,526 @@
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::ffi::CStr;
+use std::mem;
+use std::ptr;
+use std::slice;
+use std::str;
+
+use crate::cert::Cert;
+use crate::util::Binding;
+use crate::{
+    panic, raw, Cred, CredentialType, Error, IndexerProgress, Oid, PackBuilderStage, Progress,
+    PushUpdate,
+};
+
+/// A structure to contain the callbacks which are invoked when a repository is
+/// being updated or downloaded.
+///
+/// These callbacks are used to manage facilities such as authentication,
+/// transfer progress, etc.
+pub struct RemoteCallbacks<'a> {
+    push_progress: Option<Box<PushTransferProgress<'a>>>,
+    progress: Option<Box<IndexerProgress<'a>>>,
+    pack_progress: Option<Box<PackProgress<'a>>>,
+    credentials: Option<Box<Credentials<'a>>>,
+    sideband_progress: Option<Box<TransportMessage<'a>>>,
+    update_tips: Option<Box<UpdateTips<'a>>>,
+    certificate_check: Option<Box<CertificateCheck<'a>>>,
+    push_update_reference: Option<Box<PushUpdateReference<'a>>>,
+    push_negotiation: Option<Box<PushNegotiation<'a>>>,
+}
+
+/// Callback used to acquire credentials for when a remote is fetched.
+///
+/// * `url` - the resource for which the credentials are required.
+/// * `username_from_url` - the username that was embedded in the URL, or `None`
+///                         if it was not included.
+/// * `allowed_types` - a bitmask stating which cred types are OK to return.
+pub type Credentials<'a> =
+    dyn FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a;
+
+/// Callback for receiving messages delivered by the transport.
+///
+/// The return value indicates whether the network operation should continue.
+pub type TransportMessage<'a> = dyn FnMut(&[u8]) -> bool + 'a;
+
+/// Callback for whenever a reference is updated locally.
+pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a;
+
+/// Callback for a custom certificate check.
+///
+/// The first argument is the certificate received on the connection.
+/// Certificates are typically either an SSH or X509 certificate.
+///
+/// The second argument is the hostname for the connection is passed as the last
+/// argument.
+pub type CertificateCheck<'a> =
+    dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a;
+
+/// The return value for the [`RemoteCallbacks::certificate_check`] callback.
+pub enum CertificateCheckStatus {
+    /// Indicates that the certificate should be accepted.
+    CertificateOk,
+    /// Indicates that the certificate callback is neither accepting nor
+    /// rejecting the certificate. The result of the certificate checks
+    /// built-in to libgit2 will be used instead.
+    CertificatePassthrough,
+}
+
+/// Callback for each updated reference on push.
+///
+/// The first argument here is the `refname` of the reference, and the second is
+/// the status message sent by a server. If the status is `Some` then the update
+/// was rejected by the remote server with a reason why.
+pub type PushUpdateReference<'a> = dyn FnMut(&str, Option<&str>) -> Result<(), Error> + 'a;
+
+/// Callback for push transfer progress
+///
+/// Parameters:
+/// * current
+/// * total
+/// * bytes
+pub type PushTransferProgress<'a> = dyn FnMut(usize, usize, usize) + 'a;
+
+/// Callback for pack progress
+///
+/// Be aware that this is called inline with pack building operations,
+/// so performance may be affected.
+///
+/// Parameters:
+/// * stage
+/// * current
+/// * total
+pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a;
+
+/// The callback is called once between the negotiation step and the upload.
+///
+/// The argument is a slice containing the updates which will be sent as
+/// commands to the destination.
+///
+/// The push is cancelled if an error is returned.
+pub type PushNegotiation<'a> = dyn FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a;
+
+impl<'a> Default for RemoteCallbacks<'a> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'a> RemoteCallbacks<'a> {
+    /// Creates a new set of empty callbacks
+    pub fn new() -> RemoteCallbacks<'a> {
+        RemoteCallbacks {
+            credentials: None,
+            progress: None,
+            pack_progress: None,
+            sideband_progress: None,
+            update_tips: None,
+            certificate_check: None,
+            push_update_reference: None,
+            push_progress: None,
+            push_negotiation: None,
+        }
+    }
+
+    /// The callback through which to fetch credentials if required.
+    ///
+    /// # Example
+    ///
+    /// Prepare a callback to authenticate using the `$HOME/.ssh/id_rsa` SSH key, and
+    /// extracting the username from the URL (i.e. git@github.com:rust-lang/git2-rs.git):
+    ///
+    /// ```no_run
+    /// use git2::{Cred, RemoteCallbacks};
+    /// use std::env;
+    ///
+    /// let mut callbacks = RemoteCallbacks::new();
+    /// callbacks.credentials(|_url, username_from_url, _allowed_types| {
+    ///   Cred::ssh_key(
+    ///     username_from_url.unwrap(),
+    ///     None,
+    ///     std::path::Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
+    ///     None,
+    ///   )
+    /// });
+    /// ```
+    pub fn credentials<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a,
+    {
+        self.credentials = Some(Box::new(cb) as Box<Credentials<'a>>);
+        self
+    }
+
+    /// The callback through which progress is monitored.
+    pub fn transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(Progress<'_>) -> bool + 'a,
+    {
+        self.progress = Some(Box::new(cb) as Box<IndexerProgress<'a>>);
+        self
+    }
+
+    /// Textual progress from the remote.
+    ///
+    /// Text sent over the progress side-band will be passed to this function
+    /// (this is the 'counting objects' output).
+    pub fn sideband_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&[u8]) -> bool + 'a,
+    {
+        self.sideband_progress = Some(Box::new(cb) as Box<TransportMessage<'a>>);
+        self
+    }
+
+    /// Each time a reference is updated locally, the callback will be called
+    /// with information about it.
+    pub fn update_tips<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&str, Oid, Oid) -> bool + 'a,
+    {
+        self.update_tips = Some(Box::new(cb) as Box<UpdateTips<'a>>);
+        self
+    }
+
+    /// If certificate verification fails, then this callback will be invoked to
+    /// let the caller make the final decision of whether to allow the
+    /// connection to proceed.
+    pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a,
+    {
+        self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>);
+        self
+    }
+
+    /// Set a callback to get invoked for each updated reference on a push.
+    ///
+    /// The first argument to the callback is the name of the reference and the
+    /// second is a status message sent by the server. If the status is `Some`
+    /// then the push was rejected.
+    pub fn push_update_reference<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&str, Option<&str>) -> Result<(), Error> + 'a,
+    {
+        self.push_update_reference = Some(Box::new(cb) as Box<PushUpdateReference<'a>>);
+        self
+    }
+
+    /// The callback through which progress of push transfer is monitored
+    ///
+    /// Parameters:
+    /// * current
+    /// * total
+    /// * bytes
+    pub fn push_transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(usize, usize, usize) + 'a,
+    {
+        self.push_progress = Some(Box::new(cb) as Box<PushTransferProgress<'a>>);
+        self
+    }
+
+    /// Function to call with progress information during pack building.
+    ///
+    /// Be aware that this is called inline with pack building operations,
+    /// so performance may be affected.
+    ///
+    /// Parameters:
+    /// * stage
+    /// * current
+    /// * total
+    pub fn pack_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(PackBuilderStage, usize, usize) + 'a,
+    {
+        self.pack_progress = Some(Box::new(cb) as Box<PackProgress<'a>>);
+        self
+    }
+
+    /// The callback is called once between the negotiation step and the upload.
+    ///
+    /// The argument to the callback is a slice containing the updates which
+    /// will be sent as commands to the destination.
+    ///
+    /// The push is cancelled if the callback returns an error.
+    pub fn push_negotiation<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a>
+    where
+        F: FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a,
+    {
+        self.push_negotiation = Some(Box::new(cb) as Box<PushNegotiation<'a>>);
+        self
+    }
+}
+
+impl<'a> Binding for RemoteCallbacks<'a> {
+    type Raw = raw::git_remote_callbacks;
+    unsafe fn from_raw(_raw: raw::git_remote_callbacks) -> RemoteCallbacks<'a> {
+        panic!("unimplemented");
+    }
+
+    fn raw(&self) -> raw::git_remote_callbacks {
+        unsafe {
+            let mut callbacks: raw::git_remote_callbacks = mem::zeroed();
+            assert_eq!(
+                raw::git_remote_init_callbacks(&mut callbacks, raw::GIT_REMOTE_CALLBACKS_VERSION),
+                0
+            );
+            if self.progress.is_some() {
+                callbacks.transfer_progress = Some(transfer_progress_cb);
+            }
+            if self.credentials.is_some() {
+                callbacks.credentials = Some(credentials_cb);
+            }
+            if self.sideband_progress.is_some() {
+                callbacks.sideband_progress = Some(sideband_progress_cb);
+            }
+            if self.certificate_check.is_some() {
+                callbacks.certificate_check = Some(certificate_check_cb);
+            }
+            if self.push_update_reference.is_some() {
+                callbacks.push_update_reference = Some(push_update_reference_cb);
+            }
+            if self.push_progress.is_some() {
+                callbacks.push_transfer_progress = Some(push_transfer_progress_cb);
+            }
+            if self.pack_progress.is_some() {
+                callbacks.pack_progress = Some(pack_progress_cb);
+            }
+            if self.update_tips.is_some() {
+                let f: extern "C" fn(
+                    *const c_char,
+                    *const raw::git_oid,
+                    *const raw::git_oid,
+                    *mut c_void,
+                ) -> c_int = update_tips_cb;
+                callbacks.update_tips = Some(f);
+            }
+            if self.push_negotiation.is_some() {
+                callbacks.push_negotiation = Some(push_negotiation_cb);
+            }
+            callbacks.payload = self as *const _ as *mut _;
+            callbacks
+        }
+    }
+}
+
+extern "C" fn credentials_cb(
+    ret: *mut *mut raw::git_cred,
+    url: *const c_char,
+    username_from_url: *const c_char,
+    allowed_types: c_uint,
+    payload: *mut c_void,
+) -> c_int {
+    unsafe {
+        let ok = panic::wrap(|| {
+            let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
+            let callback = payload
+                .credentials
+                .as_mut()
+                .ok_or(raw::GIT_PASSTHROUGH as c_int)?;
+            *ret = ptr::null_mut();
+            let url = str::from_utf8(CStr::from_ptr(url).to_bytes())
+                .map_err(|_| raw::GIT_PASSTHROUGH as c_int)?;
+            let username_from_url = match crate::opt_bytes(&url, username_from_url) {
+                Some(username) => {
+                    Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?)
+                }
+                None => None,
+            };
+
+            let cred_type = CredentialType::from_bits_truncate(allowed_types as u32);
+
+            callback(url, username_from_url, cred_type).map_err(|e| e.raw_set_git_error())
+        });
+        match ok {
+            Some(Ok(cred)) => {
+                // Turns out it's a memory safety issue if we pass through any
+                // and all credentials into libgit2
+                if allowed_types & (cred.credtype() as c_uint) != 0 {
+                    *ret = cred.unwrap();
+                    0
+                } else {
+                    raw::GIT_PASSTHROUGH as c_int
+                }
+            }
+            Some(Err(e)) => e,
+            None => -1,
+        }
+    }
+}
+
+extern "C" fn transfer_progress_cb(
+    stats: *const raw::git_indexer_progress,
+    payload: *mut c_void,
+) -> c_int {
+    let ok = panic::wrap(|| unsafe {
+        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
+        let callback = match payload.progress {
+            Some(ref mut c) => c,
+            None => return true,
+        };
+        let progress = Binding::from_raw(stats);
+        callback(progress)
+    });
+    if ok == Some(true) {
+        0
+    } else {
+        -1
+    }
+}
+
+extern "C" fn sideband_progress_cb(str: *const c_char, len: c_int, payload: *mut c_void) -> c_int {
+    let ok = panic::wrap(|| unsafe {
+        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
+        let callback = match payload.sideband_progress {
+            Some(ref mut c) => c,
+            None => return true,
+        };
+        let buf = slice::from_raw_parts(str as *const u8, len as usize);
+        callback(buf)
+    });
+    if ok == Some(true) {
+        0
+    } else {
+        -1
+    }
+}
+
+extern "C" fn update_tips_cb(
+    refname: *const c_char,
+    a: *const raw::git_oid,
+    b: *const raw::git_oid,
+    data: *mut c_void,
+) -> c_int {
+    let ok = panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
+        let callback = match payload.update_tips {
+            Some(ref mut c) => c,
+            None => return true,
+        };
+        let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
+        let a = Binding::from_raw(a);
+        let b = Binding::from_raw(b);
+        callback(refname, a, b)
+    });
+    if ok == Some(true) {
+        0
+    } else {
+        -1
+    }
+}
+
+extern "C" fn certificate_check_cb(
+    cert: *mut raw::git_cert,
+    _valid: c_int,
+    hostname: *const c_char,
+    data: *mut c_void,
+) -> c_int {
+    let ok = panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
+        let callback = match payload.certificate_check {
+            Some(ref mut c) => c,
+            None => return Ok(CertificateCheckStatus::CertificatePassthrough),
+        };
+        let cert = Binding::from_raw(cert);
+        let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap();
+        callback(&cert, hostname)
+    });
+    match ok {
+        Some(Ok(CertificateCheckStatus::CertificateOk)) => 0,
+        Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int,
+        Some(Err(e)) => unsafe { e.raw_set_git_error() },
+        None => {
+            // Panic. The *should* get resumed by some future call to check().
+            -1
+        }
+    }
+}
+
+extern "C" fn push_update_reference_cb(
+    refname: *const c_char,
+    status: *const c_char,
+    data: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
+        let callback = match payload.push_update_reference {
+            Some(ref mut c) => c,
+            None => return 0,
+        };
+        let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap();
+        let status = if status.is_null() {
+            None
+        } else {
+            Some(str::from_utf8(CStr::from_ptr(status).to_bytes()).unwrap())
+        };
+        match callback(refname, status) {
+            Ok(()) => 0,
+            Err(e) => e.raw_set_git_error(),
+        }
+    })
+    .unwrap_or(-1)
+}
+
+extern "C" fn push_transfer_progress_cb(
+    progress: c_uint,
+    total: c_uint,
+    bytes: size_t,
+    data: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
+        let callback = match payload.push_progress {
+            Some(ref mut c) => c,
+            None => return 0,
+        };
+
+        callback(progress as usize, total as usize, bytes as usize);
+
+        0
+    })
+    .unwrap_or(-1)
+}
+
+extern "C" fn pack_progress_cb(
+    stage: raw::git_packbuilder_stage_t,
+    current: c_uint,
+    total: c_uint,
+    data: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let payload = &mut *(data as *mut RemoteCallbacks<'_>);
+        let callback = match payload.pack_progress {
+            Some(ref mut c) => c,
+            None => return 0,
+        };
+
+        let stage = Binding::from_raw(stage);
+
+        callback(stage, current as usize, total as usize);
+
+        0
+    })
+    .unwrap_or(-1)
+}
+
+extern "C" fn push_negotiation_cb(
+    updates: *mut *const raw::git_push_update,
+    len: size_t,
+    payload: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let payload = &mut *(payload as *mut RemoteCallbacks<'_>);
+        let callback = match payload.push_negotiation {
+            Some(ref mut c) => c,
+            None => return 0,
+        };
+
+        let updates = slice::from_raw_parts(updates as *mut PushUpdate<'_>, len);
+        match callback(updates) {
+            Ok(()) => 0,
+            Err(e) => e.raw_set_git_error(),
+        }
+    })
+    .unwrap_or(-1)
+}
diff --git a/git2/src/repo.rs b/git2/src/repo.rs
new file mode 100644 (file)
index 0000000..074955f
--- /dev/null
@@ -0,0 +1,4390 @@
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::env;
+use std::ffi::{CStr, CString, OsStr};
+use std::mem;
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::str;
+
+use crate::build::{CheckoutBuilder, RepoBuilder};
+use crate::diff::{
+    binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
+};
+use crate::oid_array::OidArray;
+use crate::stash::{stash_cb, StashApplyOptions, StashCbData, StashSaveOptions};
+use crate::string_array::StringArray;
+use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData};
+use crate::util::{self, path_to_repo_path, Binding};
+use crate::worktree::{Worktree, WorktreeAddOptions};
+use crate::CherrypickOptions;
+use crate::RevertOptions;
+use crate::{mailmap::Mailmap, panic};
+use crate::{
+    raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState, Revspec,
+    StashFlags,
+};
+use crate::{
+    AnnotatedCommit, MergeAnalysis, MergeOptions, MergePreference, SubmoduleIgnore,
+    SubmoduleStatus, SubmoduleUpdate,
+};
+use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions};
+use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule};
+use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree};
+use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode};
+use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder};
+use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag, Transaction};
+
+type MergeheadForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
+type FetchheadForeachCb<'a> = dyn FnMut(&str, &[u8], &Oid, bool) -> bool + 'a;
+
+struct FetchheadForeachCbData<'a> {
+    callback: &'a mut FetchheadForeachCb<'a>,
+}
+
+struct MergeheadForeachCbData<'a> {
+    callback: &'a mut MergeheadForeachCb<'a>,
+}
+
+extern "C" fn mergehead_foreach_cb(oid: *const raw::git_oid, payload: *mut c_void) -> c_int {
+    panic::wrap(|| unsafe {
+        let data = &mut *(payload as *mut MergeheadForeachCbData<'_>);
+        let res = {
+            let callback = &mut data.callback;
+            callback(&Binding::from_raw(oid))
+        };
+
+        if res {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(1)
+}
+
+extern "C" fn fetchhead_foreach_cb(
+    ref_name: *const c_char,
+    remote_url: *const c_char,
+    oid: *const raw::git_oid,
+    is_merge: c_uint,
+    payload: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let data = &mut *(payload as *mut FetchheadForeachCbData<'_>);
+        let res = {
+            let callback = &mut data.callback;
+
+            assert!(!ref_name.is_null());
+            assert!(!remote_url.is_null());
+            assert!(!oid.is_null());
+
+            let ref_name = str::from_utf8(CStr::from_ptr(ref_name).to_bytes()).unwrap();
+            let remote_url = CStr::from_ptr(remote_url).to_bytes();
+            let oid = Binding::from_raw(oid);
+            let is_merge = is_merge == 1;
+
+            callback(&ref_name, remote_url, &oid, is_merge)
+        };
+
+        if res {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(1)
+}
+
+/// An owned git repository, representing all state associated with the
+/// underlying filesystem.
+///
+/// This structure corresponds to a `git_repository` in libgit2. Many other
+/// types in git2-rs are derivative from this structure and are attached to its
+/// lifetime.
+///
+/// When a repository goes out of scope it is freed in memory but not deleted
+/// from the filesystem.
+pub struct Repository {
+    raw: *mut raw::git_repository,
+}
+
+// It is the current belief that a `Repository` can be sent among threads, or
+// even shared among threads in a mutex.
+unsafe impl Send for Repository {}
+
+/// Options which can be used to configure how a repository is initialized
+pub struct RepositoryInitOptions {
+    flags: u32,
+    mode: u32,
+    workdir_path: Option<CString>,
+    description: Option<CString>,
+    template_path: Option<CString>,
+    initial_head: Option<CString>,
+    origin_url: Option<CString>,
+}
+
+impl Repository {
+    /// Attempt to open an already-existing repository at `path`.
+    ///
+    /// The path can point to either a normal or bare repository.
+    pub fn open<P: AsRef<Path>>(path: P) -> Result<Repository, Error> {
+        crate::init();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_open(&mut ret, path));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Attempt to open an already-existing bare repository at `path`.
+    ///
+    /// The path can point to only a bare repository.
+    pub fn open_bare<P: AsRef<Path>>(path: P) -> Result<Repository, Error> {
+        crate::init();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_open_bare(&mut ret, path));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Find and open an existing repository, respecting git environment
+    /// variables.  This acts like `open_ext` with the
+    /// [FROM_ENV](RepositoryOpenFlags::FROM_ENV) flag, but additionally respects `$GIT_DIR`.
+    /// With `$GIT_DIR` unset, this will search for a repository starting in
+    /// the current directory.
+    pub fn open_from_env() -> Result<Repository, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let flags = raw::GIT_REPOSITORY_OPEN_FROM_ENV;
+        unsafe {
+            try_call!(raw::git_repository_open_ext(
+                &mut ret,
+                ptr::null(),
+                flags as c_uint,
+                ptr::null()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Find and open an existing repository, with additional options.
+    ///
+    /// If flags contains [NO_SEARCH](RepositoryOpenFlags::NO_SEARCH), the path must point
+    /// directly to a repository; otherwise, this may point to a subdirectory
+    /// of a repository, and `open_ext` will search up through parent
+    /// directories.
+    ///
+    /// If flags contains [CROSS_FS](RepositoryOpenFlags::CROSS_FS), the search through parent
+    /// directories will not cross a filesystem boundary (detected when the
+    /// stat st_dev field changes).
+    ///
+    /// If flags contains [BARE](RepositoryOpenFlags::BARE), force opening the repository as
+    /// bare even if it isn't, ignoring any working directory, and defer
+    /// loading the repository configuration for performance.
+    ///
+    /// If flags contains [NO_DOTGIT](RepositoryOpenFlags::NO_DOTGIT), don't try appending
+    /// `/.git` to `path`.
+    ///
+    /// If flags contains [FROM_ENV](RepositoryOpenFlags::FROM_ENV), `open_ext` will ignore
+    /// other flags and `ceiling_dirs`, and respect the same environment
+    /// variables git does. Note, however, that `path` overrides `$GIT_DIR`; to
+    /// respect `$GIT_DIR` as well, use `open_from_env`.
+    ///
+    /// ceiling_dirs specifies a list of paths that the search through parent
+    /// directories will stop before entering.  Use the functions in std::env
+    /// to construct or manipulate such a path list. (You can use `&[] as
+    /// &[&std::ffi::OsStr]` as an argument if there are no ceiling
+    /// directories.)
+    pub fn open_ext<P, O, I>(
+        path: P,
+        flags: RepositoryOpenFlags,
+        ceiling_dirs: I,
+    ) -> Result<Repository, Error>
+    where
+        P: AsRef<Path>,
+        O: AsRef<OsStr>,
+        I: IntoIterator<Item = O>,
+    {
+        crate::init();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        let ceiling_dirs_os = env::join_paths(ceiling_dirs)?;
+        let ceiling_dirs = ceiling_dirs_os.into_c_string()?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_open_ext(
+                &mut ret,
+                path,
+                flags.bits() as c_uint,
+                ceiling_dirs
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Attempt to open an already-existing repository from a worktree.
+    pub fn open_from_worktree(worktree: &Worktree) -> Result<Repository, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_open_from_worktree(
+                &mut ret,
+                worktree.raw()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Attempt to open an already-existing repository at or above `path`
+    ///
+    /// This starts at `path` and looks up the filesystem hierarchy
+    /// until it finds a repository.
+    pub fn discover<P: AsRef<Path>>(path: P) -> Result<Repository, Error> {
+        // TODO: this diverges significantly from the libgit2 API
+        crate::init();
+        let buf = Buf::new();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        unsafe {
+            try_call!(raw::git_repository_discover(
+                buf.raw(),
+                path,
+                1,
+                ptr::null()
+            ));
+        }
+        Repository::open(util::bytes2path(&*buf))
+    }
+
+    /// Attempt to find the path to a git repo for a given path
+    ///
+    /// This starts at `path` and looks up the filesystem hierarchy
+    /// until it finds a repository, stopping if it finds a member of ceiling_dirs
+    pub fn discover_path<P: AsRef<Path>, I, O>(path: P, ceiling_dirs: I) -> Result<PathBuf, Error>
+    where
+        O: AsRef<OsStr>,
+        I: IntoIterator<Item = O>,
+    {
+        crate::init();
+        let buf = Buf::new();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        let ceiling_dirs_os = env::join_paths(ceiling_dirs)?;
+        let ceiling_dirs = ceiling_dirs_os.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_repository_discover(
+                buf.raw(),
+                path,
+                1,
+                ceiling_dirs
+            ));
+        }
+
+        Ok(util::bytes2path(&*buf).to_path_buf())
+    }
+
+    /// Creates a new repository in the specified folder.
+    ///
+    /// This by default will create any necessary directories to create the
+    /// repository, and it will read any user-specified templates when creating
+    /// the repository. This behavior can be configured through `init_opts`.
+    pub fn init<P: AsRef<Path>>(path: P) -> Result<Repository, Error> {
+        Repository::init_opts(path, &RepositoryInitOptions::new())
+    }
+
+    /// Creates a new `--bare` repository in the specified folder.
+    ///
+    /// The folder must exist prior to invoking this function.
+    pub fn init_bare<P: AsRef<Path>>(path: P) -> Result<Repository, Error> {
+        Repository::init_opts(path, RepositoryInitOptions::new().bare(true))
+    }
+
+    /// Creates a new repository in the specified folder with the given options.
+    ///
+    /// See `RepositoryInitOptions` struct for more information.
+    pub fn init_opts<P: AsRef<Path>>(
+        path: P,
+        opts: &RepositoryInitOptions,
+    ) -> Result<Repository, Error> {
+        crate::init();
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.as_ref().into_c_string()?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            let mut opts = opts.raw();
+            try_call!(raw::git_repository_init_ext(&mut ret, path, &mut opts));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Clone a remote repository.
+    ///
+    /// See the `RepoBuilder` struct for more information. This function will
+    /// delegate to a fresh `RepoBuilder`
+    pub fn clone<P: AsRef<Path>>(url: &str, into: P) -> Result<Repository, Error> {
+        crate::init();
+        RepoBuilder::new().clone(url, into.as_ref())
+    }
+
+    /// Clone a remote repository, initialize and update its submodules
+    /// recursively.
+    ///
+    /// This is similar to `git clone --recursive`.
+    pub fn clone_recurse<P: AsRef<Path>>(url: &str, into: P) -> Result<Repository, Error> {
+        let repo = Repository::clone(url, into)?;
+        repo.update_submodules()?;
+        Ok(repo)
+    }
+
+    /// Attempt to wrap an object database as a repository.
+    pub fn from_odb(odb: Odb<'_>) -> Result<Repository, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_wrap_odb(&mut ret, odb.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Update submodules recursively.
+    ///
+    /// Uninitialized submodules will be initialized.
+    fn update_submodules(&self) -> Result<(), Error> {
+        fn add_subrepos(repo: &Repository, list: &mut Vec<Repository>) -> Result<(), Error> {
+            for mut subm in repo.submodules()? {
+                subm.update(true, None)?;
+                list.push(subm.open()?);
+            }
+            Ok(())
+        }
+
+        let mut repos = Vec::new();
+        add_subrepos(self, &mut repos)?;
+        while let Some(repo) = repos.pop() {
+            add_subrepos(&repo, &mut repos)?;
+        }
+        Ok(())
+    }
+
+    /// Execute a rev-parse operation against the `spec` listed.
+    ///
+    /// The resulting revision specification is returned, or an error is
+    /// returned if one occurs.
+    pub fn revparse(&self, spec: &str) -> Result<Revspec<'_>, Error> {
+        let mut raw = raw::git_revspec {
+            from: ptr::null_mut(),
+            to: ptr::null_mut(),
+            flags: 0,
+        };
+        let spec = CString::new(spec)?;
+        unsafe {
+            try_call!(raw::git_revparse(&mut raw, self.raw, spec));
+            let to = Binding::from_raw_opt(raw.to);
+            let from = Binding::from_raw_opt(raw.from);
+            let mode = RevparseMode::from_bits_truncate(raw.flags as u32);
+            Ok(Revspec::from_objects(from, to, mode))
+        }
+    }
+
+    /// Find a single object, as specified by a revision string.
+    pub fn revparse_single(&self, spec: &str) -> Result<Object<'_>, Error> {
+        let spec = CString::new(spec)?;
+        let mut obj = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_revparse_single(&mut obj, self.raw, spec));
+            assert!(!obj.is_null());
+            Ok(Binding::from_raw(obj))
+        }
+    }
+
+    /// Find a single object and intermediate reference by a revision string.
+    ///
+    /// See `man gitrevisions`, or
+    /// <http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions> for
+    /// information on the syntax accepted.
+    ///
+    /// In some cases (`@{<-n>}` or `<branchname>@{upstream}`), the expression
+    /// may point to an intermediate reference. When such expressions are being
+    /// passed in, this intermediate reference is returned.
+    pub fn revparse_ext(&self, spec: &str) -> Result<(Object<'_>, Option<Reference<'_>>), Error> {
+        let spec = CString::new(spec)?;
+        let mut git_obj = ptr::null_mut();
+        let mut git_ref = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_revparse_ext(
+                &mut git_obj,
+                &mut git_ref,
+                self.raw,
+                spec
+            ));
+            assert!(!git_obj.is_null());
+            Ok((Binding::from_raw(git_obj), Binding::from_raw_opt(git_ref)))
+        }
+    }
+
+    /// Tests whether this repository is a bare repository or not.
+    pub fn is_bare(&self) -> bool {
+        unsafe { raw::git_repository_is_bare(self.raw) == 1 }
+    }
+
+    /// Tests whether this repository is a shallow clone.
+    pub fn is_shallow(&self) -> bool {
+        unsafe { raw::git_repository_is_shallow(self.raw) == 1 }
+    }
+
+    /// Tests whether this repository is a worktree.
+    pub fn is_worktree(&self) -> bool {
+        unsafe { raw::git_repository_is_worktree(self.raw) == 1 }
+    }
+
+    /// Tests whether this repository is empty.
+    pub fn is_empty(&self) -> Result<bool, Error> {
+        let empty = unsafe { try_call!(raw::git_repository_is_empty(self.raw)) };
+        Ok(empty == 1)
+    }
+
+    /// Returns the path to the `.git` folder for normal repositories or the
+    /// repository itself for bare repositories.
+    pub fn path(&self) -> &Path {
+        unsafe {
+            let ptr = raw::git_repository_path(self.raw);
+            util::bytes2path(crate::opt_bytes(self, ptr).unwrap())
+        }
+    }
+
+    /// Returns the path of the shared common directory for this repository.
+    ///
+    /// If the repository is bare, it is the root directory for the repository.
+    /// If the repository is a worktree, it is the parent repo's gitdir.
+    /// Otherwise, it is the gitdir.
+    pub fn commondir(&self) -> &Path {
+        unsafe {
+            let ptr = raw::git_repository_commondir(self.raw);
+            util::bytes2path(crate::opt_bytes(self, ptr).unwrap())
+        }
+    }
+
+    /// Returns the current state of this repository
+    pub fn state(&self) -> RepositoryState {
+        let state = unsafe { raw::git_repository_state(self.raw) };
+        macro_rules! check( ($($raw:ident => $real:ident),*) => (
+            $(if state == raw::$raw as c_int {
+                super::RepositoryState::$real
+            }) else *
+            else {
+                panic!("unknown repository state: {}", state)
+            }
+        ) );
+
+        check!(
+            GIT_REPOSITORY_STATE_NONE => Clean,
+            GIT_REPOSITORY_STATE_MERGE => Merge,
+            GIT_REPOSITORY_STATE_REVERT => Revert,
+            GIT_REPOSITORY_STATE_REVERT_SEQUENCE => RevertSequence,
+            GIT_REPOSITORY_STATE_CHERRYPICK => CherryPick,
+            GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE => CherryPickSequence,
+            GIT_REPOSITORY_STATE_BISECT => Bisect,
+            GIT_REPOSITORY_STATE_REBASE => Rebase,
+            GIT_REPOSITORY_STATE_REBASE_INTERACTIVE => RebaseInteractive,
+            GIT_REPOSITORY_STATE_REBASE_MERGE => RebaseMerge,
+            GIT_REPOSITORY_STATE_APPLY_MAILBOX => ApplyMailbox,
+            GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE => ApplyMailboxOrRebase
+        )
+    }
+
+    /// Get the path of the working directory for this repository.
+    ///
+    /// If this repository is bare, then `None` is returned.
+    pub fn workdir(&self) -> Option<&Path> {
+        unsafe {
+            let ptr = raw::git_repository_workdir(self.raw);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(util::bytes2path(CStr::from_ptr(ptr).to_bytes()))
+            }
+        }
+    }
+
+    /// Set the path to the working directory for this repository.
+    ///
+    /// If `update_link` is true, create/update the gitlink file in the workdir
+    /// and set config "core.worktree" (if workdir is not the parent of the .git
+    /// directory).
+    pub fn set_workdir(&self, path: &Path, update_gitlink: bool) -> Result<(), Error> {
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_repository_set_workdir(
+                self.raw(),
+                path,
+                update_gitlink
+            ));
+        }
+        Ok(())
+    }
+
+    /// Get the currently active namespace for this repository.
+    ///
+    /// If there is no namespace, or the namespace is not a valid utf8 string,
+    /// `None` is returned.
+    pub fn namespace(&self) -> Option<&str> {
+        self.namespace_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the currently active namespace for this repository as a byte array.
+    ///
+    /// If there is no namespace, `None` is returned.
+    pub fn namespace_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_repository_get_namespace(self.raw)) }
+    }
+
+    /// Set the active namespace for this repository.
+    pub fn set_namespace(&self, namespace: &str) -> Result<(), Error> {
+        self.set_namespace_bytes(namespace.as_bytes())
+    }
+
+    /// Set the active namespace for this repository as a byte array.
+    pub fn set_namespace_bytes(&self, namespace: &[u8]) -> Result<(), Error> {
+        unsafe {
+            let namespace = CString::new(namespace)?;
+            try_call!(raw::git_repository_set_namespace(self.raw, namespace));
+            Ok(())
+        }
+    }
+
+    /// Remove the active namespace for this repository.
+    pub fn remove_namespace(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_set_namespace(self.raw, ptr::null()));
+            Ok(())
+        }
+    }
+
+    /// Retrieves the Git merge message.
+    /// Remember to remove the message when finished.
+    pub fn message(&self) -> Result<String, Error> {
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_repository_message(buf.raw(), self.raw));
+            Ok(str::from_utf8(&buf).unwrap().to_string())
+        }
+    }
+
+    /// Remove the Git merge message.
+    pub fn remove_message(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_message_remove(self.raw));
+            Ok(())
+        }
+    }
+
+    /// List all remotes for a given repository
+    pub fn remotes(&self) -> Result<StringArray, Error> {
+        let mut arr = raw::git_strarray {
+            strings: ptr::null_mut(),
+            count: 0,
+        };
+        unsafe {
+            try_call!(raw::git_remote_list(&mut arr, self.raw));
+            Ok(Binding::from_raw(arr))
+        }
+    }
+
+    /// Get the information for a particular remote
+    pub fn find_remote(&self, name: &str) -> Result<Remote<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_remote_lookup(&mut ret, self.raw, name));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Add a remote with the default fetch refspec to the repository's
+    /// configuration.
+    pub fn remote(&self, name: &str, url: &str) -> Result<Remote<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        let url = CString::new(url)?;
+        unsafe {
+            try_call!(raw::git_remote_create(&mut ret, self.raw, name, url));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Add a remote with the provided fetch refspec to the repository's
+    /// configuration.
+    pub fn remote_with_fetch(
+        &self,
+        name: &str,
+        url: &str,
+        fetch: &str,
+    ) -> Result<Remote<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        let url = CString::new(url)?;
+        let fetch = CString::new(fetch)?;
+        unsafe {
+            try_call!(raw::git_remote_create_with_fetchspec(
+                &mut ret, self.raw, name, url, fetch
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create an anonymous remote
+    ///
+    /// Create a remote with the given URL and refspec in memory. You can use
+    /// this when you have a URL instead of a remote's name. Note that anonymous
+    /// remotes cannot be converted to persisted remotes.
+    pub fn remote_anonymous(&self, url: &str) -> Result<Remote<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let url = CString::new(url)?;
+        unsafe {
+            try_call!(raw::git_remote_create_anonymous(&mut ret, self.raw, url));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Give a remote a new name
+    ///
+    /// All remote-tracking branches and configuration settings for the remote
+    /// are updated.
+    ///
+    /// A temporary in-memory remote cannot be given a name with this method.
+    ///
+    /// No loaded instances of the remote with the old name will change their
+    /// name or their list of refspecs.
+    ///
+    /// The returned array of strings is a list of the non-default refspecs
+    /// which cannot be renamed and are returned for further processing by the
+    /// caller.
+    pub fn remote_rename(&self, name: &str, new_name: &str) -> Result<StringArray, Error> {
+        let name = CString::new(name)?;
+        let new_name = CString::new(new_name)?;
+        let mut problems = raw::git_strarray {
+            count: 0,
+            strings: ptr::null_mut(),
+        };
+        unsafe {
+            try_call!(raw::git_remote_rename(
+                &mut problems,
+                self.raw,
+                name,
+                new_name
+            ));
+            Ok(Binding::from_raw(problems))
+        }
+    }
+
+    /// Delete an existing persisted remote.
+    ///
+    /// All remote-tracking branches and configuration settings for the remote
+    /// will be removed.
+    pub fn remote_delete(&self, name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_remote_delete(self.raw, name));
+        }
+        Ok(())
+    }
+
+    /// Add a fetch refspec to the remote's configuration
+    ///
+    /// Add the given refspec to the fetch list in the configuration. No loaded
+    /// remote instances will be affected.
+    pub fn remote_add_fetch(&self, name: &str, spec: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let spec = CString::new(spec)?;
+        unsafe {
+            try_call!(raw::git_remote_add_fetch(self.raw, name, spec));
+        }
+        Ok(())
+    }
+
+    /// Add a push refspec to the remote's configuration.
+    ///
+    /// Add the given refspec to the push list in the configuration. No
+    /// loaded remote instances will be affected.
+    pub fn remote_add_push(&self, name: &str, spec: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let spec = CString::new(spec)?;
+        unsafe {
+            try_call!(raw::git_remote_add_push(self.raw, name, spec));
+        }
+        Ok(())
+    }
+
+    /// Set the remote's URL in the configuration
+    ///
+    /// Remote objects already in memory will not be affected. This assumes
+    /// the common case of a single-url remote and will otherwise return an
+    /// error.
+    pub fn remote_set_url(&self, name: &str, url: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let url = CString::new(url)?;
+        unsafe {
+            try_call!(raw::git_remote_set_url(self.raw, name, url));
+        }
+        Ok(())
+    }
+
+    /// Set the remote's URL for pushing in the configuration.
+    ///
+    /// Remote objects already in memory will not be affected. This assumes
+    /// the common case of a single-url remote and will otherwise return an
+    /// error.
+    ///
+    /// `None` indicates that it should be cleared.
+    pub fn remote_set_pushurl(&self, name: &str, pushurl: Option<&str>) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let pushurl = crate::opt_cstr(pushurl)?;
+        unsafe {
+            try_call!(raw::git_remote_set_pushurl(self.raw, name, pushurl));
+        }
+        Ok(())
+    }
+
+    /// Sets the current head to the specified object and optionally resets
+    /// the index and working tree to match.
+    ///
+    /// A soft reset means the head will be moved to the commit.
+    ///
+    /// A mixed reset will trigger a soft reset, plus the index will be
+    /// replaced with the content of the commit tree.
+    ///
+    /// A hard reset will trigger a mixed reset and the working directory will
+    /// be replaced with the content of the index. (Untracked and ignored files
+    /// will be left alone, however.)
+    ///
+    /// The `target` is a commit-ish to which the head should be moved to. The
+    /// object can either be a commit or a tag, but tags must be dereferenceable
+    /// to a commit.
+    ///
+    /// The `checkout` options will only be used for a hard reset.
+    pub fn reset(
+        &self,
+        target: &Object<'_>,
+        kind: ResetType,
+        checkout: Option<&mut CheckoutBuilder<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let mut opts: raw::git_checkout_options = mem::zeroed();
+            try_call!(raw::git_checkout_init_options(
+                &mut opts,
+                raw::GIT_CHECKOUT_OPTIONS_VERSION
+            ));
+            let opts = checkout.map(|c| {
+                c.configure(&mut opts);
+                &mut opts
+            });
+            try_call!(raw::git_reset(self.raw, target.raw(), kind, opts));
+        }
+        Ok(())
+    }
+
+    /// Updates some entries in the index from the target commit tree.
+    ///
+    /// The scope of the updated entries is determined by the paths being
+    /// in the iterator provided.
+    ///
+    /// Passing a `None` target will result in removing entries in the index
+    /// matching the provided pathspecs.
+    pub fn reset_default<T, I>(&self, target: Option<&Object<'_>>, paths: I) -> Result<(), Error>
+    where
+        T: IntoCString,
+        I: IntoIterator<Item = T>,
+    {
+        let (_a, _b, mut arr) = crate::util::iter2cstrs_paths(paths)?;
+        let target = target.map(|t| t.raw());
+        unsafe {
+            try_call!(raw::git_reset_default(self.raw, target, &mut arr));
+        }
+        Ok(())
+    }
+
+    /// Retrieve and resolve the reference pointed at by HEAD.
+    pub fn head(&self) -> Result<Reference<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_head(&mut ret, self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Make the repository HEAD point to the specified reference.
+    ///
+    /// If the provided reference points to a tree or a blob, the HEAD is
+    /// unaltered and an error is returned.
+    ///
+    /// If the provided reference points to a branch, the HEAD will point to
+    /// that branch, staying attached, or become attached if it isn't yet. If
+    /// the branch doesn't exist yet, no error will be returned. The HEAD will
+    /// then be attached to an unborn branch.
+    ///
+    /// Otherwise, the HEAD will be detached and will directly point to the
+    /// commit.
+    pub fn set_head(&self, refname: &str) -> Result<(), Error> {
+        self.set_head_bytes(refname.as_bytes())
+    }
+
+    /// Make the repository HEAD point to the specified reference as a byte array.
+    ///
+    /// If the provided reference points to a tree or a blob, the HEAD is
+    /// unaltered and an error is returned.
+    ///
+    /// If the provided reference points to a branch, the HEAD will point to
+    /// that branch, staying attached, or become attached if it isn't yet. If
+    /// the branch doesn't exist yet, no error will be returned. The HEAD will
+    /// then be attached to an unborn branch.
+    ///
+    /// Otherwise, the HEAD will be detached and will directly point to the
+    /// commit.
+    pub fn set_head_bytes(&self, refname: &[u8]) -> Result<(), Error> {
+        let refname = CString::new(refname)?;
+        unsafe {
+            try_call!(raw::git_repository_set_head(self.raw, refname));
+        }
+        Ok(())
+    }
+
+    /// Determines whether the repository HEAD is detached.
+    pub fn head_detached(&self) -> Result<bool, Error> {
+        unsafe {
+            let value = raw::git_repository_head_detached(self.raw);
+            match value {
+                0 => Ok(false),
+                1 => Ok(true),
+                _ => Err(Error::last_error(value)),
+            }
+        }
+    }
+
+    /// Make the repository HEAD directly point to the commit.
+    ///
+    /// If the provided commitish cannot be found in the repository, the HEAD
+    /// is unaltered and an error is returned.
+    ///
+    /// If the provided commitish cannot be peeled into a commit, the HEAD is
+    /// unaltered and an error is returned.
+    ///
+    /// Otherwise, the HEAD will eventually be detached and will directly point
+    /// to the peeled commit.
+    pub fn set_head_detached(&self, commitish: Oid) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_set_head_detached(
+                self.raw,
+                commitish.raw()
+            ));
+        }
+        Ok(())
+    }
+
+    /// Make the repository HEAD directly point to the commit.
+    ///
+    /// If the provided commitish cannot be found in the repository, the HEAD
+    /// is unaltered and an error is returned.
+    /// If the provided commitish cannot be peeled into a commit, the HEAD is
+    /// unaltered and an error is returned.
+    /// Otherwise, the HEAD will eventually be detached and will directly point
+    /// to the peeled commit.
+    pub fn set_head_detached_from_annotated(
+        &self,
+        commitish: AnnotatedCommit<'_>,
+    ) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_set_head_detached_from_annotated(
+                self.raw,
+                commitish.raw()
+            ));
+        }
+        Ok(())
+    }
+
+    /// Create an iterator for the repo's references
+    pub fn references(&self) -> Result<References<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_iterator_new(&mut ret, self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create an iterator for the repo's references that match the specified
+    /// glob
+    pub fn references_glob(&self, glob: &str) -> Result<References<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        let glob = CString::new(glob)?;
+        unsafe {
+            try_call!(raw::git_reference_iterator_glob_new(
+                &mut ret, self.raw, glob
+            ));
+
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Load all submodules for this repository and return them.
+    pub fn submodules(&self) -> Result<Vec<Submodule<'_>>, Error> {
+        struct Data<'a, 'b> {
+            repo: &'b Repository,
+            ret: &'a mut Vec<Submodule<'b>>,
+        }
+        let mut ret = Vec::new();
+
+        unsafe {
+            let mut data = Data {
+                repo: self,
+                ret: &mut ret,
+            };
+            let cb: raw::git_submodule_cb = Some(append);
+            try_call!(raw::git_submodule_foreach(
+                self.raw,
+                cb,
+                &mut data as *mut _ as *mut c_void
+            ));
+        }
+
+        return Ok(ret);
+
+        extern "C" fn append(
+            _repo: *mut raw::git_submodule,
+            name: *const c_char,
+            data: *mut c_void,
+        ) -> c_int {
+            unsafe {
+                let data = &mut *(data as *mut Data<'_, '_>);
+                let mut raw = ptr::null_mut();
+                let rc = raw::git_submodule_lookup(&mut raw, data.repo.raw(), name);
+                assert_eq!(rc, 0);
+                data.ret.push(Binding::from_raw(raw));
+            }
+            0
+        }
+    }
+
+    /// Gather file status information and populate the returned structure.
+    ///
+    /// Note that if a pathspec is given in the options to filter the
+    /// status, then the results from rename detection (if you enable it) may
+    /// not be accurate. To do rename detection properly, this must be called
+    /// with no pathspec so that all files can be considered.
+    pub fn statuses(&self, options: Option<&mut StatusOptions>) -> Result<Statuses<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_status_list_new(
+                &mut ret,
+                self.raw,
+                options.map(|s| s.raw()).unwrap_or(ptr::null())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Test if the ignore rules apply to a given file.
+    ///
+    /// This function checks the ignore rules to see if they would apply to the
+    /// given file. This indicates if the file would be ignored regardless of
+    /// whether the file is already in the index or committed to the repository.
+    ///
+    /// One way to think of this is if you were to do "git add ." on the
+    /// directory containing the file, would it be added or not?
+    pub fn status_should_ignore(&self, path: &Path) -> Result<bool, Error> {
+        let mut ret = 0 as c_int;
+        let path = util::cstring_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_status_should_ignore(&mut ret, self.raw, path));
+        }
+        Ok(ret != 0)
+    }
+
+    /// Get file status for a single file.
+    ///
+    /// This tries to get status for the filename that you give. If no files
+    /// match that name (in either the HEAD, index, or working directory), this
+    /// returns NotFound.
+    ///
+    /// If the name matches multiple files (for example, if the path names a
+    /// directory or if running on a case- insensitive filesystem and yet the
+    /// HEAD has two entries that both match the path), then this returns
+    /// Ambiguous because it cannot give correct results.
+    ///
+    /// This does not do any sort of rename detection. Renames require a set of
+    /// targets and because of the path filtering, there is not enough
+    /// information to check renames correctly. To check file status with rename
+    /// detection, there is no choice but to do a full `statuses` and scan
+    /// through looking for the path that you are interested in.
+    pub fn status_file(&self, path: &Path) -> Result<Status, Error> {
+        let mut ret = 0 as c_uint;
+        let path = path_to_repo_path(path)?;
+        unsafe {
+            try_call!(raw::git_status_file(&mut ret, self.raw, path));
+        }
+        Ok(Status::from_bits_truncate(ret as u32))
+    }
+
+    /// Create an iterator which loops over the requested branches.
+    pub fn branches(&self, filter: Option<BranchType>) -> Result<Branches<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_branch_iterator_new(&mut raw, self.raw(), filter));
+            Ok(Branches::from_raw(raw))
+        }
+    }
+
+    /// Get the Index file for this repository.
+    ///
+    /// If a custom index has not been set, the default index for the repository
+    /// will be returned (the one located in .git/index).
+    ///
+    /// **Caution**: If the [`Repository`] of this index is dropped, then this
+    /// [`Index`] will become detached, and most methods on it will fail. See
+    /// [`Index::open`]. Be sure the repository has a binding such as a local
+    /// variable to keep it alive at least as long as the index.
+    pub fn index(&self) -> Result<Index, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_index(&mut raw, self.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Set the Index file for this repository.
+    pub fn set_index(&self, index: &mut Index) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_set_index(self.raw(), index.raw()));
+        }
+        Ok(())
+    }
+
+    /// Get the configuration file for this repository.
+    ///
+    /// If a configuration file has not been set, the default config set for the
+    /// repository will be returned, including global and system configurations
+    /// (if they are available).
+    pub fn config(&self) -> Result<Config, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_config(&mut raw, self.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get the value of a git attribute for a path as a string.
+    ///
+    /// This function will return a special string if the attribute is set to a special value.
+    /// Interpreting the special string is discouraged. You should always use
+    /// [`AttrValue::from_string`](crate::AttrValue::from_string) to interpret the return value
+    /// and avoid the special string.
+    ///
+    /// As such, the return type of this function will probably be changed in the next major version
+    /// to prevent interpreting the returned string without checking whether it's special.
+    pub fn get_attr(
+        &self,
+        path: &Path,
+        name: &str,
+        flags: AttrCheckFlags,
+    ) -> Result<Option<&str>, Error> {
+        Ok(self
+            .get_attr_bytes(path, name, flags)?
+            .and_then(|a| str::from_utf8(a).ok()))
+    }
+
+    /// Get the value of a git attribute for a path as a byte slice.
+    ///
+    /// This function will return a special byte slice if the attribute is set to a special value.
+    /// Interpreting the special byte slice is discouraged. You should always use
+    /// [`AttrValue::from_bytes`](crate::AttrValue::from_bytes) to interpret the return value and
+    /// avoid the special string.
+    ///
+    /// As such, the return type of this function will probably be changed in the next major version
+    /// to prevent interpreting the returned byte slice without checking whether it's special.
+    pub fn get_attr_bytes(
+        &self,
+        path: &Path,
+        name: &str,
+        flags: AttrCheckFlags,
+    ) -> Result<Option<&[u8]>, Error> {
+        let mut ret = ptr::null();
+        let path = util::cstring_to_repo_path(path)?;
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_attr_get(
+                &mut ret,
+                self.raw(),
+                flags.bits(),
+                path,
+                name
+            ));
+            Ok(crate::opt_bytes(self, ret))
+        }
+    }
+
+    /// Write an in-memory buffer to the ODB as a blob.
+    ///
+    /// The Oid returned can in turn be passed to `find_blob` to get a handle to
+    /// the blob.
+    pub fn blob(&self, data: &[u8]) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            let ptr = data.as_ptr() as *const c_void;
+            let len = data.len() as size_t;
+            try_call!(raw::git_blob_create_frombuffer(
+                &mut raw,
+                self.raw(),
+                ptr,
+                len
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Read a file from the filesystem and write its content to the Object
+    /// Database as a loose blob
+    ///
+    /// The Oid returned can in turn be passed to `find_blob` to get a handle to
+    /// the blob.
+    pub fn blob_path(&self, path: &Path) -> Result<Oid, Error> {
+        // Normal file path OK (does not need Windows conversion).
+        let path = path.into_c_string()?;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_blob_create_fromdisk(&mut raw, self.raw(), path));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Create a stream to write blob
+    ///
+    /// This function may need to buffer the data on disk and will in general
+    /// not be the right choice if you know the size of the data to write.
+    ///
+    /// Use `BlobWriter::commit()` to commit the write to the object db
+    /// and get the object id.
+    ///
+    /// If the `hintpath` parameter is filled, it will be used to determine
+    /// what git filters should be applied to the object before it is written
+    /// to the object database.
+    pub fn blob_writer(&self, hintpath: Option<&Path>) -> Result<BlobWriter<'_>, Error> {
+        let path_str = match hintpath {
+            Some(path) => Some(path.into_c_string()?),
+            None => None,
+        };
+        let path = match path_str {
+            Some(ref path) => path.as_ptr(),
+            None => ptr::null(),
+        };
+        let mut out = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_blob_create_fromstream(&mut out, self.raw(), path));
+            Ok(BlobWriter::from_raw(out))
+        }
+    }
+
+    /// Lookup a reference to one of the objects in a repository.
+    pub fn find_blob(&self, oid: Oid) -> Result<Blob<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_blob_lookup(&mut raw, self.raw(), oid.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get the object database for this repository
+    pub fn odb(&self) -> Result<Odb<'_>, Error> {
+        let mut odb = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_repository_odb(&mut odb, self.raw()));
+            Ok(Odb::from_raw(odb))
+        }
+    }
+
+    /// Override the object database for this repository
+    pub fn set_odb(&self, odb: &Odb<'_>) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_set_odb(self.raw(), odb.raw()));
+        }
+        Ok(())
+    }
+
+    /// Create a new branch pointing at a target commit
+    ///
+    /// A new direct reference will be created pointing to this target commit.
+    /// If `force` is true and a reference already exists with the given name,
+    /// it'll be replaced.
+    pub fn branch(
+        &self,
+        branch_name: &str,
+        target: &Commit<'_>,
+        force: bool,
+    ) -> Result<Branch<'_>, Error> {
+        let branch_name = CString::new(branch_name)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_branch_create(
+                &mut raw,
+                self.raw(),
+                branch_name,
+                target.raw(),
+                force
+            ));
+            Ok(Branch::wrap(Binding::from_raw(raw)))
+        }
+    }
+
+    /// Create a new branch pointing at a target commit
+    ///
+    /// This behaves like `Repository::branch()` but takes
+    /// an annotated commit, which lets you specify which
+    /// extended SHA syntax string was specified by a user,
+    /// allowing for more exact reflog messages.
+    ///
+    /// See the documentation for `Repository::branch()`
+    pub fn branch_from_annotated_commit(
+        &self,
+        branch_name: &str,
+        target: &AnnotatedCommit<'_>,
+        force: bool,
+    ) -> Result<Branch<'_>, Error> {
+        let branch_name = CString::new(branch_name)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_branch_create_from_annotated(
+                &mut raw,
+                self.raw(),
+                branch_name,
+                target.raw(),
+                force
+            ));
+            Ok(Branch::wrap(Binding::from_raw(raw)))
+        }
+    }
+
+    /// Lookup a branch by its name in a repository.
+    pub fn find_branch(&self, name: &str, branch_type: BranchType) -> Result<Branch<'_>, Error> {
+        let name = CString::new(name)?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_branch_lookup(
+                &mut ret,
+                self.raw(),
+                name,
+                branch_type
+            ));
+            Ok(Branch::wrap(Binding::from_raw(ret)))
+        }
+    }
+
+    /// Create new commit in the repository
+    ///
+    /// If the `update_ref` is not `None`, name of the reference that will be
+    /// updated to point to this commit. If the reference is not direct, it will
+    /// be resolved to a direct reference. Use "HEAD" to update the HEAD of the
+    /// current branch and make it point to this commit. If the reference
+    /// doesn't exist yet, it will be created. If it does exist, the first
+    /// parent must be the tip of this branch.
+    pub fn commit(
+        &self,
+        update_ref: Option<&str>,
+        author: &Signature<'_>,
+        committer: &Signature<'_>,
+        message: &str,
+        tree: &Tree<'_>,
+        parents: &[&Commit<'_>],
+    ) -> Result<Oid, Error> {
+        let update_ref = crate::opt_cstr(update_ref)?;
+        let mut parent_ptrs = parents
+            .iter()
+            .map(|p| p.raw() as *const raw::git_commit)
+            .collect::<Vec<_>>();
+        let message = CString::new(message)?;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_commit_create(
+                &mut raw,
+                self.raw(),
+                update_ref,
+                author.raw(),
+                committer.raw(),
+                ptr::null(),
+                message,
+                tree.raw(),
+                parents.len() as size_t,
+                parent_ptrs.as_mut_ptr()
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Create a commit object and return that as a Buf.
+    ///
+    /// That can be converted to a string like this `str::from_utf8(&buf).unwrap().to_string()`.
+    /// And that string can be passed to the `commit_signed` function,
+    /// the arguments behave the same as in the `commit` function.
+    pub fn commit_create_buffer(
+        &self,
+        author: &Signature<'_>,
+        committer: &Signature<'_>,
+        message: &str,
+        tree: &Tree<'_>,
+        parents: &[&Commit<'_>],
+    ) -> Result<Buf, Error> {
+        let mut parent_ptrs = parents
+            .iter()
+            .map(|p| p.raw() as *const raw::git_commit)
+            .collect::<Vec<_>>();
+        let message = CString::new(message)?;
+        let buf = Buf::new();
+        unsafe {
+            try_call!(raw::git_commit_create_buffer(
+                buf.raw(),
+                self.raw(),
+                author.raw(),
+                committer.raw(),
+                ptr::null(),
+                message,
+                tree.raw(),
+                parents.len() as size_t,
+                parent_ptrs.as_mut_ptr()
+            ));
+            Ok(buf)
+        }
+    }
+
+    /// Create a commit object from the given buffer and signature
+    ///
+    /// Given the unsigned commit object's contents, its signature and the
+    /// header field in which to store the signature, attach the signature to
+    /// the commit and write it into the given repository.
+    ///
+    /// Use `None` in `signature_field` to use the default of `gpgsig`, which is
+    /// almost certainly what you want.
+    ///
+    /// Returns the resulting (signed) commit id.
+    pub fn commit_signed(
+        &self,
+        commit_content: &str,
+        signature: &str,
+        signature_field: Option<&str>,
+    ) -> Result<Oid, Error> {
+        let commit_content = CString::new(commit_content)?;
+        let signature = CString::new(signature)?;
+        let signature_field = crate::opt_cstr(signature_field)?;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_commit_create_with_signature(
+                &mut raw,
+                self.raw(),
+                commit_content,
+                signature,
+                signature_field
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Extract the signature from a commit
+    ///
+    /// Returns a tuple containing the signature in the first value and the
+    /// signed data in the second.
+    pub fn extract_signature(
+        &self,
+        commit_id: &Oid,
+        signature_field: Option<&str>,
+    ) -> Result<(Buf, Buf), Error> {
+        let signature_field = crate::opt_cstr(signature_field)?;
+        let signature = Buf::new();
+        let content = Buf::new();
+        unsafe {
+            try_call!(raw::git_commit_extract_signature(
+                signature.raw(),
+                content.raw(),
+                self.raw(),
+                commit_id.raw() as *mut _,
+                signature_field
+            ));
+            Ok((signature, content))
+        }
+    }
+
+    /// Lookup a reference to one of the commits in a repository.
+    pub fn find_commit(&self, oid: Oid) -> Result<Commit<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_commit_lookup(&mut raw, self.raw(), oid.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference to one of the commits in a repository by short hash.
+    pub fn find_commit_by_prefix(&self, prefix_hash: &str) -> Result<Commit<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_commit_lookup_prefix(
+                &mut raw,
+                self.raw(),
+                Oid::from_str(prefix_hash)?.raw(),
+                prefix_hash.len()
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Creates an `AnnotatedCommit` from the given commit id.
+    pub fn find_annotated_commit(&self, id: Oid) -> Result<AnnotatedCommit<'_>, Error> {
+        unsafe {
+            let mut raw = ptr::null_mut();
+            try_call!(raw::git_annotated_commit_lookup(
+                &mut raw,
+                self.raw(),
+                id.raw()
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference to one of the objects in a repository.
+    pub fn find_object(&self, oid: Oid, kind: Option<ObjectType>) -> Result<Object<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_object_lookup(
+                &mut raw,
+                self.raw(),
+                oid.raw(),
+                kind
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference to one of the objects by id prefix in a repository.
+    pub fn find_object_by_prefix(
+        &self,
+        prefix_hash: &str,
+        kind: Option<ObjectType>,
+    ) -> Result<Object<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_object_lookup_prefix(
+                &mut raw,
+                self.raw(),
+                Oid::from_str(prefix_hash)?.raw(),
+                prefix_hash.len(),
+                kind
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new direct reference.
+    ///
+    /// This function will return an error if a reference already exists with
+    /// the given name unless force is true, in which case it will be
+    /// overwritten.
+    pub fn reference(
+        &self,
+        name: &str,
+        id: Oid,
+        force: bool,
+        log_message: &str,
+    ) -> Result<Reference<'_>, Error> {
+        let name = CString::new(name)?;
+        let log_message = CString::new(log_message)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_create(
+                &mut raw,
+                self.raw(),
+                name,
+                id.raw(),
+                force,
+                log_message
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Conditionally create new direct reference.
+    ///
+    /// A direct reference (also called an object id reference) refers directly
+    /// to a specific object id (a.k.a. OID or SHA) in the repository.  The id
+    /// permanently refers to the object (although the reference itself can be
+    /// moved).  For example, in libgit2 the direct ref "refs/tags/v0.17.0"
+    /// refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977.
+    ///
+    /// The direct reference will be created in the repository and written to
+    /// the disk.
+    ///
+    /// Valid reference names must follow one of two patterns:
+    ///
+    /// 1. Top-level names must contain only capital letters and underscores,
+    ///    and must begin and end with a letter.  (e.g.  "HEAD", "ORIG_HEAD").
+    /// 2. Names prefixed with "refs/" can be almost anything.  You must avoid
+    ///    the characters `~`, `^`, `:`, `\\`, `?`, `[`, and `*`, and the
+    ///    sequences ".." and "@{" which have special meaning to revparse.
+    ///
+    /// This function will return an error if a reference already exists with
+    /// the given name unless `force` is true, in which case it will be
+    /// overwritten.
+    ///
+    /// The message for the reflog will be ignored if the reference does not
+    /// belong in the standard set (HEAD, branches and remote-tracking
+    /// branches) and it does not have a reflog.
+    ///
+    /// It will return GIT_EMODIFIED if the reference's value at the time of
+    /// updating does not match the one passed through `current_id` (i.e. if the
+    /// ref has changed since the user read it).
+    pub fn reference_matching(
+        &self,
+        name: &str,
+        id: Oid,
+        force: bool,
+        current_id: Oid,
+        log_message: &str,
+    ) -> Result<Reference<'_>, Error> {
+        let name = CString::new(name)?;
+        let log_message = CString::new(log_message)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_create_matching(
+                &mut raw,
+                self.raw(),
+                name,
+                id.raw(),
+                force,
+                current_id.raw(),
+                log_message
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new symbolic reference.
+    ///
+    /// A symbolic reference is a reference name that refers to another
+    /// reference name.  If the other name moves, the symbolic name will move,
+    /// too.  As a simple example, the "HEAD" reference might refer to
+    /// "refs/heads/master" while on the "master" branch of a repository.
+    ///
+    /// Valid reference names must follow one of two patterns:
+    ///
+    /// 1. Top-level names must contain only capital letters and underscores,
+    ///    and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+    /// 2. Names prefixed with "refs/" can be almost anything.  You must avoid
+    ///    the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+    ///    sequences ".." and "@{" which have special meaning to revparse.
+    ///
+    /// This function will return an error if a reference already exists with
+    /// the given name unless force is true, in which case it will be
+    /// overwritten.
+    pub fn reference_symbolic(
+        &self,
+        name: &str,
+        target: &str,
+        force: bool,
+        log_message: &str,
+    ) -> Result<Reference<'_>, Error> {
+        let name = CString::new(name)?;
+        let target = CString::new(target)?;
+        let log_message = CString::new(log_message)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_symbolic_create(
+                &mut raw,
+                self.raw(),
+                name,
+                target,
+                force,
+                log_message
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new symbolic reference.
+    ///
+    /// This function will return an error if a reference already exists with
+    /// the given name unless force is true, in which case it will be
+    /// overwritten.
+    ///
+    /// It will return GIT_EMODIFIED if the reference's value at the time of
+    /// updating does not match the one passed through current_value (i.e. if
+    /// the ref has changed since the user read it).
+    pub fn reference_symbolic_matching(
+        &self,
+        name: &str,
+        target: &str,
+        force: bool,
+        current_value: &str,
+        log_message: &str,
+    ) -> Result<Reference<'_>, Error> {
+        let name = CString::new(name)?;
+        let target = CString::new(target)?;
+        let current_value = CString::new(current_value)?;
+        let log_message = CString::new(log_message)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_symbolic_create_matching(
+                &mut raw,
+                self.raw(),
+                name,
+                target,
+                force,
+                current_value,
+                log_message
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference to one of the objects in a repository.
+    pub fn find_reference(&self, name: &str) -> Result<Reference<'_>, Error> {
+        let name = CString::new(name)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_lookup(&mut raw, self.raw(), name));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference to one of the objects in a repository.
+    /// `Repository::find_reference` with teeth; give the method your reference in
+    /// human-readable format e.g. 'main' instead of 'refs/heads/main', and it
+    /// will do-what-you-mean, returning the `Reference`.
+    pub fn resolve_reference_from_short_name(&self, refname: &str) -> Result<Reference<'_>, Error> {
+        let refname = CString::new(refname)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reference_dwim(&mut raw, self.raw(), refname));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a reference by name and resolve immediately to OID.
+    ///
+    /// This function provides a quick way to resolve a reference name straight
+    /// through to the object id that it refers to. This avoids having to
+    /// allocate or free any `Reference` objects for simple situations.
+    pub fn refname_to_id(&self, name: &str) -> Result<Oid, Error> {
+        let name = CString::new(name)?;
+        let mut ret = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_reference_name_to_id(&mut ret, self.raw(), name));
+            Ok(Binding::from_raw(&ret as *const _))
+        }
+    }
+
+    /// Creates a git_annotated_commit from the given reference.
+    pub fn reference_to_annotated_commit(
+        &self,
+        reference: &Reference<'_>,
+    ) -> Result<AnnotatedCommit<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_annotated_commit_from_ref(
+                &mut ret,
+                self.raw(),
+                reference.raw()
+            ));
+            Ok(AnnotatedCommit::from_raw(ret))
+        }
+    }
+
+    /// Creates a git_annotated_commit from FETCH_HEAD.
+    pub fn annotated_commit_from_fetchhead(
+        &self,
+        branch_name: &str,
+        remote_url: &str,
+        id: &Oid,
+    ) -> Result<AnnotatedCommit<'_>, Error> {
+        let branch_name = CString::new(branch_name)?;
+        let remote_url = CString::new(remote_url)?;
+
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_annotated_commit_from_fetchhead(
+                &mut ret,
+                self.raw(),
+                branch_name,
+                remote_url,
+                id.raw()
+            ));
+            Ok(AnnotatedCommit::from_raw(ret))
+        }
+    }
+
+    /// Create a new action signature with default user and now timestamp.
+    ///
+    /// This looks up the user.name and user.email from the configuration and
+    /// uses the current time as the timestamp, and creates a new signature
+    /// based on that information. It will return `NotFound` if either the
+    /// user.name or user.email are not set.
+    pub fn signature(&self) -> Result<Signature<'static>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_signature_default(&mut ret, self.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Set up a new git submodule for checkout.
+    ///
+    /// This does "git submodule add" up to the fetch and checkout of the
+    /// submodule contents. It preps a new submodule, creates an entry in
+    /// `.gitmodules` and creates an empty initialized repository either at the
+    /// given path in the working directory or in `.git/modules` with a gitlink
+    /// from the working directory to the new repo.
+    ///
+    /// To fully emulate "git submodule add" call this function, then `open()`
+    /// the submodule repo and perform the clone step as needed. Lastly, call
+    /// `add_finalize()` to wrap up adding the new submodule and `.gitmodules`
+    /// to the index to be ready to commit.
+    pub fn submodule(
+        &self,
+        url: &str,
+        path: &Path,
+        use_gitlink: bool,
+    ) -> Result<Submodule<'_>, Error> {
+        let url = CString::new(url)?;
+        let path = path_to_repo_path(path)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_submodule_add_setup(
+                &mut raw,
+                self.raw(),
+                url,
+                path,
+                use_gitlink
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup submodule information by name or path.
+    ///
+    /// Given either the submodule name or path (they are usually the same),
+    /// this returns a structure describing the submodule.
+    pub fn find_submodule(&self, name: &str) -> Result<Submodule<'_>, Error> {
+        let name = CString::new(name)?;
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_submodule_lookup(&mut raw, self.raw(), name));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get the status for a submodule.
+    ///
+    /// This looks at a submodule and tries to determine the status.  It
+    /// will return a combination of the `SubmoduleStatus` values.
+    pub fn submodule_status(
+        &self,
+        name: &str,
+        ignore: SubmoduleIgnore,
+    ) -> Result<SubmoduleStatus, Error> {
+        let mut ret = 0;
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_submodule_status(&mut ret, self.raw, name, ignore));
+        }
+        Ok(SubmoduleStatus::from_bits_truncate(ret as u32))
+    }
+
+    /// Set the ignore rule for the submodule in the configuration
+    ///
+    /// This does not affect any currently-loaded instances.
+    pub fn submodule_set_ignore(
+        &mut self,
+        name: &str,
+        ignore: SubmoduleIgnore,
+    ) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_submodule_set_ignore(self.raw(), name, ignore));
+        }
+        Ok(())
+    }
+
+    /// Set the update rule for the submodule in the configuration
+    ///
+    /// This setting won't affect any existing instances.
+    pub fn submodule_set_update(
+        &mut self,
+        name: &str,
+        update: SubmoduleUpdate,
+    ) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_submodule_set_update(self.raw(), name, update));
+        }
+        Ok(())
+    }
+
+    /// Set the URL for the submodule in the configuration
+    ///
+    /// After calling this, you may wish to call [`Submodule::sync`] to write
+    /// the changes to the checked out submodule repository.
+    pub fn submodule_set_url(&mut self, name: &str, url: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let url = CString::new(url)?;
+        unsafe {
+            try_call!(raw::git_submodule_set_url(self.raw(), name, url));
+        }
+        Ok(())
+    }
+
+    /// Set the branch for the submodule in the configuration
+    ///
+    /// After calling this, you may wish to call [`Submodule::sync`] to write
+    /// the changes to the checked out submodule repository.
+    pub fn submodule_set_branch(&mut self, name: &str, branch_name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        let branch_name = CString::new(branch_name)?;
+        unsafe {
+            try_call!(raw::git_submodule_set_branch(self.raw(), name, branch_name));
+        }
+        Ok(())
+    }
+
+    /// Lookup a reference to one of the objects in a repository.
+    pub fn find_tree(&self, oid: Oid) -> Result<Tree<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tree_lookup(&mut raw, self.raw(), oid.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new TreeBuilder, optionally initialized with the
+    /// entries of the given Tree.
+    ///
+    /// The tree builder can be used to create or modify trees in memory and
+    /// write them as tree objects to the database.
+    pub fn treebuilder(&self, tree: Option<&Tree<'_>>) -> Result<TreeBuilder<'_>, Error> {
+        unsafe {
+            let mut ret = ptr::null_mut();
+            let tree = match tree {
+                Some(tree) => tree.raw(),
+                None => ptr::null_mut(),
+            };
+            try_call!(raw::git_treebuilder_new(&mut ret, self.raw, tree));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a new tag in the repository from an object
+    ///
+    /// A new reference will also be created pointing to this tag object. If
+    /// `force` is true and a reference already exists with the given name,
+    /// it'll be replaced.
+    ///
+    /// The message will not be cleaned up.
+    ///
+    /// The tag name will be checked for validity. You must avoid the characters
+    /// '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences ".." and " @
+    /// {" which have special meaning to revparse.
+    pub fn tag(
+        &self,
+        name: &str,
+        target: &Object<'_>,
+        tagger: &Signature<'_>,
+        message: &str,
+        force: bool,
+    ) -> Result<Oid, Error> {
+        let name = CString::new(name)?;
+        let message = CString::new(message)?;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_tag_create(
+                &mut raw,
+                self.raw,
+                name,
+                target.raw(),
+                tagger.raw(),
+                message,
+                force
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Create a new tag in the repository from an object without creating a reference.
+    ///
+    /// The message will not be cleaned up.
+    ///
+    /// The tag name will be checked for validity. You must avoid the characters
+    /// '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences ".." and " @
+    /// {" which have special meaning to revparse.
+    pub fn tag_annotation_create(
+        &self,
+        name: &str,
+        target: &Object<'_>,
+        tagger: &Signature<'_>,
+        message: &str,
+    ) -> Result<Oid, Error> {
+        let name = CString::new(name)?;
+        let message = CString::new(message)?;
+        let mut raw_oid = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_tag_annotation_create(
+                &mut raw_oid,
+                self.raw,
+                name,
+                target.raw(),
+                tagger.raw(),
+                message
+            ));
+            Ok(Binding::from_raw(&raw_oid as *const _))
+        }
+    }
+
+    /// Create a new lightweight tag pointing at a target object
+    ///
+    /// A new direct reference will be created pointing to this target object.
+    /// If force is true and a reference already exists with the given name,
+    /// it'll be replaced.
+    pub fn tag_lightweight(
+        &self,
+        name: &str,
+        target: &Object<'_>,
+        force: bool,
+    ) -> Result<Oid, Error> {
+        let name = CString::new(name)?;
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_tag_create_lightweight(
+                &mut raw,
+                self.raw,
+                name,
+                target.raw(),
+                force
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Lookup a tag object from the repository.
+    pub fn find_tag(&self, id: Oid) -> Result<Tag<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tag_lookup(&mut raw, self.raw, id.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Lookup a tag object by prefix hash from the repository.
+    pub fn find_tag_by_prefix(&self, prefix_hash: &str) -> Result<Tag<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tag_lookup_prefix(
+                &mut raw,
+                self.raw,
+                Oid::from_str(prefix_hash)?.raw(),
+                prefix_hash.len()
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Delete an existing tag reference.
+    ///
+    /// The tag name will be checked for validity, see `tag` for some rules
+    /// about valid names.
+    pub fn tag_delete(&self, name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_tag_delete(self.raw, name));
+            Ok(())
+        }
+    }
+
+    /// Get a list with all the tags in the repository.
+    ///
+    /// An optional fnmatch pattern can also be specified.
+    pub fn tag_names(&self, pattern: Option<&str>) -> Result<StringArray, Error> {
+        let mut arr = raw::git_strarray {
+            strings: ptr::null_mut(),
+            count: 0,
+        };
+        unsafe {
+            match pattern {
+                Some(s) => {
+                    let s = CString::new(s)?;
+                    try_call!(raw::git_tag_list_match(&mut arr, s, self.raw));
+                }
+                None => {
+                    try_call!(raw::git_tag_list(&mut arr, self.raw));
+                }
+            }
+            Ok(Binding::from_raw(arr))
+        }
+    }
+
+    /// iterate over all tags calling `cb` on each.
+    /// the callback is provided the tag id and name
+    pub fn tag_foreach<T>(&self, cb: T) -> Result<(), Error>
+    where
+        T: FnMut(Oid, &[u8]) -> bool,
+    {
+        let mut data = TagForeachData {
+            cb: Box::new(cb) as TagForeachCB<'_>,
+        };
+
+        unsafe {
+            raw::git_tag_foreach(
+                self.raw,
+                Some(tag_foreach_cb),
+                (&mut data) as *mut _ as *mut _,
+            );
+        }
+        Ok(())
+    }
+
+    /// Updates files in the index and the working tree to match the content of
+    /// the commit pointed at by HEAD.
+    pub fn checkout_head(&self, opts: Option<&mut CheckoutBuilder<'_>>) -> Result<(), Error> {
+        unsafe {
+            let mut raw_opts = mem::zeroed();
+            try_call!(raw::git_checkout_init_options(
+                &mut raw_opts,
+                raw::GIT_CHECKOUT_OPTIONS_VERSION
+            ));
+            if let Some(c) = opts {
+                c.configure(&mut raw_opts);
+            }
+
+            try_call!(raw::git_checkout_head(self.raw, &raw_opts));
+        }
+        Ok(())
+    }
+
+    /// Updates files in the working tree to match the content of the index.
+    ///
+    /// If the index is `None`, the repository's index will be used.
+    pub fn checkout_index(
+        &self,
+        index: Option<&mut Index>,
+        opts: Option<&mut CheckoutBuilder<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let mut raw_opts = mem::zeroed();
+            try_call!(raw::git_checkout_init_options(
+                &mut raw_opts,
+                raw::GIT_CHECKOUT_OPTIONS_VERSION
+            ));
+            if let Some(c) = opts {
+                c.configure(&mut raw_opts);
+            }
+
+            try_call!(raw::git_checkout_index(
+                self.raw,
+                index.map(|i| &mut *i.raw()),
+                &raw_opts
+            ));
+        }
+        Ok(())
+    }
+
+    /// Updates files in the index and working tree to match the content of the
+    /// tree pointed at by the treeish.
+    pub fn checkout_tree(
+        &self,
+        treeish: &Object<'_>,
+        opts: Option<&mut CheckoutBuilder<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let mut raw_opts = mem::zeroed();
+            try_call!(raw::git_checkout_init_options(
+                &mut raw_opts,
+                raw::GIT_CHECKOUT_OPTIONS_VERSION
+            ));
+            if let Some(c) = opts {
+                c.configure(&mut raw_opts);
+            }
+
+            try_call!(raw::git_checkout_tree(self.raw, &*treeish.raw(), &raw_opts));
+        }
+        Ok(())
+    }
+
+    /// Merges the given commit(s) into HEAD, writing the results into the
+    /// working directory. Any changes are staged for commit and any conflicts
+    /// are written to the index. Callers should inspect the repository's index
+    /// after this completes, resolve any conflicts and prepare a commit.
+    ///
+    /// For compatibility with git, the repository is put into a merging state.
+    /// Once the commit is done (or if the user wishes to abort), you should
+    /// clear this state by calling cleanup_state().
+    pub fn merge(
+        &self,
+        annotated_commits: &[&AnnotatedCommit<'_>],
+        merge_opts: Option<&mut MergeOptions>,
+        checkout_opts: Option<&mut CheckoutBuilder<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let mut raw_checkout_opts = mem::zeroed();
+            try_call!(raw::git_checkout_init_options(
+                &mut raw_checkout_opts,
+                raw::GIT_CHECKOUT_OPTIONS_VERSION
+            ));
+            if let Some(c) = checkout_opts {
+                c.configure(&mut raw_checkout_opts);
+            }
+
+            let mut commit_ptrs = annotated_commits
+                .iter()
+                .map(|c| c.raw() as *const raw::git_annotated_commit)
+                .collect::<Vec<_>>();
+
+            try_call!(raw::git_merge(
+                self.raw,
+                commit_ptrs.as_mut_ptr(),
+                annotated_commits.len() as size_t,
+                merge_opts.map(|o| o.raw()).unwrap_or(ptr::null()),
+                &raw_checkout_opts
+            ));
+        }
+        Ok(())
+    }
+
+    /// Merge two commits, producing an index that reflects the result of
+    /// the merge. The index may be written as-is to the working directory or
+    /// checked out. If the index is to be converted to a tree, the caller
+    /// should resolve any conflicts that arose as part of the merge.
+    pub fn merge_commits(
+        &self,
+        our_commit: &Commit<'_>,
+        their_commit: &Commit<'_>,
+        opts: Option<&MergeOptions>,
+    ) -> Result<Index, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_merge_commits(
+                &mut raw,
+                self.raw,
+                our_commit.raw(),
+                their_commit.raw(),
+                opts.map(|o| o.raw())
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Merge two trees, producing an index that reflects the result of
+    /// the merge. The index may be written as-is to the working directory or
+    /// checked out. If the index is to be converted to a tree, the caller
+    /// should resolve any conflicts that arose as part of the merge.
+    pub fn merge_trees(
+        &self,
+        ancestor_tree: &Tree<'_>,
+        our_tree: &Tree<'_>,
+        their_tree: &Tree<'_>,
+        opts: Option<&MergeOptions>,
+    ) -> Result<Index, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_merge_trees(
+                &mut raw,
+                self.raw,
+                ancestor_tree.raw(),
+                our_tree.raw(),
+                their_tree.raw(),
+                opts.map(|o| o.raw())
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Remove all the metadata associated with an ongoing command like merge,
+    /// revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc.
+    pub fn cleanup_state(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_repository_state_cleanup(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Analyzes the given branch(es) and determines the opportunities for
+    /// merging them into the HEAD of the repository.
+    pub fn merge_analysis(
+        &self,
+        their_heads: &[&AnnotatedCommit<'_>],
+    ) -> Result<(MergeAnalysis, MergePreference), Error> {
+        unsafe {
+            let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t;
+            let mut raw_merge_preference = 0 as raw::git_merge_preference_t;
+            let mut their_heads = their_heads
+                .iter()
+                .map(|v| v.raw() as *const _)
+                .collect::<Vec<_>>();
+            try_call!(raw::git_merge_analysis(
+                &mut raw_merge_analysis,
+                &mut raw_merge_preference,
+                self.raw,
+                their_heads.as_mut_ptr() as *mut _,
+                their_heads.len()
+            ));
+            Ok((
+                MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32),
+                MergePreference::from_bits_truncate(raw_merge_preference as u32),
+            ))
+        }
+    }
+
+    /// Analyzes the given branch(es) and determines the opportunities for
+    /// merging them into a reference.
+    pub fn merge_analysis_for_ref(
+        &self,
+        our_ref: &Reference<'_>,
+        their_heads: &[&AnnotatedCommit<'_>],
+    ) -> Result<(MergeAnalysis, MergePreference), Error> {
+        unsafe {
+            let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t;
+            let mut raw_merge_preference = 0 as raw::git_merge_preference_t;
+            let mut their_heads = their_heads
+                .iter()
+                .map(|v| v.raw() as *const _)
+                .collect::<Vec<_>>();
+            try_call!(raw::git_merge_analysis_for_ref(
+                &mut raw_merge_analysis,
+                &mut raw_merge_preference,
+                self.raw,
+                our_ref.raw(),
+                their_heads.as_mut_ptr() as *mut _,
+                their_heads.len()
+            ));
+            Ok((
+                MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32),
+                MergePreference::from_bits_truncate(raw_merge_preference as u32),
+            ))
+        }
+    }
+
+    /// Initializes a rebase operation to rebase the changes in `branch`
+    /// relative to `upstream` onto another branch. To begin the rebase process,
+    /// call `next()`.
+    pub fn rebase(
+        &self,
+        branch: Option<&AnnotatedCommit<'_>>,
+        upstream: Option<&AnnotatedCommit<'_>>,
+        onto: Option<&AnnotatedCommit<'_>>,
+        opts: Option<&mut RebaseOptions<'_>>,
+    ) -> Result<Rebase<'_>, Error> {
+        let mut rebase: *mut raw::git_rebase = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_rebase_init(
+                &mut rebase,
+                self.raw(),
+                branch.map(|c| c.raw()),
+                upstream.map(|c| c.raw()),
+                onto.map(|c| c.raw()),
+                opts.map(|o| o.raw()).unwrap_or(ptr::null())
+            ));
+
+            Ok(Rebase::from_raw(rebase))
+        }
+    }
+
+    /// Opens an existing rebase that was previously started by either an
+    /// invocation of `rebase()` or by another client.
+    pub fn open_rebase(&self, opts: Option<&mut RebaseOptions<'_>>) -> Result<Rebase<'_>, Error> {
+        let mut rebase: *mut raw::git_rebase = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_rebase_open(
+                &mut rebase,
+                self.raw(),
+                opts.map(|o| o.raw()).unwrap_or(ptr::null())
+            ));
+            Ok(Rebase::from_raw(rebase))
+        }
+    }
+
+    /// Add a note for an object
+    ///
+    /// The `notes_ref` argument is the canonical name of the reference to use,
+    /// defaulting to "refs/notes/commits". If `force` is specified then
+    /// previous notes are overwritten.
+    pub fn note(
+        &self,
+        author: &Signature<'_>,
+        committer: &Signature<'_>,
+        notes_ref: Option<&str>,
+        oid: Oid,
+        note: &str,
+        force: bool,
+    ) -> Result<Oid, Error> {
+        let notes_ref = crate::opt_cstr(notes_ref)?;
+        let note = CString::new(note)?;
+        let mut ret = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_note_create(
+                &mut ret,
+                self.raw,
+                notes_ref,
+                author.raw(),
+                committer.raw(),
+                oid.raw(),
+                note,
+                force
+            ));
+            Ok(Binding::from_raw(&ret as *const _))
+        }
+    }
+
+    /// Get the default notes reference for this repository
+    pub fn note_default_ref(&self) -> Result<String, Error> {
+        let ret = Buf::new();
+        unsafe {
+            try_call!(raw::git_note_default_ref(ret.raw(), self.raw));
+        }
+        Ok(str::from_utf8(&ret).unwrap().to_string())
+    }
+
+    /// Creates a new iterator for notes in this repository.
+    ///
+    /// The `notes_ref` argument is the canonical name of the reference to use,
+    /// defaulting to "refs/notes/commits".
+    ///
+    /// The iterator returned yields pairs of (Oid, Oid) where the first element
+    /// is the id of the note and the second id is the id the note is
+    /// annotating.
+    pub fn notes(&self, notes_ref: Option<&str>) -> Result<Notes<'_>, Error> {
+        let notes_ref = crate::opt_cstr(notes_ref)?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_note_iterator_new(&mut ret, self.raw, notes_ref));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Read the note for an object.
+    ///
+    /// The `notes_ref` argument is the canonical name of the reference to use,
+    /// defaulting to "refs/notes/commits".
+    ///
+    /// The id specified is the Oid of the git object to read the note from.
+    pub fn find_note(&self, notes_ref: Option<&str>, id: Oid) -> Result<Note<'_>, Error> {
+        let notes_ref = crate::opt_cstr(notes_ref)?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_note_read(&mut ret, self.raw, notes_ref, id.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Remove the note for an object.
+    ///
+    /// The `notes_ref` argument is the canonical name of the reference to use,
+    /// defaulting to "refs/notes/commits".
+    ///
+    /// The id specified is the Oid of the git object to remove the note from.
+    pub fn note_delete(
+        &self,
+        id: Oid,
+        notes_ref: Option<&str>,
+        author: &Signature<'_>,
+        committer: &Signature<'_>,
+    ) -> Result<(), Error> {
+        let notes_ref = crate::opt_cstr(notes_ref)?;
+        unsafe {
+            try_call!(raw::git_note_remove(
+                self.raw,
+                notes_ref,
+                author.raw(),
+                committer.raw(),
+                id.raw()
+            ));
+            Ok(())
+        }
+    }
+
+    /// Create a revwalk that can be used to traverse the commit graph.
+    pub fn revwalk(&self) -> Result<Revwalk<'_>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_revwalk_new(&mut raw, self.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Get the blame for a single file.
+    pub fn blame_file(
+        &self,
+        path: &Path,
+        opts: Option<&mut BlameOptions>,
+    ) -> Result<Blame<'_>, Error> {
+        let path = path_to_repo_path(path)?;
+        let mut raw = ptr::null_mut();
+
+        unsafe {
+            try_call!(raw::git_blame_file(
+                &mut raw,
+                self.raw(),
+                path,
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Find a merge base between two commits
+    pub fn merge_base(&self, one: Oid, two: Oid) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_merge_base(
+                &mut raw,
+                self.raw,
+                one.raw(),
+                two.raw()
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Find a merge base given a list of commits
+    ///
+    /// This behaves similar to [`git merge-base`](https://git-scm.com/docs/git-merge-base#_discussion).
+    /// Given three commits `a`, `b`, and `c`, `merge_base_many(&[a, b, c])`
+    /// will compute a hypothetical commit `m`, which is a merge between `b`
+    /// and `c`.
+    ///
+    /// For example, with the following topology:
+    /// ```text
+    ///        o---o---o---o---C
+    ///       /
+    ///      /   o---o---o---B
+    ///     /   /
+    /// ---2---1---o---o---o---A
+    /// ```
+    ///
+    /// the result of `merge_base_many(&[a, b, c])` is 1. This is because the
+    /// equivalent topology with a merge commit `m` between `b` and `c` would
+    /// is:
+    /// ```text
+    ///        o---o---o---o---o
+    ///       /                 \
+    ///      /   o---o---o---o---M
+    ///     /   /
+    /// ---2---1---o---o---o---A
+    /// ```
+    ///
+    /// and the result of `merge_base_many(&[a, m])` is 1.
+    ///
+    /// ---
+    ///
+    /// If you're looking to recieve the common merge base between all the
+    /// given commits, use [`Self::merge_base_octopus`].
+    pub fn merge_base_many(&self, oids: &[Oid]) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+
+        unsafe {
+            try_call!(raw::git_merge_base_many(
+                &mut raw,
+                self.raw,
+                oids.len() as size_t,
+                oids.as_ptr() as *const raw::git_oid
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Find a common merge base between all given a list of commits
+    pub fn merge_base_octopus(&self, oids: &[Oid]) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+
+        unsafe {
+            try_call!(raw::git_merge_base_octopus(
+                &mut raw,
+                self.raw,
+                oids.len() as size_t,
+                oids.as_ptr() as *const raw::git_oid
+            ));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+
+    /// Find all merge bases between two commits
+    pub fn merge_bases(&self, one: Oid, two: Oid) -> Result<OidArray, Error> {
+        let mut arr = raw::git_oidarray {
+            ids: ptr::null_mut(),
+            count: 0,
+        };
+        unsafe {
+            try_call!(raw::git_merge_bases(
+                &mut arr,
+                self.raw,
+                one.raw(),
+                two.raw()
+            ));
+            Ok(Binding::from_raw(arr))
+        }
+    }
+
+    /// Find all merge bases given a list of commits
+    pub fn merge_bases_many(&self, oids: &[Oid]) -> Result<OidArray, Error> {
+        let mut arr = raw::git_oidarray {
+            ids: ptr::null_mut(),
+            count: 0,
+        };
+        unsafe {
+            try_call!(raw::git_merge_bases_many(
+                &mut arr,
+                self.raw,
+                oids.len() as size_t,
+                oids.as_ptr() as *const raw::git_oid
+            ));
+            Ok(Binding::from_raw(arr))
+        }
+    }
+
+    /// Count the number of unique commits between two commit objects
+    ///
+    /// There is no need for branches containing the commits to have any
+    /// upstream relationship, but it helps to think of one as a branch and the
+    /// other as its upstream, the ahead and behind values will be what git
+    /// would report for the branches.
+    pub fn graph_ahead_behind(&self, local: Oid, upstream: Oid) -> Result<(usize, usize), Error> {
+        unsafe {
+            let mut ahead: size_t = 0;
+            let mut behind: size_t = 0;
+            try_call!(raw::git_graph_ahead_behind(
+                &mut ahead,
+                &mut behind,
+                self.raw(),
+                local.raw(),
+                upstream.raw()
+            ));
+            Ok((ahead as usize, behind as usize))
+        }
+    }
+
+    /// Determine if a commit is the descendant of another commit
+    ///
+    /// Note that a commit is not considered a descendant of itself, in contrast
+    /// to `git merge-base --is-ancestor`.
+    pub fn graph_descendant_of(&self, commit: Oid, ancestor: Oid) -> Result<bool, Error> {
+        unsafe {
+            let rv = try_call!(raw::git_graph_descendant_of(
+                self.raw(),
+                commit.raw(),
+                ancestor.raw()
+            ));
+            Ok(rv != 0)
+        }
+    }
+
+    /// Read the reflog for the given reference
+    ///
+    /// If there is no reflog file for the given reference yet, an empty reflog
+    /// object will be returned.
+    pub fn reflog(&self, name: &str) -> Result<Reflog, Error> {
+        let name = CString::new(name)?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_reflog_read(&mut ret, self.raw, name));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Delete the reflog for the given reference
+    pub fn reflog_delete(&self, name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_reflog_delete(self.raw, name));
+        }
+        Ok(())
+    }
+
+    /// Rename a reflog
+    ///
+    /// The reflog to be renamed is expected to already exist.
+    pub fn reflog_rename(&self, old_name: &str, new_name: &str) -> Result<(), Error> {
+        let old_name = CString::new(old_name)?;
+        let new_name = CString::new(new_name)?;
+        unsafe {
+            try_call!(raw::git_reflog_rename(self.raw, old_name, new_name));
+        }
+        Ok(())
+    }
+
+    /// Check if the given reference has a reflog.
+    pub fn reference_has_log(&self, name: &str) -> Result<bool, Error> {
+        let name = CString::new(name)?;
+        let ret = unsafe { try_call!(raw::git_reference_has_log(self.raw, name)) };
+        Ok(ret != 0)
+    }
+
+    /// Ensure that the given reference has a reflog.
+    pub fn reference_ensure_log(&self, name: &str) -> Result<(), Error> {
+        let name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_reference_ensure_log(self.raw, name));
+        }
+        Ok(())
+    }
+
+    /// Describes a commit
+    ///
+    /// Performs a describe operation on the current commit and the worktree.
+    /// After performing a describe on HEAD, a status is run and description is
+    /// considered to be dirty if there are.
+    pub fn describe(&self, opts: &DescribeOptions) -> Result<Describe<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_describe_workdir(&mut ret, self.raw, opts.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Directly run a diff on two blobs.
+    ///
+    /// Compared to a file, a blob lacks some contextual information. As such, the
+    /// `DiffFile` given to the callback will have some fake data; i.e. mode will be
+    /// 0 and path will be `None`.
+    ///
+    /// `None` is allowed for either `old_blob` or `new_blob` and will be treated
+    /// as an empty blob, with the oid set to zero in the `DiffFile`. Passing `None`
+    /// for both blobs is a noop; no callbacks will be made at all.
+    ///
+    /// We do run a binary content check on the blob content and if either blob looks
+    /// like binary data, the `DiffFile` binary attribute will be set to 1 and no call to
+    /// the `hunk_cb` nor `line_cb` will be made (unless you set the `force_text`
+    /// option).
+    pub fn diff_blobs(
+        &self,
+        old_blob: Option<&Blob<'_>>,
+        old_as_path: Option<&str>,
+        new_blob: Option<&Blob<'_>>,
+        new_as_path: Option<&str>,
+        opts: Option<&mut DiffOptions>,
+        file_cb: Option<&mut FileCb<'_>>,
+        binary_cb: Option<&mut BinaryCb<'_>>,
+        hunk_cb: Option<&mut HunkCb<'_>>,
+        line_cb: Option<&mut LineCb<'_>>,
+    ) -> Result<(), Error> {
+        let old_as_path = crate::opt_cstr(old_as_path)?;
+        let new_as_path = crate::opt_cstr(new_as_path)?;
+        let mut cbs = DiffCallbacks {
+            file: file_cb,
+            binary: binary_cb,
+            hunk: hunk_cb,
+            line: line_cb,
+        };
+        let ptr = &mut cbs as *mut _;
+        unsafe {
+            let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() {
+                Some(file_cb_c)
+            } else {
+                None
+            };
+            let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() {
+                Some(binary_cb_c)
+            } else {
+                None
+            };
+            let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() {
+                Some(hunk_cb_c)
+            } else {
+                None
+            };
+            let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() {
+                Some(line_cb_c)
+            } else {
+                None
+            };
+            try_call!(raw::git_diff_blobs(
+                old_blob.map(|s| s.raw()),
+                old_as_path,
+                new_blob.map(|s| s.raw()),
+                new_as_path,
+                opts.map(|s| s.raw()),
+                file_cb_c,
+                binary_cb_c,
+                hunk_cb_c,
+                line_cb_c,
+                ptr as *mut _
+            ));
+            Ok(())
+        }
+    }
+
+    /// Create a diff with the difference between two tree objects.
+    ///
+    /// This is equivalent to `git diff <old-tree> <new-tree>`
+    ///
+    /// The first tree will be used for the "old_file" side of the delta and the
+    /// second tree will be used for the "new_file" side of the delta.  You can
+    /// pass `None` to indicate an empty tree, although it is an error to pass
+    /// `None` for both the `old_tree` and `new_tree`.
+    pub fn diff_tree_to_tree(
+        &self,
+        old_tree: Option<&Tree<'_>>,
+        new_tree: Option<&Tree<'_>>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_tree_to_tree(
+                &mut ret,
+                self.raw(),
+                old_tree.map(|s| s.raw()),
+                new_tree.map(|s| s.raw()),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a diff between a tree and repository index.
+    ///
+    /// This is equivalent to `git diff --cached <treeish>` or if you pass
+    /// the HEAD tree, then like `git diff --cached`.
+    ///
+    /// The tree you pass will be used for the "old_file" side of the delta, and
+    /// the index will be used for the "new_file" side of the delta.
+    ///
+    /// If you pass `None` for the index, then the existing index of the `repo`
+    /// will be used.  In this case, the index will be refreshed from disk
+    /// (if it has changed) before the diff is generated.
+    ///
+    /// If the tree is `None`, then it is considered an empty tree.
+    pub fn diff_tree_to_index(
+        &self,
+        old_tree: Option<&Tree<'_>>,
+        index: Option<&Index>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_tree_to_index(
+                &mut ret,
+                self.raw(),
+                old_tree.map(|s| s.raw()),
+                index.map(|s| s.raw()),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a diff between two index objects.
+    ///
+    /// The first index will be used for the "old_file" side of the delta, and
+    /// the second index will be used for the "new_file" side of the delta.
+    pub fn diff_index_to_index(
+        &self,
+        old_index: &Index,
+        new_index: &Index,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_index_to_index(
+                &mut ret,
+                self.raw(),
+                old_index.raw(),
+                new_index.raw(),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a diff between the repository index and the workdir directory.
+    ///
+    /// This matches the `git diff` command.  See the note below on
+    /// `tree_to_workdir` for a discussion of the difference between
+    /// `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
+    /// using libgit2.
+    ///
+    /// The index will be used for the "old_file" side of the delta, and the
+    /// working directory will be used for the "new_file" side of the delta.
+    ///
+    /// If you pass `None` for the index, then the existing index of the `repo`
+    /// will be used.  In this case, the index will be refreshed from disk
+    /// (if it has changed) before the diff is generated.
+    pub fn diff_index_to_workdir(
+        &self,
+        index: Option<&Index>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_index_to_workdir(
+                &mut ret,
+                self.raw(),
+                index.map(|s| s.raw()),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a diff between a tree and the working directory.
+    ///
+    /// The tree you provide will be used for the "old_file" side of the delta,
+    /// and the working directory will be used for the "new_file" side.
+    ///
+    /// This is not the same as `git diff <treeish>` or `git diff-index
+    /// <treeish>`.  Those commands use information from the index, whereas this
+    /// function strictly returns the differences between the tree and the files
+    /// in the working directory, regardless of the state of the index.  Use
+    /// `tree_to_workdir_with_index` to emulate those commands.
+    ///
+    /// To see difference between this and `tree_to_workdir_with_index`,
+    /// consider the example of a staged file deletion where the file has then
+    /// been put back into the working dir and further modified.  The
+    /// tree-to-workdir diff for that file is 'modified', but `git diff` would
+    /// show status 'deleted' since there is a staged delete.
+    ///
+    /// If `None` is passed for `tree`, then an empty tree is used.
+    pub fn diff_tree_to_workdir(
+        &self,
+        old_tree: Option<&Tree<'_>>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_tree_to_workdir(
+                &mut ret,
+                self.raw(),
+                old_tree.map(|s| s.raw()),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a diff between a tree and the working directory using index data
+    /// to account for staged deletes, tracked files, etc.
+    ///
+    /// This emulates `git diff <tree>` by diffing the tree to the index and
+    /// the index to the working directory and blending the results into a
+    /// single diff that includes staged deleted, etc.
+    pub fn diff_tree_to_workdir_with_index(
+        &self,
+        old_tree: Option<&Tree<'_>>,
+        opts: Option<&mut DiffOptions>,
+    ) -> Result<Diff<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_diff_tree_to_workdir_with_index(
+                &mut ret,
+                self.raw(),
+                old_tree.map(|s| s.raw()),
+                opts.map(|s| s.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a PackBuilder
+    pub fn packbuilder(&self) -> Result<PackBuilder<'_>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_packbuilder_new(&mut ret, self.raw()));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Save the local modifications to a new stash.
+    pub fn stash_save(
+        &mut self,
+        stasher: &Signature<'_>,
+        message: &str,
+        flags: Option<StashFlags>,
+    ) -> Result<Oid, Error> {
+        self.stash_save2(stasher, Some(message), flags)
+    }
+
+    /// Save the local modifications to a new stash.
+    /// unlike `stash_save` it allows to pass a null `message`
+    pub fn stash_save2(
+        &mut self,
+        stasher: &Signature<'_>,
+        message: Option<&str>,
+        flags: Option<StashFlags>,
+    ) -> Result<Oid, Error> {
+        unsafe {
+            let mut raw_oid = raw::git_oid {
+                id: [0; raw::GIT_OID_RAWSZ],
+            };
+            let message = crate::opt_cstr(message)?;
+            let flags = flags.unwrap_or_else(StashFlags::empty);
+            try_call!(raw::git_stash_save(
+                &mut raw_oid,
+                self.raw(),
+                stasher.raw(),
+                message,
+                flags.bits() as c_uint
+            ));
+            Ok(Binding::from_raw(&raw_oid as *const _))
+        }
+    }
+
+    /// Like `stash_save` but with more options like selective statshing via path patterns.
+    pub fn stash_save_ext(
+        &mut self,
+        opts: Option<&mut StashSaveOptions<'_>>,
+    ) -> Result<Oid, Error> {
+        unsafe {
+            let mut raw_oid = raw::git_oid {
+                id: [0; raw::GIT_OID_RAWSZ],
+            };
+            let opts = opts.map(|opts| opts.raw());
+            try_call!(raw::git_stash_save_with_opts(
+                &mut raw_oid,
+                self.raw(),
+                opts
+            ));
+            Ok(Binding::from_raw(&raw_oid as *const _))
+        }
+    }
+
+    /// Apply a single stashed state from the stash list.
+    pub fn stash_apply(
+        &mut self,
+        index: usize,
+        opts: Option<&mut StashApplyOptions<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let opts = opts.map(|opts| opts.raw());
+            try_call!(raw::git_stash_apply(self.raw(), index, opts));
+            Ok(())
+        }
+    }
+
+    /// Loop over all the stashed states and issue a callback for each one.
+    ///
+    /// Return `true` to continue iterating or `false` to stop.
+    pub fn stash_foreach<C>(&mut self, mut callback: C) -> Result<(), Error>
+    where
+        C: FnMut(usize, &str, &Oid) -> bool,
+    {
+        unsafe {
+            let mut data = StashCbData {
+                callback: &mut callback,
+            };
+            let cb: raw::git_stash_cb = Some(stash_cb);
+            try_call!(raw::git_stash_foreach(
+                self.raw(),
+                cb,
+                &mut data as *mut _ as *mut _
+            ));
+            Ok(())
+        }
+    }
+
+    /// Remove a single stashed state from the stash list.
+    pub fn stash_drop(&mut self, index: usize) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_stash_drop(self.raw(), index));
+            Ok(())
+        }
+    }
+
+    /// Apply a single stashed state from the stash list and remove it from the list if successful.
+    pub fn stash_pop(
+        &mut self,
+        index: usize,
+        opts: Option<&mut StashApplyOptions<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let opts = opts.map(|opts| opts.raw());
+            try_call!(raw::git_stash_pop(self.raw(), index, opts));
+            Ok(())
+        }
+    }
+
+    /// Add ignore rules for a repository.
+    ///
+    /// The format of the rules is the same one of the .gitignore file.
+    pub fn add_ignore_rule(&self, rules: &str) -> Result<(), Error> {
+        let rules = CString::new(rules)?;
+        unsafe {
+            try_call!(raw::git_ignore_add_rule(self.raw, rules));
+        }
+        Ok(())
+    }
+
+    /// Clear ignore rules that were explicitly added.
+    pub fn clear_ignore_rules(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_ignore_clear_internal_rules(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Test if the ignore rules apply to a given path.
+    pub fn is_path_ignored<P: AsRef<Path>>(&self, path: P) -> Result<bool, Error> {
+        let path = util::cstring_to_repo_path(path.as_ref())?;
+        let mut ignored: c_int = 0;
+        unsafe {
+            try_call!(raw::git_ignore_path_is_ignored(
+                &mut ignored,
+                self.raw,
+                path
+            ));
+        }
+        Ok(ignored == 1)
+    }
+
+    /// Perform a cherrypick
+    pub fn cherrypick(
+        &self,
+        commit: &Commit<'_>,
+        options: Option<&mut CherrypickOptions<'_>>,
+    ) -> Result<(), Error> {
+        let raw_opts = options.map(|o| o.raw());
+        let ptr_raw_opts = match raw_opts.as_ref() {
+            Some(v) => v,
+            None => std::ptr::null(),
+        };
+        unsafe {
+            try_call!(raw::git_cherrypick(self.raw(), commit.raw(), ptr_raw_opts));
+
+            Ok(())
+        }
+    }
+
+    /// Create an index of uncommitted changes, representing the result of
+    /// cherry-picking.
+    pub fn cherrypick_commit(
+        &self,
+        cherrypick_commit: &Commit<'_>,
+        our_commit: &Commit<'_>,
+        mainline: u32,
+        options: Option<&MergeOptions>,
+    ) -> Result<Index, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_cherrypick_commit(
+                &mut ret,
+                self.raw(),
+                cherrypick_commit.raw(),
+                our_commit.raw(),
+                mainline,
+                options.map(|o| o.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Find the remote name of a remote-tracking branch
+    pub fn branch_remote_name(&self, refname: &str) -> Result<Buf, Error> {
+        let refname = CString::new(refname)?;
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_branch_remote_name(buf.raw(), self.raw, refname));
+            Ok(buf)
+        }
+    }
+
+    /// Retrieves the name of the reference supporting the remote tracking branch,
+    /// given the name of a local branch reference.
+    pub fn branch_upstream_name(&self, refname: &str) -> Result<Buf, Error> {
+        let refname = CString::new(refname)?;
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_branch_upstream_name(buf.raw(), self.raw, refname));
+            Ok(buf)
+        }
+    }
+
+    /// Retrieve the name of the upstream remote of a local branch.
+    ///
+    /// `refname` must be in the form `refs/heads/{branch_name}`
+    pub fn branch_upstream_remote(&self, refname: &str) -> Result<Buf, Error> {
+        let refname = CString::new(refname)?;
+        unsafe {
+            let buf = Buf::new();
+            try_call!(raw::git_branch_upstream_remote(
+                buf.raw(),
+                self.raw,
+                refname
+            ));
+            Ok(buf)
+        }
+    }
+
+    /// Apply a Diff to the given repo, making changes directly in the working directory, the index, or both.
+    pub fn apply(
+        &self,
+        diff: &Diff<'_>,
+        location: ApplyLocation,
+        options: Option<&mut ApplyOptions<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_apply(
+                self.raw,
+                diff.raw(),
+                location.raw(),
+                options.map(|s| s.raw()).unwrap_or(ptr::null())
+            ));
+
+            Ok(())
+        }
+    }
+
+    /// Apply a Diff to the provided tree, and return the resulting Index.
+    pub fn apply_to_tree(
+        &self,
+        tree: &Tree<'_>,
+        diff: &Diff<'_>,
+        options: Option<&mut ApplyOptions<'_>>,
+    ) -> Result<Index, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_apply_to_tree(
+                &mut ret,
+                self.raw,
+                tree.raw(),
+                diff.raw(),
+                options.map(|s| s.raw()).unwrap_or(ptr::null())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Reverts the given commit, producing changes in the index and working directory.
+    pub fn revert(
+        &self,
+        commit: &Commit<'_>,
+        options: Option<&mut RevertOptions<'_>>,
+    ) -> Result<(), Error> {
+        let raw_opts = options.map(|o| o.raw());
+        let ptr_raw_opts = match raw_opts.as_ref() {
+            Some(v) => v,
+            None => 0 as *const _,
+        };
+        unsafe {
+            try_call!(raw::git_revert(self.raw(), commit.raw(), ptr_raw_opts));
+            Ok(())
+        }
+    }
+
+    /// Reverts the given commit against the given "our" commit,
+    /// producing an index that reflects the result of the revert.
+    pub fn revert_commit(
+        &self,
+        revert_commit: &Commit<'_>,
+        our_commit: &Commit<'_>,
+        mainline: u32,
+        options: Option<&MergeOptions>,
+    ) -> Result<Index, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_revert_commit(
+                &mut ret,
+                self.raw(),
+                revert_commit.raw(),
+                our_commit.raw(),
+                mainline,
+                options.map(|o| o.raw())
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Lists all the worktrees for the repository
+    pub fn worktrees(&self) -> Result<StringArray, Error> {
+        let mut arr = raw::git_strarray {
+            strings: ptr::null_mut(),
+            count: 0,
+        };
+        unsafe {
+            try_call!(raw::git_worktree_list(&mut arr, self.raw));
+            Ok(Binding::from_raw(arr))
+        }
+    }
+
+    /// Opens a worktree by name for the given repository
+    ///
+    /// This can open any worktree that the worktrees method returns.
+    pub fn find_worktree(&self, name: &str) -> Result<Worktree, Error> {
+        let mut raw = ptr::null_mut();
+        let raw_name = CString::new(name)?;
+        unsafe {
+            try_call!(raw::git_worktree_lookup(&mut raw, self.raw, raw_name));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Creates a new worktree for the repository
+    pub fn worktree<'a>(
+        &'a self,
+        name: &str,
+        path: &Path,
+        opts: Option<&WorktreeAddOptions<'a>>,
+    ) -> Result<Worktree, Error> {
+        let mut raw = ptr::null_mut();
+        let raw_name = CString::new(name)?;
+        let raw_path = path.into_c_string()?;
+
+        unsafe {
+            try_call!(raw::git_worktree_add(
+                &mut raw,
+                self.raw,
+                raw_name,
+                raw_path,
+                opts.map(|o| o.raw())
+            ));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Create a new transaction
+    pub fn transaction<'a>(&'a self) -> Result<Transaction<'a>, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_transaction_new(&mut raw, self.raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Gets this repository's mailmap.
+    pub fn mailmap(&self) -> Result<Mailmap, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_mailmap_from_repository(&mut ret, self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    ///  If a merge is in progress, invoke 'callback' for each commit ID in the
+    ///  MERGE_HEAD file.
+    pub fn mergehead_foreach<C>(&mut self, mut callback: C) -> Result<(), Error>
+    where
+        C: FnMut(&Oid) -> bool,
+    {
+        unsafe {
+            let mut data = MergeheadForeachCbData {
+                callback: &mut callback,
+            };
+            let cb: raw::git_repository_mergehead_foreach_cb = Some(mergehead_foreach_cb);
+            try_call!(raw::git_repository_mergehead_foreach(
+                self.raw(),
+                cb,
+                &mut data as *mut _ as *mut _
+            ));
+            Ok(())
+        }
+    }
+
+    /// Invoke 'callback' for each entry in the given FETCH_HEAD file.
+    ///
+    /// `callback` will be called with with following arguments:
+    ///
+    /// - `&str`: the reference name
+    /// - `&[u8]`: the remote URL
+    /// - `&Oid`: the reference target OID
+    /// - `bool`: was the reference the result of a merge
+    pub fn fetchhead_foreach<C>(&self, mut callback: C) -> Result<(), Error>
+    where
+        C: FnMut(&str, &[u8], &Oid, bool) -> bool,
+    {
+        unsafe {
+            let mut data = FetchheadForeachCbData {
+                callback: &mut callback,
+            };
+            let cb: raw::git_repository_fetchhead_foreach_cb = Some(fetchhead_foreach_cb);
+            try_call!(raw::git_repository_fetchhead_foreach(
+                self.raw(),
+                cb,
+                &mut data as *mut _ as *mut _
+            ));
+            Ok(())
+        }
+    }
+}
+
+impl Binding for Repository {
+    type Raw = *mut raw::git_repository;
+    unsafe fn from_raw(ptr: *mut raw::git_repository) -> Repository {
+        Repository { raw: ptr }
+    }
+    fn raw(&self) -> *mut raw::git_repository {
+        self.raw
+    }
+}
+
+impl Drop for Repository {
+    fn drop(&mut self) {
+        unsafe { raw::git_repository_free(self.raw) }
+    }
+}
+
+impl RepositoryInitOptions {
+    /// Creates a default set of initialization options.
+    ///
+    /// By default this will set flags for creating all necessary directories
+    /// and initializing a directory from the user-configured templates path.
+    pub fn new() -> RepositoryInitOptions {
+        RepositoryInitOptions {
+            flags: raw::GIT_REPOSITORY_INIT_MKDIR as u32
+                | raw::GIT_REPOSITORY_INIT_MKPATH as u32
+                | raw::GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE as u32,
+            mode: 0,
+            workdir_path: None,
+            description: None,
+            template_path: None,
+            initial_head: None,
+            origin_url: None,
+        }
+    }
+
+    /// Create a bare repository with no working directory.
+    ///
+    /// Defaults to false.
+    pub fn bare(&mut self, bare: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_BARE, bare)
+    }
+
+    /// Return an error if the repository path appears to already be a git
+    /// repository.
+    ///
+    /// Defaults to false.
+    pub fn no_reinit(&mut self, enabled: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_NO_REINIT, enabled)
+    }
+
+    /// Normally a '/.git/' will be appended to the repo path for non-bare repos
+    /// (if it is not already there), but passing this flag prevents that
+    /// behavior.
+    ///
+    /// Defaults to false.
+    pub fn no_dotgit_dir(&mut self, enabled: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_NO_DOTGIT_DIR, enabled)
+    }
+
+    /// Make the repo path (and workdir path) as needed. The ".git" directory
+    /// will always be created regardless of this flag.
+    ///
+    /// Defaults to true.
+    pub fn mkdir(&mut self, enabled: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_MKDIR, enabled)
+    }
+
+    /// Recursively make all components of the repo and workdir path as
+    /// necessary.
+    ///
+    /// Defaults to true.
+    pub fn mkpath(&mut self, enabled: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_MKPATH, enabled)
+    }
+
+    /// Set to one of the `RepositoryInit` constants, or a custom value.
+    pub fn mode(&mut self, mode: RepositoryInitMode) -> &mut RepositoryInitOptions {
+        self.mode = mode.bits();
+        self
+    }
+
+    /// Enable or disable using external templates.
+    ///
+    /// If enabled, then the `template_path` option will be queried first, then
+    /// `init.templatedir` from the global config, and finally
+    /// `/usr/share/git-core-templates` will be used (if it exists).
+    ///
+    /// Defaults to true.
+    pub fn external_template(&mut self, enabled: bool) -> &mut RepositoryInitOptions {
+        self.flag(raw::GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE, enabled)
+    }
+
+    fn flag(
+        &mut self,
+        flag: raw::git_repository_init_flag_t,
+        on: bool,
+    ) -> &mut RepositoryInitOptions {
+        if on {
+            self.flags |= flag as u32;
+        } else {
+            self.flags &= !(flag as u32);
+        }
+        self
+    }
+
+    /// The path to the working directory.
+    ///
+    /// If this is a relative path it will be evaluated relative to the repo
+    /// path. If this is not the "natural" working directory, a .git gitlink
+    /// file will be created here linking to the repo path.
+    pub fn workdir_path(&mut self, path: &Path) -> &mut RepositoryInitOptions {
+        // Normal file path OK (does not need Windows conversion).
+        self.workdir_path = Some(path.into_c_string().unwrap());
+        self
+    }
+
+    /// If set, this will be used to initialize the "description" file in the
+    /// repository instead of using the template content.
+    pub fn description(&mut self, desc: &str) -> &mut RepositoryInitOptions {
+        self.description = Some(CString::new(desc).unwrap());
+        self
+    }
+
+    /// When the `external_template` option is set, this is the first location
+    /// to check for the template directory.
+    ///
+    /// If this is not configured, then the default locations will be searched
+    /// instead.
+    pub fn template_path(&mut self, path: &Path) -> &mut RepositoryInitOptions {
+        // Normal file path OK (does not need Windows conversion).
+        self.template_path = Some(path.into_c_string().unwrap());
+        self
+    }
+
+    /// The name of the head to point HEAD at.
+    ///
+    /// If not configured, this will be taken from your git configuration.
+    /// If this begins with `refs/` it will be used verbatim;
+    /// otherwise `refs/heads/` will be prefixed
+    pub fn initial_head(&mut self, head: &str) -> &mut RepositoryInitOptions {
+        self.initial_head = Some(CString::new(head).unwrap());
+        self
+    }
+
+    /// If set, then after the rest of the repository initialization is
+    /// completed an `origin` remote will be added pointing to this URL.
+    pub fn origin_url(&mut self, url: &str) -> &mut RepositoryInitOptions {
+        self.origin_url = Some(CString::new(url).unwrap());
+        self
+    }
+
+    /// Creates a set of raw init options to be used with
+    /// `git_repository_init_ext`.
+    ///
+    /// This method is unsafe as the returned value may have pointers to the
+    /// interior of this structure.
+    pub unsafe fn raw(&self) -> raw::git_repository_init_options {
+        let mut opts = mem::zeroed();
+        assert_eq!(
+            raw::git_repository_init_init_options(
+                &mut opts,
+                raw::GIT_REPOSITORY_INIT_OPTIONS_VERSION
+            ),
+            0
+        );
+        opts.flags = self.flags;
+        opts.mode = self.mode;
+        opts.workdir_path = crate::call::convert(&self.workdir_path);
+        opts.description = crate::call::convert(&self.description);
+        opts.template_path = crate::call::convert(&self.template_path);
+        opts.initial_head = crate::call::convert(&self.initial_head);
+        opts.origin_url = crate::call::convert(&self.origin_url);
+        opts
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::build::CheckoutBuilder;
+    use crate::CherrypickOptions;
+    use crate::{
+        ObjectType, Oid, Repository, ResetType, Signature, SubmoduleIgnore, SubmoduleUpdate,
+    };
+    use std::ffi::OsStr;
+    use std::fs;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    #[test]
+    fn smoke_init() {
+        let td = TempDir::new().unwrap();
+        let path = td.path();
+
+        let repo = Repository::init(path).unwrap();
+        assert!(!repo.is_bare());
+    }
+
+    #[test]
+    fn smoke_init_bare() {
+        let td = TempDir::new().unwrap();
+        let path = td.path();
+
+        let repo = Repository::init_bare(path).unwrap();
+        assert!(repo.is_bare());
+        assert!(repo.namespace().is_none());
+    }
+
+    #[test]
+    fn smoke_open() {
+        let td = TempDir::new().unwrap();
+        let path = td.path();
+        Repository::init(td.path()).unwrap();
+        let repo = Repository::open(path).unwrap();
+        assert!(!repo.is_bare());
+        assert!(!repo.is_shallow());
+        assert!(repo.is_empty().unwrap());
+        assert_eq!(
+            crate::test::realpath(&repo.path()).unwrap(),
+            crate::test::realpath(&td.path().join(".git/")).unwrap()
+        );
+        assert_eq!(repo.state(), crate::RepositoryState::Clean);
+    }
+
+    #[test]
+    fn smoke_open_bare() {
+        let td = TempDir::new().unwrap();
+        let path = td.path();
+        Repository::init_bare(td.path()).unwrap();
+
+        let repo = Repository::open(path).unwrap();
+        assert!(repo.is_bare());
+        assert_eq!(
+            crate::test::realpath(&repo.path()).unwrap(),
+            crate::test::realpath(&td.path().join("")).unwrap()
+        );
+    }
+
+    #[test]
+    fn smoke_checkout() {
+        let (_td, repo) = crate::test::repo_init();
+        repo.checkout_head(None).unwrap();
+    }
+
+    #[test]
+    fn smoke_revparse() {
+        let (_td, repo) = crate::test::repo_init();
+        let rev = repo.revparse("HEAD").unwrap();
+        assert!(rev.to().is_none());
+        let from = rev.from().unwrap();
+        assert!(rev.from().is_some());
+
+        assert_eq!(repo.revparse_single("HEAD").unwrap().id(), from.id());
+        let obj = repo.find_object(from.id(), None).unwrap().clone();
+        obj.peel(ObjectType::Any).unwrap();
+        obj.short_id().unwrap();
+        repo.reset(&obj, ResetType::Hard, None).unwrap();
+        let mut opts = CheckoutBuilder::new();
+        t!(repo.reset(&obj, ResetType::Soft, Some(&mut opts)));
+    }
+
+    #[test]
+    fn makes_dirs() {
+        let td = TempDir::new().unwrap();
+        Repository::init(&td.path().join("a/b/c/d")).unwrap();
+    }
+
+    #[test]
+    fn smoke_discover() {
+        let td = TempDir::new().unwrap();
+        let subdir = td.path().join("subdi");
+        fs::create_dir(&subdir).unwrap();
+        Repository::init_bare(td.path()).unwrap();
+        let repo = Repository::discover(&subdir).unwrap();
+        assert_eq!(
+            crate::test::realpath(&repo.path()).unwrap(),
+            crate::test::realpath(&td.path().join("")).unwrap()
+        );
+    }
+
+    #[test]
+    fn smoke_discover_path() {
+        let td = TempDir::new().unwrap();
+        let subdir = td.path().join("subdi");
+        fs::create_dir(&subdir).unwrap();
+        Repository::init_bare(td.path()).unwrap();
+        let path = Repository::discover_path(&subdir, &[] as &[&OsStr]).unwrap();
+        assert_eq!(
+            crate::test::realpath(&path).unwrap(),
+            crate::test::realpath(&td.path().join("")).unwrap()
+        );
+    }
+
+    #[test]
+    fn smoke_discover_path_ceiling_dir() {
+        let td = TempDir::new().unwrap();
+        let subdir = td.path().join("subdi");
+        fs::create_dir(&subdir).unwrap();
+        let ceilingdir = subdir.join("ceiling");
+        fs::create_dir(&ceilingdir).unwrap();
+        let testdir = ceilingdir.join("testdi");
+        fs::create_dir(&testdir).unwrap();
+        Repository::init_bare(td.path()).unwrap();
+        let path = Repository::discover_path(&testdir, &[ceilingdir.as_os_str()]);
+
+        assert!(path.is_err());
+    }
+
+    #[test]
+    fn smoke_open_ext() {
+        let td = TempDir::new().unwrap();
+        let subdir = td.path().join("subdir");
+        fs::create_dir(&subdir).unwrap();
+        Repository::init(td.path()).unwrap();
+
+        let repo = Repository::open_ext(
+            &subdir,
+            crate::RepositoryOpenFlags::empty(),
+            &[] as &[&OsStr],
+        )
+        .unwrap();
+        assert!(!repo.is_bare());
+        assert_eq!(
+            crate::test::realpath(&repo.path()).unwrap(),
+            crate::test::realpath(&td.path().join(".git")).unwrap()
+        );
+
+        let repo =
+            Repository::open_ext(&subdir, crate::RepositoryOpenFlags::BARE, &[] as &[&OsStr])
+                .unwrap();
+        assert!(repo.is_bare());
+        assert_eq!(
+            crate::test::realpath(&repo.path()).unwrap(),
+            crate::test::realpath(&td.path().join(".git")).unwrap()
+        );
+
+        let err = Repository::open_ext(
+            &subdir,
+            crate::RepositoryOpenFlags::NO_SEARCH,
+            &[] as &[&OsStr],
+        )
+        .err()
+        .unwrap();
+        assert_eq!(err.code(), crate::ErrorCode::NotFound);
+
+        assert!(
+            Repository::open_ext(&subdir, crate::RepositoryOpenFlags::empty(), &[&subdir]).is_ok()
+        );
+    }
+
+    fn graph_repo_init() -> (TempDir, Repository) {
+        let (_td, repo) = crate::test::repo_init();
+        {
+            let head = repo.head().unwrap().target().unwrap();
+            let head = repo.find_commit(head).unwrap();
+
+            let mut index = repo.index().unwrap();
+            let id = index.write_tree().unwrap();
+
+            let tree = repo.find_tree(id).unwrap();
+            let sig = repo.signature().unwrap();
+            repo.commit(Some("HEAD"), &sig, &sig, "second", &tree, &[&head])
+                .unwrap();
+        }
+        (_td, repo)
+    }
+
+    #[test]
+    fn smoke_graph_ahead_behind() {
+        let (_td, repo) = graph_repo_init();
+        let head = repo.head().unwrap().target().unwrap();
+        let head = repo.find_commit(head).unwrap();
+        let head_id = head.id();
+        let head_parent_id = head.parent(0).unwrap().id();
+        let (ahead, behind) = repo.graph_ahead_behind(head_id, head_parent_id).unwrap();
+        assert_eq!(ahead, 1);
+        assert_eq!(behind, 0);
+        let (ahead, behind) = repo.graph_ahead_behind(head_parent_id, head_id).unwrap();
+        assert_eq!(ahead, 0);
+        assert_eq!(behind, 1);
+    }
+
+    #[test]
+    fn smoke_graph_descendant_of() {
+        let (_td, repo) = graph_repo_init();
+        let head = repo.head().unwrap().target().unwrap();
+        let head = repo.find_commit(head).unwrap();
+        let head_id = head.id();
+        let head_parent_id = head.parent(0).unwrap().id();
+        assert!(repo.graph_descendant_of(head_id, head_parent_id).unwrap());
+        assert!(!repo.graph_descendant_of(head_parent_id, head_id).unwrap());
+    }
+
+    #[test]
+    fn smoke_reference_has_log_ensure_log() {
+        let (_td, repo) = crate::test::repo_init();
+
+        assert_eq!(repo.reference_has_log("HEAD").unwrap(), true);
+        assert_eq!(repo.reference_has_log("refs/heads/main").unwrap(), true);
+        assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false);
+        let main_oid = repo.revparse_single("main").unwrap().id();
+        assert!(repo
+            .reference("NOT_HEAD", main_oid, false, "creating a new branch")
+            .is_ok());
+        assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false);
+        assert!(repo.reference_ensure_log("NOT_HEAD").is_ok());
+        assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), true);
+    }
+
+    #[test]
+    fn smoke_set_head() {
+        let (_td, repo) = crate::test::repo_init();
+
+        assert!(repo.set_head("refs/heads/does-not-exist").is_ok());
+        assert!(repo.head().is_err());
+
+        assert!(repo.set_head("refs/heads/main").is_ok());
+        assert!(repo.head().is_ok());
+
+        assert!(repo.set_head("*").is_err());
+    }
+
+    #[test]
+    fn smoke_set_head_bytes() {
+        let (_td, repo) = crate::test::repo_init();
+
+        assert!(repo.set_head_bytes(b"refs/heads/does-not-exist").is_ok());
+        assert!(repo.head().is_err());
+
+        assert!(repo.set_head_bytes(b"refs/heads/main").is_ok());
+        assert!(repo.head().is_ok());
+
+        assert!(repo.set_head_bytes(b"*").is_err());
+    }
+
+    #[test]
+    fn smoke_set_head_detached() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let void_oid = Oid::from_bytes(b"00000000000000000000").unwrap();
+        assert!(repo.set_head_detached(void_oid).is_err());
+
+        let main_oid = repo.revparse_single("main").unwrap().id();
+        assert!(repo.set_head_detached(main_oid).is_ok());
+        assert_eq!(repo.head().unwrap().target().unwrap(), main_oid);
+    }
+
+    #[test]
+    fn smoke_find_object_by_prefix() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap().target().unwrap();
+        let head = repo.find_commit(head).unwrap();
+        let head_id = head.id();
+        let head_prefix = &head_id.to_string()[..7];
+        let obj = repo.find_object_by_prefix(head_prefix, None).unwrap();
+        assert_eq!(obj.id(), head_id);
+    }
+
+    /// create the following:
+    ///    /---o4
+    ///   /---o3
+    /// o1---o2
+    #[test]
+    fn smoke_merge_base() {
+        let (_td, repo) = graph_repo_init();
+        let sig = repo.signature().unwrap();
+
+        // let oid1 = head
+        let oid1 = repo.head().unwrap().target().unwrap();
+        let commit1 = repo.find_commit(oid1).unwrap();
+        println!("created oid1 {:?}", oid1);
+
+        repo.branch("branch_a", &commit1, true).unwrap();
+        repo.branch("branch_b", &commit1, true).unwrap();
+        repo.branch("branch_c", &commit1, true).unwrap();
+
+        // create commit oid2 on branch_a
+        let mut index = repo.index().unwrap();
+        let p = Path::new(repo.workdir().unwrap()).join("file_a");
+        println!("using path {:?}", p);
+        fs::File::create(&p).unwrap();
+        index.add_path(Path::new("file_a")).unwrap();
+        let id_a = index.write_tree().unwrap();
+        let tree_a = repo.find_tree(id_a).unwrap();
+        let oid2 = repo
+            .commit(
+                Some("refs/heads/branch_a"),
+                &sig,
+                &sig,
+                "commit 2",
+                &tree_a,
+                &[&commit1],
+            )
+            .unwrap();
+        repo.find_commit(oid2).unwrap();
+        println!("created oid2 {:?}", oid2);
+
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+        // create commit oid3 on branch_b
+        let mut index = repo.index().unwrap();
+        let p = Path::new(repo.workdir().unwrap()).join("file_b");
+        fs::File::create(&p).unwrap();
+        index.add_path(Path::new("file_b")).unwrap();
+        let id_b = index.write_tree().unwrap();
+        let tree_b = repo.find_tree(id_b).unwrap();
+        let oid3 = repo
+            .commit(
+                Some("refs/heads/branch_b"),
+                &sig,
+                &sig,
+                "commit 3",
+                &tree_b,
+                &[&commit1],
+            )
+            .unwrap();
+        repo.find_commit(oid3).unwrap();
+        println!("created oid3 {:?}", oid3);
+
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+        // create commit oid4 on branch_c
+        let mut index = repo.index().unwrap();
+        let p = Path::new(repo.workdir().unwrap()).join("file_c");
+        fs::File::create(&p).unwrap();
+        index.add_path(Path::new("file_c")).unwrap();
+        let id_c = index.write_tree().unwrap();
+        let tree_c = repo.find_tree(id_c).unwrap();
+        let oid4 = repo
+            .commit(
+                Some("refs/heads/branch_c"),
+                &sig,
+                &sig,
+                "commit 3",
+                &tree_c,
+                &[&commit1],
+            )
+            .unwrap();
+        repo.find_commit(oid4).unwrap();
+        println!("created oid4 {:?}", oid4);
+
+        // the merge base of (oid2,oid3) should be oid1
+        let merge_base = repo.merge_base(oid2, oid3).unwrap();
+        assert_eq!(merge_base, oid1);
+
+        // the merge base of (oid2,oid3,oid4) should be oid1
+        let merge_base = repo.merge_base_many(&[oid2, oid3, oid4]).unwrap();
+        assert_eq!(merge_base, oid1);
+
+        // the octopus merge base of (oid2,oid3,oid4) should be oid1
+        let merge_base = repo.merge_base_octopus(&[oid2, oid3, oid4]).unwrap();
+        assert_eq!(merge_base, oid1);
+    }
+
+    /// create an octopus:
+    ///   /---o2-o4
+    /// o1      X
+    ///   \---o3-o5
+    /// and checks that the merge bases of (o4,o5) are (o2,o3)
+    #[test]
+    fn smoke_merge_bases() {
+        let (_td, repo) = graph_repo_init();
+        let sig = repo.signature().unwrap();
+
+        // let oid1 = head
+        let oid1 = repo.head().unwrap().target().unwrap();
+        let commit1 = repo.find_commit(oid1).unwrap();
+        println!("created oid1 {:?}", oid1);
+
+        repo.branch("branch_a", &commit1, true).unwrap();
+        repo.branch("branch_b", &commit1, true).unwrap();
+
+        // create commit oid2 on branchA
+        let mut index = repo.index().unwrap();
+        let p = Path::new(repo.workdir().unwrap()).join("file_a");
+        println!("using path {:?}", p);
+        fs::File::create(&p).unwrap();
+        index.add_path(Path::new("file_a")).unwrap();
+        let id_a = index.write_tree().unwrap();
+        let tree_a = repo.find_tree(id_a).unwrap();
+        let oid2 = repo
+            .commit(
+                Some("refs/heads/branch_a"),
+                &sig,
+                &sig,
+                "commit 2",
+                &tree_a,
+                &[&commit1],
+            )
+            .unwrap();
+        let commit2 = repo.find_commit(oid2).unwrap();
+        println!("created oid2 {:?}", oid2);
+
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+        // create commit oid3 on branchB
+        let mut index = repo.index().unwrap();
+        let p = Path::new(repo.workdir().unwrap()).join("file_b");
+        fs::File::create(&p).unwrap();
+        index.add_path(Path::new("file_b")).unwrap();
+        let id_b = index.write_tree().unwrap();
+        let tree_b = repo.find_tree(id_b).unwrap();
+        let oid3 = repo
+            .commit(
+                Some("refs/heads/branch_b"),
+                &sig,
+                &sig,
+                "commit 3",
+                &tree_b,
+                &[&commit1],
+            )
+            .unwrap();
+        let commit3 = repo.find_commit(oid3).unwrap();
+        println!("created oid3 {:?}", oid3);
+
+        // create merge commit oid4 on branchA with parents oid2 and oid3
+        //let mut index4 = repo.merge_commits(&commit2, &commit3, None).unwrap();
+        repo.set_head("refs/heads/branch_a").unwrap();
+        repo.checkout_head(None).unwrap();
+        let oid4 = repo
+            .commit(
+                Some("refs/heads/branch_a"),
+                &sig,
+                &sig,
+                "commit 4",
+                &tree_a,
+                &[&commit2, &commit3],
+            )
+            .unwrap();
+        //index4.write_tree_to(&repo).unwrap();
+        println!("created oid4 {:?}", oid4);
+
+        // create merge commit oid5 on branchB with parents oid2 and oid3
+        //let mut index5 = repo.merge_commits(&commit3, &commit2, None).unwrap();
+        repo.set_head("refs/heads/branch_b").unwrap();
+        repo.checkout_head(None).unwrap();
+        let oid5 = repo
+            .commit(
+                Some("refs/heads/branch_b"),
+                &sig,
+                &sig,
+                "commit 5",
+                &tree_a,
+                &[&commit3, &commit2],
+            )
+            .unwrap();
+        //index5.write_tree_to(&repo).unwrap();
+        println!("created oid5 {:?}", oid5);
+
+        // merge bases of (oid4,oid5) should be (oid2,oid3)
+        let merge_bases = repo.merge_bases(oid4, oid5).unwrap();
+        let mut found_oid2 = false;
+        let mut found_oid3 = false;
+        for mg in merge_bases.iter() {
+            println!("found merge base {:?}", mg);
+            if mg == &oid2 {
+                found_oid2 = true;
+            } else if mg == &oid3 {
+                found_oid3 = true;
+            } else {
+                assert!(false);
+            }
+        }
+        assert!(found_oid2);
+        assert!(found_oid3);
+        assert_eq!(merge_bases.len(), 2);
+
+        // merge bases of (oid4,oid5) should be (oid2,oid3)
+        let merge_bases = repo.merge_bases_many(&[oid4, oid5]).unwrap();
+        let mut found_oid2 = false;
+        let mut found_oid3 = false;
+        for mg in merge_bases.iter() {
+            println!("found merge base {:?}", mg);
+            if mg == &oid2 {
+                found_oid2 = true;
+            } else if mg == &oid3 {
+                found_oid3 = true;
+            } else {
+                assert!(false);
+            }
+        }
+        assert!(found_oid2);
+        assert!(found_oid3);
+        assert_eq!(merge_bases.len(), 2);
+    }
+
+    #[test]
+    fn smoke_revparse_ext() {
+        let (_td, repo) = graph_repo_init();
+
+        {
+            let short_refname = "main";
+            let expected_refname = "refs/heads/main";
+            let (obj, reference) = repo.revparse_ext(short_refname).unwrap();
+            let expected_obj = repo.revparse_single(expected_refname).unwrap();
+            assert_eq!(obj.id(), expected_obj.id());
+            assert_eq!(reference.unwrap().name().unwrap(), expected_refname);
+        }
+        {
+            let missing_refname = "refs/heads/does-not-exist";
+            assert!(repo.revparse_ext(missing_refname).is_err());
+        }
+        {
+            let (_obj, reference) = repo.revparse_ext("HEAD^").unwrap();
+            assert!(reference.is_none());
+        }
+    }
+
+    #[test]
+    fn smoke_is_path_ignored() {
+        let (_td, repo) = graph_repo_init();
+
+        assert!(!repo.is_path_ignored(Path::new("foo")).unwrap());
+
+        let _ = repo.add_ignore_rule("/foo");
+        assert!(repo.is_path_ignored(Path::new("foo")).unwrap());
+        if cfg!(windows) {
+            assert!(repo.is_path_ignored(Path::new("foo\\thing")).unwrap());
+        }
+
+        let _ = repo.clear_ignore_rules();
+        assert!(!repo.is_path_ignored(Path::new("foo")).unwrap());
+        if cfg!(windows) {
+            assert!(!repo.is_path_ignored(Path::new("foo\\thing")).unwrap());
+        }
+    }
+
+    #[test]
+    fn smoke_cherrypick() {
+        let (_td, repo) = crate::test::repo_init();
+        let sig = repo.signature().unwrap();
+
+        let oid1 = repo.head().unwrap().target().unwrap();
+        let commit1 = repo.find_commit(oid1).unwrap();
+
+        repo.branch("branch_a", &commit1, true).unwrap();
+
+        // Add 2 commits on top of the initial one in branch_a
+        let mut index = repo.index().unwrap();
+        let p1 = Path::new(repo.workdir().unwrap()).join("file_c");
+        fs::File::create(&p1).unwrap();
+        index.add_path(Path::new("file_c")).unwrap();
+        let id = index.write_tree().unwrap();
+        let tree_c = repo.find_tree(id).unwrap();
+        let oid2 = repo
+            .commit(
+                Some("refs/heads/branch_a"),
+                &sig,
+                &sig,
+                "commit 2",
+                &tree_c,
+                &[&commit1],
+            )
+            .unwrap();
+        let commit2 = repo.find_commit(oid2).unwrap();
+        println!("created oid2 {:?}", oid2);
+        assert!(p1.exists());
+
+        let mut index = repo.index().unwrap();
+        let p2 = Path::new(repo.workdir().unwrap()).join("file_d");
+        fs::File::create(&p2).unwrap();
+        index.add_path(Path::new("file_d")).unwrap();
+        let id = index.write_tree().unwrap();
+        let tree_d = repo.find_tree(id).unwrap();
+        let oid3 = repo
+            .commit(
+                Some("refs/heads/branch_a"),
+                &sig,
+                &sig,
+                "commit 3",
+                &tree_d,
+                &[&commit2],
+            )
+            .unwrap();
+        let commit3 = repo.find_commit(oid3).unwrap();
+        println!("created oid3 {:?}", oid3);
+        assert!(p1.exists());
+        assert!(p2.exists());
+
+        // cherry-pick commit3 on top of commit1 in branch b
+        repo.reset(commit1.as_object(), ResetType::Hard, None)
+            .unwrap();
+        let mut cherrypick_opts = CherrypickOptions::new();
+        repo.cherrypick(&commit3, Some(&mut cherrypick_opts))
+            .unwrap();
+        let id = repo.index().unwrap().write_tree().unwrap();
+        let tree_d = repo.find_tree(id).unwrap();
+        let oid4 = repo
+            .commit(Some("HEAD"), &sig, &sig, "commit 4", &tree_d, &[&commit1])
+            .unwrap();
+        let commit4 = repo.find_commit(oid4).unwrap();
+        // should have file from commit3, but not the file from commit2
+        assert_eq!(commit4.parent(0).unwrap().id(), commit1.id());
+        assert!(!p1.exists());
+        assert!(p2.exists());
+    }
+
+    #[test]
+    fn smoke_revert() {
+        let (_td, repo) = crate::test::repo_init();
+        let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
+        assert!(!foo_file.exists());
+
+        let (oid1, _id) = crate::test::commit(&repo);
+        let commit1 = repo.find_commit(oid1).unwrap();
+        t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+        assert!(foo_file.exists());
+
+        repo.revert(&commit1, None).unwrap();
+        let id = repo.index().unwrap().write_tree().unwrap();
+        let tree2 = repo.find_tree(id).unwrap();
+        let sig = repo.signature().unwrap();
+        repo.commit(Some("HEAD"), &sig, &sig, "commit 1", &tree2, &[&commit1])
+            .unwrap();
+        // reverting once removes `foo` file
+        assert!(!foo_file.exists());
+
+        let oid2 = repo.head().unwrap().target().unwrap();
+        let commit2 = repo.find_commit(oid2).unwrap();
+        repo.revert(&commit2, None).unwrap();
+        let id = repo.index().unwrap().write_tree().unwrap();
+        let tree3 = repo.find_tree(id).unwrap();
+        repo.commit(Some("HEAD"), &sig, &sig, "commit 2", &tree3, &[&commit2])
+            .unwrap();
+        // reverting twice restores `foo` file
+        assert!(foo_file.exists());
+    }
+
+    #[test]
+    fn smoke_config_write_and_read() {
+        let (td, repo) = crate::test::repo_init();
+
+        let mut config = repo.config().unwrap();
+
+        config.set_bool("commit.gpgsign", false).unwrap();
+
+        let c = fs::read_to_string(td.path().join(".git").join("config")).unwrap();
+
+        assert!(c.contains("[commit]"));
+        assert!(c.contains("gpgsign = false"));
+
+        let config = repo.config().unwrap();
+
+        assert!(!config.get_bool("commit.gpgsign").unwrap());
+    }
+
+    #[test]
+    fn smoke_merge_analysis_for_ref() -> Result<(), crate::Error> {
+        let (_td, repo) = graph_repo_init();
+
+        // Set up this repo state:
+        // * second (their-branch)
+        // * initial (HEAD -> main)
+        //
+        // We expect that their-branch can be fast-forward merged into main.
+
+        // git checkout --detach HEAD
+        let head_commit = repo.head()?.peel_to_commit()?;
+        repo.set_head_detached(head_commit.id())?;
+
+        // git branch their-branch HEAD
+        let their_branch = repo.branch("their-branch", &head_commit, false)?;
+
+        // git branch -f main HEAD~
+        let mut parents_iter = head_commit.parents();
+        let parent = parents_iter.next().unwrap();
+        assert!(parents_iter.next().is_none());
+
+        let main = repo.branch("main", &parent, true)?;
+
+        // git checkout main
+        repo.set_head(main.get().name().expect("should be utf-8"))?;
+
+        let (merge_analysis, _merge_preference) = repo.merge_analysis_for_ref(
+            main.get(),
+            &[&repo.reference_to_annotated_commit(their_branch.get())?],
+        )?;
+
+        assert!(merge_analysis.contains(crate::MergeAnalysis::ANALYSIS_FASTFORWARD));
+
+        Ok(())
+    }
+
+    #[test]
+    fn smoke_submodule_set() -> Result<(), crate::Error> {
+        let (td1, _repo) = crate::test::repo_init();
+        let (td2, mut repo2) = crate::test::repo_init();
+        let url = crate::test::path2url(td1.path());
+        let name = "bar";
+        {
+            let mut s = repo2.submodule(&url, Path::new(name), true)?;
+            fs::remove_dir_all(td2.path().join("bar")).unwrap();
+            Repository::clone(&url, td2.path().join("bar"))?;
+            s.add_to_index(false)?;
+            s.add_finalize()?;
+        }
+
+        // update strategy
+        repo2.submodule_set_update(name, SubmoduleUpdate::None)?;
+        assert!(matches!(
+            repo2.find_submodule(name)?.update_strategy(),
+            SubmoduleUpdate::None
+        ));
+        repo2.submodule_set_update(name, SubmoduleUpdate::Rebase)?;
+        assert!(matches!(
+            repo2.find_submodule(name)?.update_strategy(),
+            SubmoduleUpdate::Rebase
+        ));
+
+        // ignore rule
+        repo2.submodule_set_ignore(name, SubmoduleIgnore::Untracked)?;
+        assert!(matches!(
+            repo2.find_submodule(name)?.ignore_rule(),
+            SubmoduleIgnore::Untracked
+        ));
+        repo2.submodule_set_ignore(name, SubmoduleIgnore::Dirty)?;
+        assert!(matches!(
+            repo2.find_submodule(name)?.ignore_rule(),
+            SubmoduleIgnore::Dirty
+        ));
+
+        // url
+        repo2.submodule_set_url(name, "fake-url")?;
+        assert_eq!(repo2.find_submodule(name)?.url(), Some("fake-url"));
+
+        // branch
+        repo2.submodule_set_branch(name, "fake-branch")?;
+        assert_eq!(repo2.find_submodule(name)?.branch(), Some("fake-branch"));
+
+        Ok(())
+    }
+
+    #[test]
+    fn smoke_mailmap_from_repository() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let commit = {
+            let head = t!(repo.head()).target().unwrap();
+            t!(repo.find_commit(head))
+        };
+
+        // This is our baseline for HEAD.
+        let author = commit.author();
+        let committer = commit.committer();
+        assert_eq!(author.name(), Some("name"));
+        assert_eq!(author.email(), Some("email"));
+        assert_eq!(committer.name(), Some("name"));
+        assert_eq!(committer.email(), Some("email"));
+
+        // There is no .mailmap file in the test repo so all signature identities are equal.
+        let mailmap = t!(repo.mailmap());
+        let mailmapped_author = t!(commit.author_with_mailmap(&mailmap));
+        let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap));
+        assert_eq!(mailmapped_author.name(), author.name());
+        assert_eq!(mailmapped_author.email(), author.email());
+        assert_eq!(mailmapped_committer.name(), committer.name());
+        assert_eq!(mailmapped_committer.email(), committer.email());
+
+        let commit = {
+            // - Add a .mailmap file to the repository.
+            // - Commit with a signature identity different from the author's.
+            // - Include entries for both author and committer to prove we call
+            //   the right raw functions.
+            let mailmap_file = Path::new(".mailmap");
+            let p = Path::new(repo.workdir().unwrap()).join(&mailmap_file);
+            t!(fs::write(
+                p,
+                r#"
+Author Name <author.proper@email> name <email>
+Committer Name <committer.proper@email> <committer@email>"#,
+            ));
+            let mut index = t!(repo.index());
+            t!(index.add_path(&mailmap_file));
+            let id_mailmap = t!(index.write_tree());
+            let tree_mailmap = t!(repo.find_tree(id_mailmap));
+
+            let head = t!(repo.commit(
+                Some("HEAD"),
+                &author,
+                t!(&Signature::now("committer", "committer@email")),
+                "Add mailmap",
+                &tree_mailmap,
+                &[&commit],
+            ));
+            t!(repo.find_commit(head))
+        };
+
+        // Sanity check that we're working with the right commit and that its
+        // author and committer identities differ.
+        let author = commit.author();
+        let committer = commit.committer();
+        assert_ne!(author.name(), committer.name());
+        assert_ne!(author.email(), committer.email());
+        assert_eq!(author.name(), Some("name"));
+        assert_eq!(author.email(), Some("email"));
+        assert_eq!(committer.name(), Some("committer"));
+        assert_eq!(committer.email(), Some("committer@email"));
+
+        // Fetch the newly added .mailmap from the repository.
+        let mailmap = t!(repo.mailmap());
+        let mailmapped_author = t!(commit.author_with_mailmap(&mailmap));
+        let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap));
+
+        let mm_resolve_author = t!(mailmap.resolve_signature(&author));
+        let mm_resolve_committer = t!(mailmap.resolve_signature(&committer));
+
+        // Mailmap Signature lifetime is independent of Commit lifetime.
+        drop(author);
+        drop(committer);
+        drop(commit);
+
+        // author_with_mailmap() + committer_with_mailmap() work
+        assert_eq!(mailmapped_author.name(), Some("Author Name"));
+        assert_eq!(mailmapped_author.email(), Some("author.proper@email"));
+        assert_eq!(mailmapped_committer.name(), Some("Committer Name"));
+        assert_eq!(mailmapped_committer.email(), Some("committer.proper@email"));
+
+        // resolve_signature() works
+        assert_eq!(mm_resolve_author.email(), mailmapped_author.email());
+        assert_eq!(mm_resolve_committer.email(), mailmapped_committer.email());
+    }
+
+    #[test]
+    fn smoke_find_tag_by_prefix() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let tag_oid = repo
+            .tag(
+                "tag",
+                &repo
+                    .find_object(head.peel_to_commit().unwrap().id(), None)
+                    .unwrap(),
+                &repo.signature().unwrap(),
+                "message",
+                false,
+            )
+            .unwrap();
+        let tag = repo.find_tag(tag_oid).unwrap();
+        let found_tag = repo
+            .find_tag_by_prefix(&tag.id().to_string()[0..7])
+            .unwrap();
+        assert_eq!(tag.id(), found_tag.id());
+    }
+
+    #[test]
+    fn smoke_commondir() {
+        let (td, repo) = crate::test::repo_init();
+        assert_eq!(
+            crate::test::realpath(repo.path()).unwrap(),
+            crate::test::realpath(repo.commondir()).unwrap()
+        );
+
+        let worktree = repo
+            .worktree("test", &td.path().join("worktree"), None)
+            .unwrap();
+        let worktree_repo = Repository::open_from_worktree(&worktree).unwrap();
+        assert_eq!(
+            crate::test::realpath(repo.path()).unwrap(),
+            crate::test::realpath(worktree_repo.commondir()).unwrap()
+        );
+    }
+}
diff --git a/git2/src/revert.rs b/git2/src/revert.rs
new file mode 100644 (file)
index 0000000..55d7026
--- /dev/null
@@ -0,0 +1,69 @@
+use std::mem;
+
+use crate::build::CheckoutBuilder;
+use crate::merge::MergeOptions;
+use crate::raw;
+use std::ptr;
+
+/// Options to specify when reverting
+pub struct RevertOptions<'cb> {
+    mainline: u32,
+    checkout_builder: Option<CheckoutBuilder<'cb>>,
+    merge_opts: Option<MergeOptions>,
+}
+
+impl<'cb> RevertOptions<'cb> {
+    /// Creates a default set of revert options
+    pub fn new() -> RevertOptions<'cb> {
+        RevertOptions {
+            mainline: 0,
+            checkout_builder: None,
+            merge_opts: None,
+        }
+    }
+
+    /// Set the mainline value
+    ///
+    /// For merge commits, the "mainline" is treated as the parent.
+    pub fn mainline(&mut self, mainline: u32) -> &mut Self {
+        self.mainline = mainline;
+        self
+    }
+
+    /// Set the checkout builder
+    pub fn checkout_builder(&mut self, cb: CheckoutBuilder<'cb>) -> &mut Self {
+        self.checkout_builder = Some(cb);
+        self
+    }
+
+    /// Set the merge options
+    pub fn merge_opts(&mut self, merge_opts: MergeOptions) -> &mut Self {
+        self.merge_opts = Some(merge_opts);
+        self
+    }
+
+    /// Obtain the raw struct
+    pub fn raw(&mut self) -> raw::git_revert_options {
+        unsafe {
+            let mut checkout_opts: raw::git_checkout_options = mem::zeroed();
+            raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION);
+            if let Some(ref mut cb) = self.checkout_builder {
+                cb.configure(&mut checkout_opts);
+            }
+
+            let mut merge_opts: raw::git_merge_options = mem::zeroed();
+            raw::git_merge_init_options(&mut merge_opts, raw::GIT_MERGE_OPTIONS_VERSION);
+            if let Some(ref opts) = self.merge_opts {
+                ptr::copy(opts.raw(), &mut merge_opts, 1);
+            }
+
+            let mut revert_opts: raw::git_revert_options = mem::zeroed();
+            raw::git_revert_options_init(&mut revert_opts, raw::GIT_REVERT_OPTIONS_VERSION);
+            revert_opts.mainline = self.mainline;
+            revert_opts.checkout_opts = checkout_opts;
+            revert_opts.merge_opts = merge_opts;
+
+            revert_opts
+        }
+    }
+}
diff --git a/git2/src/revspec.rs b/git2/src/revspec.rs
new file mode 100644 (file)
index 0000000..d2e0867
--- /dev/null
@@ -0,0 +1,34 @@
+use crate::{Object, RevparseMode};
+
+/// A revspec represents a range of revisions within a repository.
+pub struct Revspec<'repo> {
+    from: Option<Object<'repo>>,
+    to: Option<Object<'repo>>,
+    mode: RevparseMode,
+}
+
+impl<'repo> Revspec<'repo> {
+    /// Assembles a new revspec from the from/to components.
+    pub fn from_objects(
+        from: Option<Object<'repo>>,
+        to: Option<Object<'repo>>,
+        mode: RevparseMode,
+    ) -> Revspec<'repo> {
+        Revspec { from, to, mode }
+    }
+
+    /// Access the `from` range of this revspec.
+    pub fn from(&self) -> Option<&Object<'repo>> {
+        self.from.as_ref()
+    }
+
+    /// Access the `to` range of this revspec.
+    pub fn to(&self) -> Option<&Object<'repo>> {
+        self.to.as_ref()
+    }
+
+    /// Returns the intent of the revspec.
+    pub fn mode(&self) -> RevparseMode {
+        self.mode
+    }
+}
diff --git a/git2/src/revwalk.rs b/git2/src/revwalk.rs
new file mode 100644 (file)
index 0000000..7837f00
--- /dev/null
@@ -0,0 +1,316 @@
+use libc::{c_int, c_uint, c_void};
+use std::ffi::CString;
+use std::marker;
+
+use crate::util::Binding;
+use crate::{panic, raw, Error, Oid, Repository, Sort};
+
+/// A revwalk allows traversal of the commit graph defined by including one or
+/// more leaves and excluding one or more roots.
+pub struct Revwalk<'repo> {
+    raw: *mut raw::git_revwalk,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// A `Revwalk` with an associated "hide callback", see `with_hide_callback`
+pub struct RevwalkWithHideCb<'repo, 'cb, C>
+where
+    C: FnMut(Oid) -> bool,
+{
+    revwalk: Revwalk<'repo>,
+    _marker: marker::PhantomData<&'cb C>,
+}
+
+extern "C" fn revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int
+where
+    C: FnMut(Oid) -> bool,
+{
+    panic::wrap(|| unsafe {
+        let hide_cb = payload as *mut C;
+        if (*hide_cb)(Oid::from_raw(commit_id)) {
+            1
+        } else {
+            0
+        }
+    })
+    .unwrap_or(-1)
+}
+
+impl<'repo, 'cb, C: FnMut(Oid) -> bool> RevwalkWithHideCb<'repo, 'cb, C> {
+    /// Consumes the `RevwalkWithHideCb` and returns the contained `Revwalk`.
+    ///
+    /// Note that this will reset the `Revwalk`.
+    pub fn into_inner(mut self) -> Result<Revwalk<'repo>, Error> {
+        self.revwalk.reset()?;
+        Ok(self.revwalk)
+    }
+}
+
+impl<'repo> Revwalk<'repo> {
+    /// Reset a revwalk to allow re-configuring it.
+    ///
+    /// The revwalk is automatically reset when iteration of its commits
+    /// completes.
+    pub fn reset(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_reset(self.raw()));
+        }
+        Ok(())
+    }
+
+    /// Set the order in which commits are visited.
+    pub fn set_sorting(&mut self, sort_mode: Sort) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_sorting(
+                self.raw(),
+                sort_mode.bits() as c_uint
+            ));
+        }
+        Ok(())
+    }
+
+    /// Simplify the history by first-parent
+    ///
+    /// No parents other than the first for each commit will be enqueued.
+    pub fn simplify_first_parent(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_simplify_first_parent(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Mark a commit to start traversal from.
+    ///
+    /// The given OID must belong to a commitish on the walked repository.
+    ///
+    /// The given commit will be used as one of the roots when starting the
+    /// revision walk. At least one commit must be pushed onto the walker before
+    /// a walk can be started.
+    pub fn push(&mut self, oid: Oid) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_push(self.raw(), oid.raw()));
+        }
+        Ok(())
+    }
+
+    /// Push the repository's HEAD
+    ///
+    /// For more information, see `push`.
+    pub fn push_head(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_push_head(self.raw()));
+        }
+        Ok(())
+    }
+
+    /// Push matching references
+    ///
+    /// The OIDs pointed to by the references that match the given glob pattern
+    /// will be pushed to the revision walker.
+    ///
+    /// A leading 'refs/' is implied if not present as well as a trailing `/ \
+    /// *` if the glob lacks '?', ' \ *' or '['.
+    ///
+    /// Any references matching this glob which do not point to a commitish
+    /// will be ignored.
+    pub fn push_glob(&mut self, glob: &str) -> Result<(), Error> {
+        let glob = CString::new(glob)?;
+        unsafe {
+            try_call!(raw::git_revwalk_push_glob(self.raw, glob));
+        }
+        Ok(())
+    }
+
+    /// Push and hide the respective endpoints of the given range.
+    ///
+    /// The range should be of the form `<commit>..<commit>` where each
+    /// `<commit>` is in the form accepted by `revparse_single`. The left-hand
+    /// commit will be hidden and the right-hand commit pushed.
+    pub fn push_range(&mut self, range: &str) -> Result<(), Error> {
+        let range = CString::new(range)?;
+        unsafe {
+            try_call!(raw::git_revwalk_push_range(self.raw, range));
+        }
+        Ok(())
+    }
+
+    /// Push the OID pointed to by a reference
+    ///
+    /// The reference must point to a commitish.
+    pub fn push_ref(&mut self, reference: &str) -> Result<(), Error> {
+        let reference = CString::new(reference)?;
+        unsafe {
+            try_call!(raw::git_revwalk_push_ref(self.raw, reference));
+        }
+        Ok(())
+    }
+
+    /// Mark a commit as not of interest to this revwalk.
+    pub fn hide(&mut self, oid: Oid) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_hide(self.raw(), oid.raw()));
+        }
+        Ok(())
+    }
+
+    /// Hide all commits for which the callback returns true from
+    /// the walk.
+    pub fn with_hide_callback<'cb, C>(
+        self,
+        callback: &'cb mut C,
+    ) -> Result<RevwalkWithHideCb<'repo, 'cb, C>, Error>
+    where
+        C: FnMut(Oid) -> bool,
+    {
+        let r = RevwalkWithHideCb {
+            revwalk: self,
+            _marker: marker::PhantomData,
+        };
+        unsafe {
+            raw::git_revwalk_add_hide_cb(
+                r.revwalk.raw(),
+                Some(revwalk_hide_cb::<C>),
+                callback as *mut _ as *mut c_void,
+            );
+        };
+        Ok(r)
+    }
+
+    /// Hide the repository's HEAD
+    ///
+    /// For more information, see `hide`.
+    pub fn hide_head(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_revwalk_hide_head(self.raw()));
+        }
+        Ok(())
+    }
+
+    /// Hide matching references.
+    ///
+    /// The OIDs pointed to by the references that match the given glob pattern
+    /// and their ancestors will be hidden from the output on the revision walk.
+    ///
+    /// A leading 'refs/' is implied if not present as well as a trailing `/ \
+    /// *` if the glob lacks '?', ' \ *' or '['.
+    ///
+    /// Any references matching this glob which do not point to a commitish
+    /// will be ignored.
+    pub fn hide_glob(&mut self, glob: &str) -> Result<(), Error> {
+        let glob = CString::new(glob)?;
+        unsafe {
+            try_call!(raw::git_revwalk_hide_glob(self.raw, glob));
+        }
+        Ok(())
+    }
+
+    /// Hide the OID pointed to by a reference.
+    ///
+    /// The reference must point to a commitish.
+    pub fn hide_ref(&mut self, reference: &str) -> Result<(), Error> {
+        let reference = CString::new(reference)?;
+        unsafe {
+            try_call!(raw::git_revwalk_hide_ref(self.raw, reference));
+        }
+        Ok(())
+    }
+}
+
+impl<'repo> Binding for Revwalk<'repo> {
+    type Raw = *mut raw::git_revwalk;
+    unsafe fn from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo> {
+        Revwalk {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_revwalk {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Revwalk<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_revwalk_free(self.raw) }
+    }
+}
+
+impl<'repo> Iterator for Revwalk<'repo> {
+    type Item = Result<Oid, Error>;
+    fn next(&mut self) -> Option<Result<Oid, Error>> {
+        let mut out: raw::git_oid = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call_iter!(raw::git_revwalk_next(&mut out, self.raw()));
+            Some(Ok(Binding::from_raw(&out as *const _)))
+        }
+    }
+}
+
+impl<'repo, 'cb, C: FnMut(Oid) -> bool> Iterator for RevwalkWithHideCb<'repo, 'cb, C> {
+    type Item = Result<Oid, Error>;
+    fn next(&mut self) -> Option<Result<Oid, Error>> {
+        let out = self.revwalk.next();
+        crate::panic::check();
+        out
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+
+        let mut walk = repo.revwalk().unwrap();
+        walk.push(target).unwrap();
+
+        let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
+
+        assert_eq!(oids.len(), 1);
+        assert_eq!(oids[0], target);
+
+        walk.reset().unwrap();
+        walk.push_head().unwrap();
+        assert_eq!(walk.by_ref().count(), 1);
+
+        walk.reset().unwrap();
+        walk.push_head().unwrap();
+        walk.hide_head().unwrap();
+        assert_eq!(walk.by_ref().count(), 0);
+    }
+
+    #[test]
+    fn smoke_hide_cb() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+
+        let mut walk = repo.revwalk().unwrap();
+        walk.push(target).unwrap();
+
+        let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap();
+
+        assert_eq!(oids.len(), 1);
+        assert_eq!(oids[0], target);
+
+        walk.reset().unwrap();
+        walk.push_head().unwrap();
+        assert_eq!(walk.by_ref().count(), 1);
+
+        walk.reset().unwrap();
+        walk.push_head().unwrap();
+
+        let mut hide_cb = |oid| oid == target;
+        let mut walk = walk.with_hide_callback(&mut hide_cb).unwrap();
+
+        assert_eq!(walk.by_ref().count(), 0);
+
+        let mut walk = walk.into_inner().unwrap();
+        walk.push_head().unwrap();
+        assert_eq!(walk.by_ref().count(), 1);
+    }
+}
diff --git a/git2/src/signature.rs b/git2/src/signature.rs
new file mode 100644 (file)
index 0000000..7c9ffb3
--- /dev/null
@@ -0,0 +1,188 @@
+use std::ffi::CString;
+use std::fmt;
+use std::marker;
+use std::mem;
+use std::ptr;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, Error, Time};
+
+/// A Signature is used to indicate authorship of various actions throughout the
+/// library.
+///
+/// Signatures contain a name, email, and timestamp. All fields can be specified
+/// with `new` while the `now` constructor omits the timestamp. The
+/// [`Repository::signature`] method can be used to create a default signature
+/// with name and email values read from the configuration.
+///
+/// [`Repository::signature`]: struct.Repository.html#method.signature
+pub struct Signature<'a> {
+    raw: *mut raw::git_signature,
+    _marker: marker::PhantomData<&'a str>,
+    owned: bool,
+}
+
+impl<'a> Signature<'a> {
+    /// Create a new action signature with a timestamp of 'now'.
+    ///
+    /// See `new` for more information
+    pub fn now(name: &str, email: &str) -> Result<Signature<'static>, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        let email = CString::new(email)?;
+        unsafe {
+            try_call!(raw::git_signature_now(&mut ret, name, email));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Create a new action signature.
+    ///
+    /// The `time` specified is in seconds since the epoch, and the `offset` is
+    /// the time zone offset in minutes.
+    ///
+    /// Returns error if either `name` or `email` contain angle brackets.
+    pub fn new(name: &str, email: &str, time: &Time) -> Result<Signature<'static>, Error> {
+        crate::init();
+        let mut ret = ptr::null_mut();
+        let name = CString::new(name)?;
+        let email = CString::new(email)?;
+        unsafe {
+            try_call!(raw::git_signature_new(
+                &mut ret,
+                name,
+                email,
+                time.seconds() as raw::git_time_t,
+                time.offset_minutes() as libc::c_int
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Gets the name on the signature.
+    ///
+    /// Returns `None` if the name is not valid utf-8
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Gets the name on the signature as a byte slice.
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }
+    }
+
+    /// Gets the email on the signature.
+    ///
+    /// Returns `None` if the email is not valid utf-8
+    pub fn email(&self) -> Option<&str> {
+        str::from_utf8(self.email_bytes()).ok()
+    }
+
+    /// Gets the email on the signature as a byte slice.
+    pub fn email_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, (*self.raw).email).unwrap() }
+    }
+
+    /// Get the `when` of this signature.
+    pub fn when(&self) -> Time {
+        unsafe { Binding::from_raw((*self.raw).when) }
+    }
+
+    /// Convert a signature of any lifetime into an owned signature with a
+    /// static lifetime.
+    pub fn to_owned(&self) -> Signature<'static> {
+        unsafe {
+            let me = mem::transmute::<&Signature<'a>, &Signature<'static>>(self);
+            me.clone()
+        }
+    }
+}
+
+impl<'a> Binding for Signature<'a> {
+    type Raw = *mut raw::git_signature;
+    unsafe fn from_raw(raw: *mut raw::git_signature) -> Signature<'a> {
+        Signature {
+            raw,
+            _marker: marker::PhantomData,
+            owned: true,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_signature {
+        self.raw
+    }
+}
+
+/// Creates a new signature from the give raw pointer, tied to the lifetime
+/// of the given object.
+///
+/// This function is unsafe as there is no guarantee that `raw` is valid for
+/// `'a` nor if it's a valid pointer.
+pub unsafe fn from_raw_const<'b, T>(_lt: &'b T, raw: *const raw::git_signature) -> Signature<'b> {
+    Signature {
+        raw: raw as *mut raw::git_signature,
+        _marker: marker::PhantomData,
+        owned: false,
+    }
+}
+
+impl Clone for Signature<'static> {
+    fn clone(&self) -> Signature<'static> {
+        // TODO: can this be defined for 'a and just do a plain old copy if the
+        //       lifetime isn't static?
+        let mut raw = ptr::null_mut();
+        let rc = unsafe { raw::git_signature_dup(&mut raw, &*self.raw) };
+        assert_eq!(rc, 0);
+        unsafe { Binding::from_raw(raw) }
+    }
+}
+
+impl<'a> Drop for Signature<'a> {
+    fn drop(&mut self) {
+        if self.owned {
+            unsafe { raw::git_signature_free(self.raw) }
+        }
+    }
+}
+
+impl<'a> fmt::Display for Signature<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{} <{}>",
+            String::from_utf8_lossy(self.name_bytes()),
+            String::from_utf8_lossy(self.email_bytes())
+        )
+    }
+}
+
+impl PartialEq for Signature<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        self.when() == other.when()
+            && self.email_bytes() == other.email_bytes()
+            && self.name_bytes() == other.name_bytes()
+    }
+}
+
+impl Eq for Signature<'_> {}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Signature, Time};
+
+    #[test]
+    fn smoke() {
+        Signature::new("foo", "bar", &Time::new(89, 0)).unwrap();
+        Signature::now("foo", "bar").unwrap();
+        assert!(Signature::new("<foo>", "bar", &Time::new(89, 0)).is_err());
+        assert!(Signature::now("<foo>", "bar").is_err());
+
+        let s = Signature::now("foo", "bar").unwrap();
+        assert_eq!(s.name(), Some("foo"));
+        assert_eq!(s.email(), Some("bar"));
+
+        drop(s.clone());
+        drop(s.to_owned());
+    }
+}
diff --git a/git2/src/stash.rs b/git2/src/stash.rs
new file mode 100644 (file)
index 0000000..ea898e4
--- /dev/null
@@ -0,0 +1,348 @@
+use crate::build::CheckoutBuilder;
+use crate::util::{self, Binding};
+use crate::{panic, raw, IntoCString, Oid, Signature, StashApplyProgress, StashFlags};
+use libc::{c_char, c_int, c_void, size_t};
+use std::ffi::{c_uint, CStr, CString};
+use std::mem;
+
+/// Stash application options structure
+pub struct StashSaveOptions<'a> {
+    message: Option<CString>,
+    flags: Option<StashFlags>,
+    stasher: Signature<'a>,
+    pathspec: Vec<CString>,
+    pathspec_ptrs: Vec<*const c_char>,
+    raw_opts: raw::git_stash_save_options,
+}
+
+impl<'a> StashSaveOptions<'a> {
+    /// Creates a default
+    pub fn new(stasher: Signature<'a>) -> Self {
+        let mut opts = Self {
+            message: None,
+            flags: None,
+            stasher,
+            pathspec: Vec::new(),
+            pathspec_ptrs: Vec::new(),
+            raw_opts: unsafe { mem::zeroed() },
+        };
+        assert_eq!(
+            unsafe {
+                raw::git_stash_save_options_init(
+                    &mut opts.raw_opts,
+                    raw::GIT_STASH_SAVE_OPTIONS_VERSION,
+                )
+            },
+            0
+        );
+        opts
+    }
+
+    /// Customize optional `flags` field
+    pub fn flags(&mut self, flags: Option<StashFlags>) -> &mut Self {
+        self.flags = flags;
+        self
+    }
+
+    /// Add to the array of paths patterns to build the stash.
+    pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut Self {
+        let s = util::cstring_to_repo_path(pathspec).unwrap();
+        self.pathspec_ptrs.push(s.as_ptr());
+        self.pathspec.push(s);
+        self
+    }
+
+    /// Acquire a pointer to the underlying raw options.
+    ///
+    /// This function is unsafe as the pointer is only valid so long as this
+    /// structure is not moved, modified, or used elsewhere.
+    pub unsafe fn raw(&mut self) -> *const raw::git_stash_save_options {
+        self.raw_opts.flags = self.flags.unwrap_or_else(StashFlags::empty).bits() as c_uint;
+        self.raw_opts.message = crate::call::convert(&self.message);
+        self.raw_opts.paths.count = self.pathspec_ptrs.len() as size_t;
+        self.raw_opts.paths.strings = self.pathspec_ptrs.as_ptr() as *mut _;
+        self.raw_opts.stasher = self.stasher.raw();
+
+        &self.raw_opts as *const _
+    }
+}
+
+/// Stash application progress notification function.
+///
+/// Return `true` to continue processing, or `false` to
+/// abort the stash application.
+// FIXME: This probably should have been pub(crate) since it is not used anywhere.
+pub type StashApplyProgressCb<'a> = dyn FnMut(StashApplyProgress) -> bool + 'a;
+
+/// This is a callback function you can provide to iterate over all the
+/// stashed states that will be invoked per entry.
+// FIXME: This probably should have been pub(crate) since it is not used anywhere.
+pub type StashCb<'a> = dyn FnMut(usize, &str, &Oid) -> bool + 'a;
+
+/// Stash application options structure
+pub struct StashApplyOptions<'cb> {
+    progress: Option<Box<StashApplyProgressCb<'cb>>>,
+    checkout_options: Option<CheckoutBuilder<'cb>>,
+    raw_opts: raw::git_stash_apply_options,
+}
+
+impl<'cb> Default for StashApplyOptions<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<'cb> StashApplyOptions<'cb> {
+    /// Creates a default set of merge options.
+    pub fn new() -> StashApplyOptions<'cb> {
+        let mut opts = StashApplyOptions {
+            progress: None,
+            checkout_options: None,
+            raw_opts: unsafe { mem::zeroed() },
+        };
+        assert_eq!(
+            unsafe { raw::git_stash_apply_init_options(&mut opts.raw_opts, 1) },
+            0
+        );
+        opts
+    }
+
+    /// Set stash application flag to GIT_STASH_APPLY_REINSTATE_INDEX
+    pub fn reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb> {
+        self.raw_opts.flags = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32;
+        self
+    }
+
+    /// Options to use when writing files to the working directory
+    pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb> {
+        self.checkout_options = Some(opts);
+        self
+    }
+
+    /// Optional callback to notify the consumer of application progress.
+    ///
+    /// Return `true` to continue processing, or `false` to
+    /// abort the stash application.
+    pub fn progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb>
+    where
+        C: FnMut(StashApplyProgress) -> bool + 'cb,
+    {
+        self.progress = Some(Box::new(callback) as Box<StashApplyProgressCb<'cb>>);
+        self.raw_opts.progress_cb = Some(stash_apply_progress_cb);
+        self.raw_opts.progress_payload = self as *mut _ as *mut _;
+        self
+    }
+
+    /// Pointer to a raw git_stash_apply_options
+    pub fn raw(&mut self) -> &raw::git_stash_apply_options {
+        unsafe {
+            if let Some(opts) = self.checkout_options.as_mut() {
+                opts.configure(&mut self.raw_opts.checkout_options);
+            }
+        }
+        &self.raw_opts
+    }
+}
+
+pub(crate) struct StashCbData<'a> {
+    pub callback: &'a mut StashCb<'a>,
+}
+
+pub(crate) extern "C" fn stash_cb(
+    index: size_t,
+    message: *const c_char,
+    stash_id: *const raw::git_oid,
+    payload: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let data = &mut *(payload as *mut StashCbData<'_>);
+        let res = {
+            let callback = &mut data.callback;
+            callback(
+                index,
+                CStr::from_ptr(message).to_str().unwrap(),
+                &Binding::from_raw(stash_id),
+            )
+        };
+
+        if res {
+            0
+        } else {
+            1
+        }
+    })
+    .unwrap_or(1)
+}
+
+fn convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress {
+    match progress {
+        raw::GIT_STASH_APPLY_PROGRESS_NONE => StashApplyProgress::None,
+        raw::GIT_STASH_APPLY_PROGRESS_LOADING_STASH => StashApplyProgress::LoadingStash,
+        raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX => StashApplyProgress::AnalyzeIndex,
+        raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED => StashApplyProgress::AnalyzeModified,
+        raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED => StashApplyProgress::AnalyzeUntracked,
+        raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED => StashApplyProgress::CheckoutUntracked,
+        raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED => StashApplyProgress::CheckoutModified,
+        raw::GIT_STASH_APPLY_PROGRESS_DONE => StashApplyProgress::Done,
+
+        _ => StashApplyProgress::None,
+    }
+}
+
+extern "C" fn stash_apply_progress_cb(
+    progress: raw::git_stash_apply_progress_t,
+    payload: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let options = &mut *(payload as *mut StashApplyOptions<'_>);
+        let res = {
+            let callback = options.progress.as_mut().unwrap();
+            callback(convert_progress(progress))
+        };
+
+        if res {
+            0
+        } else {
+            -1
+        }
+    })
+    .unwrap_or(-1)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::stash::{StashApplyOptions, StashSaveOptions};
+    use crate::test::repo_init;
+    use crate::{IndexAddOption, Repository, StashFlags, Status};
+    use std::fs;
+    use std::path::{Path, PathBuf};
+
+    fn make_stash<C>(next: C)
+    where
+        C: FnOnce(&mut Repository),
+    {
+        let (_td, mut repo) = repo_init();
+        let signature = repo.signature().unwrap();
+
+        let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
+        println!("using path {:?}", p);
+
+        fs::write(&p, "data".as_bytes()).unwrap();
+
+        let rel_p = Path::new("file_b.txt");
+        assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW);
+
+        repo.stash_save(&signature, "msg1", Some(StashFlags::INCLUDE_UNTRACKED))
+            .unwrap();
+
+        assert!(repo.status_file(&rel_p).is_err());
+
+        let mut count = 0;
+        repo.stash_foreach(|index, name, _oid| {
+            count += 1;
+            assert!(index == 0);
+            assert!(name == "On main: msg1");
+            true
+        })
+        .unwrap();
+
+        assert!(count == 1);
+        next(&mut repo);
+    }
+
+    fn count_stash(repo: &mut Repository) -> usize {
+        let mut count = 0;
+        repo.stash_foreach(|_, _, _| {
+            count += 1;
+            true
+        })
+        .unwrap();
+        count
+    }
+
+    #[test]
+    fn smoke_stash_save_drop() {
+        make_stash(|repo| {
+            repo.stash_drop(0).unwrap();
+            assert!(count_stash(repo) == 0)
+        })
+    }
+
+    #[test]
+    fn smoke_stash_save_pop() {
+        make_stash(|repo| {
+            repo.stash_pop(0, None).unwrap();
+            assert!(count_stash(repo) == 0)
+        })
+    }
+
+    #[test]
+    fn smoke_stash_save_apply() {
+        make_stash(|repo| {
+            let mut options = StashApplyOptions::new();
+            options.progress_cb(|progress| {
+                println!("{:?}", progress);
+                true
+            });
+
+            repo.stash_apply(0, Some(&mut options)).unwrap();
+            assert!(count_stash(repo) == 1)
+        })
+    }
+
+    #[test]
+    fn test_stash_save2_msg_none() {
+        let (_td, mut repo) = repo_init();
+        let signature = repo.signature().unwrap();
+
+        let p = Path::new(repo.workdir().unwrap()).join("file_b.txt");
+
+        fs::write(&p, "data".as_bytes()).unwrap();
+
+        repo.stash_save2(&signature, None, Some(StashFlags::INCLUDE_UNTRACKED))
+            .unwrap();
+
+        let mut stash_name = String::new();
+        repo.stash_foreach(|index, name, _oid| {
+            assert_eq!(index, 0);
+            stash_name = name.to_string();
+            true
+        })
+        .unwrap();
+
+        assert!(stash_name.starts_with("WIP on main:"));
+    }
+
+    fn create_file(r: &Repository, name: &str, data: &str) -> PathBuf {
+        let p = Path::new(r.workdir().unwrap()).join(name);
+        fs::write(&p, data).unwrap();
+        p
+    }
+
+    #[test]
+    fn test_stash_save_ext() {
+        let (_td, mut repo) = repo_init();
+        let signature = repo.signature().unwrap();
+
+        create_file(&repo, "file_a", "foo");
+        create_file(&repo, "file_b", "foo");
+
+        let mut index = repo.index().unwrap();
+        index
+            .add_all(["*"].iter(), IndexAddOption::DEFAULT, None)
+            .unwrap();
+        index.write().unwrap();
+
+        assert_eq!(repo.statuses(None).unwrap().len(), 2);
+
+        let mut opt = StashSaveOptions::new(signature);
+        opt.pathspec("file_a");
+        repo.stash_save_ext(Some(&mut opt)).unwrap();
+
+        assert_eq!(repo.statuses(None).unwrap().len(), 0);
+
+        repo.stash_pop(0, None).unwrap();
+
+        assert_eq!(repo.statuses(None).unwrap().len(), 1);
+    }
+}
diff --git a/git2/src/status.rs b/git2/src/status.rs
new file mode 100644 (file)
index 0000000..a5a8cff
--- /dev/null
@@ -0,0 +1,435 @@
+use libc::{c_char, c_uint, size_t};
+use std::ffi::CString;
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::str;
+
+use crate::util::{self, Binding};
+use crate::{raw, DiffDelta, IntoCString, Repository, Status};
+
+/// Options that can be provided to `repo.statuses()` to control how the status
+/// information is gathered.
+pub struct StatusOptions {
+    raw: raw::git_status_options,
+    pathspec: Vec<CString>,
+    ptrs: Vec<*const c_char>,
+}
+
+/// Enumeration of possible methods of what can be shown through a status
+/// operation.
+#[derive(Copy, Clone)]
+pub enum StatusShow {
+    /// Only gives status based on HEAD to index comparison, not looking at
+    /// working directory changes.
+    Index,
+
+    /// Only gives status based on index to working directory comparison, not
+    /// comparing the index to the HEAD.
+    Workdir,
+
+    /// The default, this roughly matches `git status --porcelain` regarding
+    /// which files are included and in what order.
+    IndexAndWorkdir,
+}
+
+/// A container for a list of status information about a repository.
+///
+/// Each instance appears as if it were a collection, having a length and
+/// allowing indexing, as well as providing an iterator.
+pub struct Statuses<'repo> {
+    raw: *mut raw::git_status_list,
+
+    // Hm, not currently present, but can't hurt?
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+/// An iterator over the statuses in a `Statuses` instance.
+pub struct StatusIter<'statuses> {
+    statuses: &'statuses Statuses<'statuses>,
+    range: Range<usize>,
+}
+
+/// A structure representing an entry in the `Statuses` structure.
+///
+/// Instances are created through the `.iter()` method or the `.get()` method.
+pub struct StatusEntry<'statuses> {
+    raw: *const raw::git_status_entry,
+    _marker: marker::PhantomData<&'statuses DiffDelta<'statuses>>,
+}
+
+impl Default for StatusOptions {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl StatusOptions {
+    /// Creates a new blank set of status options.
+    pub fn new() -> StatusOptions {
+        unsafe {
+            let mut raw = mem::zeroed();
+            let r = raw::git_status_init_options(&mut raw, raw::GIT_STATUS_OPTIONS_VERSION);
+            assert_eq!(r, 0);
+            StatusOptions {
+                raw,
+                pathspec: Vec::new(),
+                ptrs: Vec::new(),
+            }
+        }
+    }
+
+    /// Select the files on which to report status.
+    ///
+    /// The default, if unspecified, is to show the index and the working
+    /// directory.
+    pub fn show(&mut self, show: StatusShow) -> &mut StatusOptions {
+        self.raw.show = match show {
+            StatusShow::Index => raw::GIT_STATUS_SHOW_INDEX_ONLY,
+            StatusShow::Workdir => raw::GIT_STATUS_SHOW_WORKDIR_ONLY,
+            StatusShow::IndexAndWorkdir => raw::GIT_STATUS_SHOW_INDEX_AND_WORKDIR,
+        };
+        self
+    }
+
+    /// Add a path pattern to match (using fnmatch-style matching).
+    ///
+    /// If the `disable_pathspec_match` option is given, then this is a literal
+    /// path to match. If this is not called, then there will be no patterns to
+    /// match and the entire directory will be used.
+    pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut StatusOptions {
+        let s = util::cstring_to_repo_path(pathspec).unwrap();
+        self.ptrs.push(s.as_ptr());
+        self.pathspec.push(s);
+        self
+    }
+
+    fn flag(&mut self, flag: raw::git_status_opt_t, val: bool) -> &mut StatusOptions {
+        if val {
+            self.raw.flags |= flag as c_uint;
+        } else {
+            self.raw.flags &= !(flag as c_uint);
+        }
+        self
+    }
+
+    /// Flag whether untracked files will be included.
+    ///
+    /// Untracked files will only be included if the workdir files are included
+    /// in the status "show" option.
+    pub fn include_untracked(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNTRACKED, include)
+    }
+
+    /// Flag whether ignored files will be included.
+    ///
+    /// The files will only be included if the workdir files are included
+    /// in the status "show" option.
+    pub fn include_ignored(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_INCLUDE_IGNORED, include)
+    }
+
+    /// Flag to include unmodified files.
+    pub fn include_unmodified(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNMODIFIED, include)
+    }
+
+    /// Flag that submodules should be skipped.
+    ///
+    /// This only applies if there are no pending typechanges to the submodule
+    /// (either from or to another type).
+    pub fn exclude_submodules(&mut self, exclude: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_EXCLUDE_SUBMODULES, exclude)
+    }
+
+    /// Flag that all files in untracked directories should be included.
+    ///
+    /// Normally if an entire directory is new then just the top-level directory
+    /// is included (with a trailing slash on the entry name).
+    pub fn recurse_untracked_dirs(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS, include)
+    }
+
+    /// Indicates that the given paths should be treated as literals paths, note
+    /// patterns.
+    pub fn disable_pathspec_match(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH, include)
+    }
+
+    /// Indicates that the contents of ignored directories should be included in
+    /// the status.
+    pub fn recurse_ignored_dirs(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_RECURSE_IGNORED_DIRS, include)
+    }
+
+    /// Indicates that rename detection should be processed between the head.
+    pub fn renames_head_to_index(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX, include)
+    }
+
+    /// Indicates that rename detection should be run between the index and the
+    /// working directory.
+    pub fn renames_index_to_workdir(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR, include)
+    }
+
+    /// Override the native case sensitivity for the file system and force the
+    /// output to be in case sensitive order.
+    pub fn sort_case_sensitively(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_SORT_CASE_SENSITIVELY, include)
+    }
+
+    /// Override the native case sensitivity for the file system and force the
+    /// output to be in case-insensitive order.
+    pub fn sort_case_insensitively(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY, include)
+    }
+
+    /// Indicates that rename detection should include rewritten files.
+    pub fn renames_from_rewrites(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_RENAMES_FROM_REWRITES, include)
+    }
+
+    /// Bypasses the default status behavior of doing a "soft" index reload.
+    pub fn no_refresh(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_NO_REFRESH, include)
+    }
+
+    /// Refresh the stat cache in the index for files are unchanged but have
+    /// out of date stat information in the index.
+    ///
+    /// This will result in less work being done on subsequent calls to fetching
+    /// the status.
+    pub fn update_index(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_UPDATE_INDEX, include)
+    }
+
+    // erm...
+    #[allow(missing_docs)]
+    pub fn include_unreadable(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE, include)
+    }
+
+    // erm...
+    #[allow(missing_docs)]
+    pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut StatusOptions {
+        self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
+    }
+
+    /// Set threshold above which similar files will be considered renames.
+    ///
+    /// This is equivalent to the `-M` option. Defaults to 50.
+    pub fn rename_threshold(&mut self, threshold: u16) -> &mut StatusOptions {
+        self.raw.rename_threshold = threshold;
+        self
+    }
+
+    /// Get a pointer to the inner list of status options.
+    ///
+    /// This function is unsafe as the returned structure has interior pointers
+    /// and may no longer be valid if these options continue to be mutated.
+    pub unsafe fn raw(&mut self) -> *const raw::git_status_options {
+        self.raw.pathspec.strings = self.ptrs.as_ptr() as *mut _;
+        self.raw.pathspec.count = self.ptrs.len() as size_t;
+        &self.raw
+    }
+}
+
+impl<'repo> Statuses<'repo> {
+    /// Gets a status entry from this list at the specified index.
+    ///
+    /// Returns `None` if the index is out of bounds.
+    pub fn get(&self, index: usize) -> Option<StatusEntry<'_>> {
+        unsafe {
+            let p = raw::git_status_byindex(self.raw, index as size_t);
+            Binding::from_raw_opt(p)
+        }
+    }
+
+    /// Gets the count of status entries in this list.
+    ///
+    /// If there are no changes in status (according to the options given
+    /// when the status list was created), this should return 0.
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_status_list_entrycount(self.raw) as usize }
+    }
+
+    /// Return `true` if there is no status entry in this list.
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Returns an iterator over the statuses in this list.
+    pub fn iter(&self) -> StatusIter<'_> {
+        StatusIter {
+            statuses: self,
+            range: 0..self.len(),
+        }
+    }
+}
+
+impl<'repo> Binding for Statuses<'repo> {
+    type Raw = *mut raw::git_status_list;
+    unsafe fn from_raw(raw: *mut raw::git_status_list) -> Statuses<'repo> {
+        Statuses {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_status_list {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Statuses<'repo> {
+    fn drop(&mut self) {
+        unsafe {
+            raw::git_status_list_free(self.raw);
+        }
+    }
+}
+
+impl<'a> Iterator for StatusIter<'a> {
+    type Item = StatusEntry<'a>;
+    fn next(&mut self) -> Option<StatusEntry<'a>> {
+        self.range.next().and_then(|i| self.statuses.get(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'a> DoubleEndedIterator for StatusIter<'a> {
+    fn next_back(&mut self) -> Option<StatusEntry<'a>> {
+        self.range.next_back().and_then(|i| self.statuses.get(i))
+    }
+}
+impl<'a> FusedIterator for StatusIter<'a> {}
+impl<'a> ExactSizeIterator for StatusIter<'a> {}
+
+impl<'a> IntoIterator for &'a Statuses<'a> {
+    type Item = StatusEntry<'a>;
+    type IntoIter = StatusIter<'a>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl<'statuses> StatusEntry<'statuses> {
+    /// Access the bytes for this entry's corresponding pathname
+    pub fn path_bytes(&self) -> &[u8] {
+        unsafe {
+            if (*self.raw).head_to_index.is_null() {
+                crate::opt_bytes(self, (*(*self.raw).index_to_workdir).old_file.path)
+            } else {
+                crate::opt_bytes(self, (*(*self.raw).head_to_index).old_file.path)
+            }
+            .unwrap()
+        }
+    }
+
+    /// Access this entry's path name as a string.
+    ///
+    /// Returns `None` if the path is not valid utf-8.
+    pub fn path(&self) -> Option<&str> {
+        str::from_utf8(self.path_bytes()).ok()
+    }
+
+    /// Access the status flags for this file
+    pub fn status(&self) -> Status {
+        Status::from_bits_truncate(unsafe { (*self.raw).status as u32 })
+    }
+
+    /// Access detailed information about the differences between the file in
+    /// HEAD and the file in the index.
+    pub fn head_to_index(&self) -> Option<DiffDelta<'statuses>> {
+        unsafe { Binding::from_raw_opt((*self.raw).head_to_index) }
+    }
+
+    /// Access detailed information about the differences between the file in
+    /// the index and the file in the working directory.
+    pub fn index_to_workdir(&self) -> Option<DiffDelta<'statuses>> {
+        unsafe { Binding::from_raw_opt((*self.raw).index_to_workdir) }
+    }
+}
+
+impl<'statuses> Binding for StatusEntry<'statuses> {
+    type Raw = *const raw::git_status_entry;
+
+    unsafe fn from_raw(raw: *const raw::git_status_entry) -> StatusEntry<'statuses> {
+        StatusEntry {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *const raw::git_status_entry {
+        self.raw
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::StatusOptions;
+    use std::fs::File;
+    use std::io::prelude::*;
+    use std::path::Path;
+
+    #[test]
+    fn smoke() {
+        let (td, repo) = crate::test::repo_init();
+        assert_eq!(repo.statuses(None).unwrap().len(), 0);
+        File::create(&td.path().join("foo")).unwrap();
+        let statuses = repo.statuses(None).unwrap();
+        assert_eq!(statuses.iter().count(), 1);
+        let status = statuses.iter().next().unwrap();
+        assert_eq!(status.path(), Some("foo"));
+        assert!(status.status().contains(crate::Status::WT_NEW));
+        assert!(!status.status().contains(crate::Status::INDEX_NEW));
+        assert!(status.head_to_index().is_none());
+        let diff = status.index_to_workdir().unwrap();
+        assert_eq!(diff.old_file().path_bytes().unwrap(), b"foo");
+        assert_eq!(diff.new_file().path_bytes().unwrap(), b"foo");
+    }
+
+    #[test]
+    fn filter() {
+        let (td, repo) = crate::test::repo_init();
+        t!(File::create(&td.path().join("foo")));
+        t!(File::create(&td.path().join("bar")));
+        let mut opts = StatusOptions::new();
+        opts.include_untracked(true).pathspec("foo");
+
+        let statuses = t!(repo.statuses(Some(&mut opts)));
+        assert_eq!(statuses.iter().count(), 1);
+        let status = statuses.iter().next().unwrap();
+        assert_eq!(status.path(), Some("foo"));
+    }
+
+    #[test]
+    fn gitignore() {
+        let (td, repo) = crate::test::repo_init();
+        t!(t!(File::create(td.path().join(".gitignore"))).write_all(b"foo\n"));
+        assert!(!t!(repo.status_should_ignore(Path::new("bar"))));
+        assert!(t!(repo.status_should_ignore(Path::new("foo"))));
+    }
+
+    #[test]
+    fn status_file() {
+        let (td, repo) = crate::test::repo_init();
+        assert!(repo.status_file(Path::new("foo")).is_err());
+        if cfg!(windows) {
+            assert!(repo.status_file(Path::new("bar\\foo.txt")).is_err());
+        }
+        t!(File::create(td.path().join("foo")));
+        if cfg!(windows) {
+            t!(::std::fs::create_dir_all(td.path().join("bar")));
+            t!(File::create(td.path().join("bar").join("foo.txt")));
+        }
+        let status = t!(repo.status_file(Path::new("foo")));
+        assert!(status.contains(crate::Status::WT_NEW));
+        if cfg!(windows) {
+            let status = t!(repo.status_file(Path::new("bar\\foo.txt")));
+            assert!(status.contains(crate::Status::WT_NEW));
+        }
+    }
+}
diff --git a/git2/src/string_array.rs b/git2/src/string_array.rs
new file mode 100644 (file)
index 0000000..c77ccda
--- /dev/null
@@ -0,0 +1,136 @@
+//! Bindings to libgit2's raw `git_strarray` type
+
+use std::iter::FusedIterator;
+use std::ops::Range;
+use std::str;
+
+use crate::raw;
+use crate::util::Binding;
+
+/// A string array structure used by libgit2
+///
+/// Some APIs return arrays of strings which originate from libgit2. This
+/// wrapper type behaves a little like `Vec<&str>` but does so without copying
+/// the underlying strings until necessary.
+pub struct StringArray {
+    raw: raw::git_strarray,
+}
+
+/// A forward iterator over the strings of an array, casted to `&str`.
+pub struct Iter<'a> {
+    range: Range<usize>,
+    arr: &'a StringArray,
+}
+
+/// A forward iterator over the strings of an array, casted to `&[u8]`.
+pub struct IterBytes<'a> {
+    range: Range<usize>,
+    arr: &'a StringArray,
+}
+
+impl StringArray {
+    /// Returns None if the i'th string is not utf8 or if i is out of bounds.
+    pub fn get(&self, i: usize) -> Option<&str> {
+        self.get_bytes(i).and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Returns None if `i` is out of bounds.
+    pub fn get_bytes(&self, i: usize) -> Option<&[u8]> {
+        if i < self.raw.count as usize {
+            unsafe {
+                let ptr = *self.raw.strings.add(i) as *const _;
+                Some(crate::opt_bytes(self, ptr).unwrap())
+            }
+        } else {
+            None
+        }
+    }
+
+    /// Returns an iterator over the strings contained within this array.
+    ///
+    /// The iterator yields `Option<&str>` as it is unknown whether the contents
+    /// are utf-8 or not.
+    pub fn iter(&self) -> Iter<'_> {
+        Iter {
+            range: 0..self.len(),
+            arr: self,
+        }
+    }
+
+    /// Returns an iterator over the strings contained within this array,
+    /// yielding byte slices.
+    pub fn iter_bytes(&self) -> IterBytes<'_> {
+        IterBytes {
+            range: 0..self.len(),
+            arr: self,
+        }
+    }
+
+    /// Returns the number of strings in this array.
+    pub fn len(&self) -> usize {
+        self.raw.count as usize
+    }
+
+    /// Return `true` if this array is empty.
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+}
+
+impl Binding for StringArray {
+    type Raw = raw::git_strarray;
+    unsafe fn from_raw(raw: raw::git_strarray) -> StringArray {
+        StringArray { raw }
+    }
+    fn raw(&self) -> raw::git_strarray {
+        self.raw
+    }
+}
+
+impl<'a> IntoIterator for &'a StringArray {
+    type Item = Option<&'a str>;
+    type IntoIter = Iter<'a>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+impl<'a> Iterator for Iter<'a> {
+    type Item = Option<&'a str>;
+    fn next(&mut self) -> Option<Option<&'a str>> {
+        self.range.next().map(|i| self.arr.get(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'a> DoubleEndedIterator for Iter<'a> {
+    fn next_back(&mut self) -> Option<Option<&'a str>> {
+        self.range.next_back().map(|i| self.arr.get(i))
+    }
+}
+impl<'a> FusedIterator for Iter<'a> {}
+impl<'a> ExactSizeIterator for Iter<'a> {}
+
+impl<'a> Iterator for IterBytes<'a> {
+    type Item = &'a [u8];
+    fn next(&mut self) -> Option<&'a [u8]> {
+        self.range.next().and_then(|i| self.arr.get_bytes(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+}
+impl<'a> DoubleEndedIterator for IterBytes<'a> {
+    fn next_back(&mut self) -> Option<&'a [u8]> {
+        self.range.next_back().and_then(|i| self.arr.get_bytes(i))
+    }
+}
+impl<'a> FusedIterator for IterBytes<'a> {}
+impl<'a> ExactSizeIterator for IterBytes<'a> {}
+
+impl Drop for StringArray {
+    fn drop(&mut self) {
+        unsafe { raw::git_strarray_free(&mut self.raw) }
+    }
+}
diff --git a/git2/src/submodule.rs b/git2/src/submodule.rs
new file mode 100644 (file)
index 0000000..06a6359
--- /dev/null
@@ -0,0 +1,471 @@
+use std::marker;
+use std::mem;
+use std::os::raw::c_int;
+use std::path::Path;
+use std::ptr;
+use std::str;
+
+use crate::util::{self, Binding};
+use crate::{build::CheckoutBuilder, SubmoduleIgnore, SubmoduleUpdate};
+use crate::{raw, Error, FetchOptions, Oid, Repository};
+
+/// A structure to represent a git [submodule][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Tools-Submodules
+pub struct Submodule<'repo> {
+    raw: *mut raw::git_submodule,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl<'repo> Submodule<'repo> {
+    /// Get the submodule's branch.
+    ///
+    /// Returns `None` if the branch is not valid utf-8 or if the branch is not
+    /// yet available.
+    pub fn branch(&self) -> Option<&str> {
+        self.branch_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the branch for the submodule.
+    ///
+    /// Returns `None` if the branch is not yet available.
+    pub fn branch_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_submodule_branch(self.raw)) }
+    }
+
+    /// Perform the clone step for a newly created submodule.
+    ///
+    /// This performs the necessary `git_clone` to setup a newly-created submodule.
+    pub fn clone(
+        &mut self,
+        opts: Option<&mut SubmoduleUpdateOptions<'_>>,
+    ) -> Result<Repository, Error> {
+        unsafe {
+            let raw_opts = opts.map(|o| o.raw());
+            let mut raw_repo = ptr::null_mut();
+            try_call!(raw::git_submodule_clone(
+                &mut raw_repo,
+                self.raw,
+                raw_opts.as_ref()
+            ));
+            Ok(Binding::from_raw(raw_repo))
+        }
+    }
+
+    /// Get the submodule's URL.
+    ///
+    /// Returns `None` if the URL is not valid utf-8 or if the URL isn't present
+    pub fn url(&self) -> Option<&str> {
+        self.opt_url_bytes().and_then(|b| str::from_utf8(b).ok())
+    }
+
+    /// Get the URL for the submodule.
+    #[doc(hidden)]
+    #[deprecated(note = "renamed to `opt_url_bytes`")]
+    pub fn url_bytes(&self) -> &[u8] {
+        self.opt_url_bytes().unwrap()
+    }
+
+    /// Get the URL for the submodule.
+    ///
+    /// Returns `None` if the URL isn't present
+    // TODO: delete this method and fix the signature of `url_bytes` on next
+    // major version bump
+    pub fn opt_url_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_submodule_url(self.raw)) }
+    }
+
+    /// Get the submodule's name.
+    ///
+    /// Returns `None` if the name is not valid utf-8
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Get the name for the submodule.
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap() }
+    }
+
+    /// Get the path for the submodule.
+    pub fn path(&self) -> &Path {
+        util::bytes2path(unsafe {
+            crate::opt_bytes(self, raw::git_submodule_path(self.raw)).unwrap()
+        })
+    }
+
+    /// Get the OID for the submodule in the current HEAD tree.
+    pub fn head_id(&self) -> Option<Oid> {
+        unsafe { Binding::from_raw_opt(raw::git_submodule_head_id(self.raw)) }
+    }
+
+    /// Get the OID for the submodule in the index.
+    pub fn index_id(&self) -> Option<Oid> {
+        unsafe { Binding::from_raw_opt(raw::git_submodule_index_id(self.raw)) }
+    }
+
+    /// Get the OID for the submodule in the current working directory.
+    ///
+    /// This returns the OID that corresponds to looking up 'HEAD' in the
+    /// checked out submodule. If there are pending changes in the index or
+    /// anything else, this won't notice that.
+    pub fn workdir_id(&self) -> Option<Oid> {
+        unsafe { Binding::from_raw_opt(raw::git_submodule_wd_id(self.raw)) }
+    }
+
+    /// Get the ignore rule that will be used for the submodule.
+    pub fn ignore_rule(&self) -> SubmoduleIgnore {
+        SubmoduleIgnore::from_raw(unsafe { raw::git_submodule_ignore(self.raw) })
+    }
+
+    /// Get the update rule that will be used for the submodule.
+    pub fn update_strategy(&self) -> SubmoduleUpdate {
+        SubmoduleUpdate::from_raw(unsafe { raw::git_submodule_update_strategy(self.raw) })
+    }
+
+    /// Copy submodule info into ".git/config" file.
+    ///
+    /// Just like "git submodule init", this copies information about the
+    /// submodule into ".git/config". You can use the accessor functions above
+    /// to alter the in-memory git_submodule object and control what is written
+    /// to the config, overriding what is in .gitmodules.
+    ///
+    /// By default, existing entries will not be overwritten, but passing `true`
+    /// for `overwrite` forces them to be updated.
+    pub fn init(&mut self, overwrite: bool) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_submodule_init(self.raw, overwrite));
+        }
+        Ok(())
+    }
+
+    /// Set up the subrepository for a submodule in preparation for clone.
+    ///
+    /// This function can be called to init and set up a submodule repository
+    /// from a submodule in preparation to clone it from its remote.
+
+    /// use_gitlink: Should the workdir contain a gitlink to the repo in
+    /// .git/modules vs. repo directly in workdir.
+    pub fn repo_init(&mut self, use_gitlink: bool) -> Result<Repository, Error> {
+        unsafe {
+            let mut raw_repo = ptr::null_mut();
+            try_call!(raw::git_submodule_repo_init(
+                &mut raw_repo,
+                self.raw,
+                use_gitlink
+            ));
+            Ok(Binding::from_raw(raw_repo))
+        }
+    }
+
+    /// Open the repository for a submodule.
+    ///
+    /// This will only work if the submodule is checked out into the working
+    /// directory.
+    pub fn open(&self) -> Result<Repository, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_submodule_open(&mut raw, self.raw));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Reread submodule info from config, index, and HEAD.
+    ///
+    /// Call this to reread cached submodule information for this submodule if
+    /// you have reason to believe that it has changed.
+    ///
+    /// If `force` is `true`, then data will be reloaded even if it doesn't seem
+    /// out of date
+    pub fn reload(&mut self, force: bool) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_submodule_reload(self.raw, force));
+        }
+        Ok(())
+    }
+
+    /// Copy submodule remote info into submodule repo.
+    ///
+    /// This copies the information about the submodules URL into the checked
+    /// out submodule config, acting like "git submodule sync". This is useful
+    /// if you have altered the URL for the submodule (or it has been altered
+    /// by a fetch of upstream changes) and you need to update your local repo.
+    pub fn sync(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_submodule_sync(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Add current submodule HEAD commit to index of superproject.
+    ///
+    /// If `write_index` is true, then the index file will be immediately
+    /// written. Otherwise you must explicitly call `write()` on an `Index`
+    /// later on.
+    pub fn add_to_index(&mut self, write_index: bool) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_submodule_add_to_index(self.raw, write_index));
+        }
+        Ok(())
+    }
+
+    /// Resolve the setup of a new git submodule.
+    ///
+    /// This should be called on a submodule once you have called add setup and
+    /// done the clone of the submodule. This adds the .gitmodules file and the
+    /// newly cloned submodule to the index to be ready to be committed (but
+    /// doesn't actually do the commit).
+    pub fn add_finalize(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_submodule_add_finalize(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Update submodule.
+    ///
+    /// This will clone a missing submodule and check out the subrepository to
+    /// the commit specified in the index of the containing repository. If
+    /// the submodule repository doesn't contain the target commit, then the
+    /// submodule is fetched using the fetch options supplied in `opts`.
+    ///
+    /// `init` indicates if the submodule should be initialized first if it has
+    /// not been initialized yet.
+    pub fn update(
+        &mut self,
+        init: bool,
+        opts: Option<&mut SubmoduleUpdateOptions<'_>>,
+    ) -> Result<(), Error> {
+        unsafe {
+            let mut raw_opts = opts.map(|o| o.raw());
+            try_call!(raw::git_submodule_update(
+                self.raw,
+                init as c_int,
+                raw_opts.as_mut().map_or(ptr::null_mut(), |o| o)
+            ));
+        }
+        Ok(())
+    }
+}
+
+impl<'repo> Binding for Submodule<'repo> {
+    type Raw = *mut raw::git_submodule;
+    unsafe fn from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo> {
+        Submodule {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_submodule {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for Submodule<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_submodule_free(self.raw) }
+    }
+}
+
+/// Options to update a submodule.
+pub struct SubmoduleUpdateOptions<'cb> {
+    checkout_builder: CheckoutBuilder<'cb>,
+    fetch_opts: FetchOptions<'cb>,
+    allow_fetch: bool,
+}
+
+impl<'cb> SubmoduleUpdateOptions<'cb> {
+    /// Return default options.
+    pub fn new() -> Self {
+        SubmoduleUpdateOptions {
+            checkout_builder: CheckoutBuilder::new(),
+            fetch_opts: FetchOptions::new(),
+            allow_fetch: true,
+        }
+    }
+
+    unsafe fn raw(&mut self) -> raw::git_submodule_update_options {
+        let mut checkout_opts: raw::git_checkout_options = mem::zeroed();
+        let init_res =
+            raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION);
+        assert_eq!(0, init_res);
+        self.checkout_builder.configure(&mut checkout_opts);
+        let opts = raw::git_submodule_update_options {
+            version: raw::GIT_SUBMODULE_UPDATE_OPTIONS_VERSION,
+            checkout_opts,
+            fetch_opts: self.fetch_opts.raw(),
+            allow_fetch: self.allow_fetch as c_int,
+        };
+        opts
+    }
+
+    /// Set checkout options.
+    pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self {
+        self.checkout_builder = opts;
+        self
+    }
+
+    /// Set fetch options and allow fetching.
+    pub fn fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self {
+        self.fetch_opts = opts;
+        self.allow_fetch = true;
+        self
+    }
+
+    /// Allow or disallow fetching.
+    pub fn allow_fetch(&mut self, b: bool) -> &mut Self {
+        self.allow_fetch = b;
+        self
+    }
+}
+
+impl<'cb> Default for SubmoduleUpdateOptions<'cb> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::fs;
+    use std::path::Path;
+    use tempfile::TempDir;
+    use url::Url;
+
+    use crate::Repository;
+    use crate::SubmoduleUpdateOptions;
+
+    #[test]
+    fn smoke() {
+        let td = TempDir::new().unwrap();
+        let repo = Repository::init(td.path()).unwrap();
+        let mut s1 = repo
+            .submodule("/path/to/nowhere", Path::new("foo"), true)
+            .unwrap();
+        s1.init(false).unwrap();
+        s1.sync().unwrap();
+
+        let s2 = repo
+            .submodule("/path/to/nowhere", Path::new("bar"), true)
+            .unwrap();
+        drop((s1, s2));
+
+        let mut submodules = repo.submodules().unwrap();
+        assert_eq!(submodules.len(), 2);
+        let mut s = submodules.remove(0);
+        assert_eq!(s.name(), Some("bar"));
+        assert_eq!(s.url(), Some("/path/to/nowhere"));
+        assert_eq!(s.branch(), None);
+        assert!(s.head_id().is_none());
+        assert!(s.index_id().is_none());
+        assert!(s.workdir_id().is_none());
+
+        repo.find_submodule("bar").unwrap();
+        s.open().unwrap();
+        assert!(s.path() == Path::new("bar"));
+        s.reload(true).unwrap();
+    }
+
+    #[test]
+    fn add_a_submodule() {
+        let (_td, repo1) = crate::test::repo_init();
+        let (td, repo2) = crate::test::repo_init();
+
+        let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
+        let mut s = repo2
+            .submodule(&url.to_string(), Path::new("bar"), true)
+            .unwrap();
+        t!(fs::remove_dir_all(td.path().join("bar")));
+        t!(Repository::clone(&url.to_string(), td.path().join("bar")));
+        t!(s.add_to_index(false));
+        t!(s.add_finalize());
+    }
+
+    #[test]
+    fn update_submodule() {
+        // -----------------------------------
+        // Same as `add_a_submodule()`
+        let (_td, repo1) = crate::test::repo_init();
+        let (td, repo2) = crate::test::repo_init();
+
+        let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
+        let mut s = repo2
+            .submodule(&url.to_string(), Path::new("bar"), true)
+            .unwrap();
+        t!(fs::remove_dir_all(td.path().join("bar")));
+        t!(Repository::clone(&url.to_string(), td.path().join("bar")));
+        t!(s.add_to_index(false));
+        t!(s.add_finalize());
+        // -----------------------------------
+
+        // Attempt to update submodule
+        let submodules = t!(repo1.submodules());
+        for mut submodule in submodules {
+            let mut submodule_options = SubmoduleUpdateOptions::new();
+            let init = true;
+            let opts = Some(&mut submodule_options);
+
+            t!(submodule.update(init, opts));
+        }
+    }
+
+    #[test]
+    fn clone_submodule() {
+        // -----------------------------------
+        // Same as `add_a_submodule()`
+        let (_td, repo1) = crate::test::repo_init();
+        let (_td, repo2) = crate::test::repo_init();
+        let (_td, parent) = crate::test::repo_init();
+
+        let url1 = Url::from_file_path(&repo1.workdir().unwrap()).unwrap();
+        let url2 = Url::from_file_path(&repo2.workdir().unwrap()).unwrap();
+        let mut s1 = parent
+            .submodule(&url1.to_string(), Path::new("bar"), true)
+            .unwrap();
+        let mut s2 = parent
+            .submodule(&url2.to_string(), Path::new("bar2"), true)
+            .unwrap();
+        // -----------------------------------
+
+        t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default())));
+        t!(s2.clone(None));
+    }
+
+    #[test]
+    fn repo_init_submodule() {
+        // -----------------------------------
+        // Same as `clone_submodule()`
+        let (_td, child) = crate::test::repo_init();
+        let (_td, parent) = crate::test::repo_init();
+
+        let url_child = Url::from_file_path(&child.workdir().unwrap()).unwrap();
+        let url_parent = Url::from_file_path(&parent.workdir().unwrap()).unwrap();
+        let mut sub = parent
+            .submodule(&url_child.to_string(), Path::new("bar"), true)
+            .unwrap();
+
+        // -----------------------------------
+        // Let's commit the submodule for later clone
+        t!(sub.clone(None));
+        t!(sub.add_to_index(true));
+        t!(sub.add_finalize());
+
+        crate::test::commit(&parent);
+
+        // Clone the parent to init its submodules
+        let td = TempDir::new().unwrap();
+        let new_parent = Repository::clone(&url_parent.to_string(), &td).unwrap();
+
+        let mut submodules = new_parent.submodules().unwrap();
+        let child = submodules.first_mut().unwrap();
+
+        // First init child
+        t!(child.init(false));
+        assert_eq!(child.url().unwrap(), url_child.as_str());
+
+        // open() is not possible before initializing the repo
+        assert!(child.open().is_err());
+        t!(child.repo_init(true));
+        assert!(child.open().is_ok());
+    }
+}
diff --git a/git2/src/tag.rs b/git2/src/tag.rs
new file mode 100644 (file)
index 0000000..6986c7c
--- /dev/null
@@ -0,0 +1,234 @@
+use std::ffi::CString;
+use std::marker;
+use std::mem;
+use std::ptr;
+use std::str;
+
+use crate::util::Binding;
+use crate::{call, raw, signature, Error, Object, ObjectType, Oid, Signature};
+
+/// A structure to represent a git [tag][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Basics-Tagging
+pub struct Tag<'repo> {
+    raw: *mut raw::git_tag,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+impl<'repo> Tag<'repo> {
+    /// Determine whether a tag name is valid, meaning that (when prefixed with refs/tags/) that
+    /// it is a valid reference name, and that any additional tag name restrictions are imposed
+    /// (eg, it cannot start with a -).
+    pub fn is_valid_name(tag_name: &str) -> bool {
+        crate::init();
+        let tag_name = CString::new(tag_name).unwrap();
+        let mut valid: libc::c_int = 0;
+        unsafe {
+            call::c_try(raw::git_tag_name_is_valid(&mut valid, tag_name.as_ptr())).unwrap();
+        }
+        valid == 1
+    }
+
+    /// Get the id (SHA1) of a repository tag
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_tag_id(&*self.raw)) }
+    }
+
+    /// Get the message of a tag
+    ///
+    /// Returns None if there is no message or if it is not valid utf8
+    pub fn message(&self) -> Option<&str> {
+        self.message_bytes().and_then(|s| str::from_utf8(s).ok())
+    }
+
+    /// Get the message of a tag
+    ///
+    /// Returns None if there is no message
+    pub fn message_bytes(&self) -> Option<&[u8]> {
+        unsafe { crate::opt_bytes(self, raw::git_tag_message(&*self.raw)) }
+    }
+
+    /// Get the name of a tag
+    ///
+    /// Returns None if it is not valid utf8
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Get the name of a tag
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_tag_name(&*self.raw)).unwrap() }
+    }
+
+    /// Recursively peel a tag until a non tag git_object is found
+    pub fn peel(&self) -> Result<Object<'repo>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tag_peel(&mut ret, &*self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the tagger (author) of a tag
+    ///
+    /// If the author is unspecified, then `None` is returned.
+    pub fn tagger(&self) -> Option<Signature<'_>> {
+        unsafe {
+            let ptr = raw::git_tag_tagger(&*self.raw);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(signature::from_raw_const(self, ptr))
+            }
+        }
+    }
+
+    /// Get the tagged object of a tag
+    ///
+    /// This method performs a repository lookup for the given object and
+    /// returns it
+    pub fn target(&self) -> Result<Object<'repo>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tag_target(&mut ret, &*self.raw));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the OID of the tagged object of a tag
+    pub fn target_id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_tag_target_id(&*self.raw)) }
+    }
+
+    /// Get the ObjectType of the tagged object of a tag
+    pub fn target_type(&self) -> Option<ObjectType> {
+        unsafe { ObjectType::from_raw(raw::git_tag_target_type(&*self.raw)) }
+    }
+
+    /// Casts this Tag to be usable as an `Object`
+    pub fn as_object(&self) -> &Object<'repo> {
+        unsafe { &*(self as *const _ as *const Object<'repo>) }
+    }
+
+    /// Consumes Tag to be returned as an `Object`
+    pub fn into_object(self) -> Object<'repo> {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
+        unsafe { mem::transmute(self) }
+    }
+}
+
+impl<'repo> std::fmt::Debug for Tag<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        let mut ds = f.debug_struct("Tag");
+        if let Some(name) = self.name() {
+            ds.field("name", &name);
+        }
+        ds.field("id", &self.id());
+        ds.finish()
+    }
+}
+
+impl<'repo> Binding for Tag<'repo> {
+    type Raw = *mut raw::git_tag;
+    unsafe fn from_raw(raw: *mut raw::git_tag) -> Tag<'repo> {
+        Tag {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_tag {
+        self.raw
+    }
+}
+
+impl<'repo> Clone for Tag<'repo> {
+    fn clone(&self) -> Self {
+        self.as_object().clone().into_tag().ok().unwrap()
+    }
+}
+
+impl<'repo> Drop for Tag<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_tag_free(self.raw) }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::Tag;
+
+    // Reference -- https://git-scm.com/docs/git-check-ref-format
+    #[test]
+    fn name_is_valid() {
+        assert_eq!(Tag::is_valid_name("blah_blah"), true);
+        assert_eq!(Tag::is_valid_name("v1.2.3"), true);
+        assert_eq!(Tag::is_valid_name("my/tag"), true);
+        assert_eq!(Tag::is_valid_name("@"), true);
+
+        assert_eq!(Tag::is_valid_name("-foo"), false);
+        assert_eq!(Tag::is_valid_name("foo:bar"), false);
+        assert_eq!(Tag::is_valid_name("foo^bar"), false);
+        assert_eq!(Tag::is_valid_name("foo."), false);
+        assert_eq!(Tag::is_valid_name("@{"), false);
+        assert_eq!(Tag::is_valid_name("as\\cd"), false);
+    }
+
+    #[test]
+    #[should_panic]
+    fn is_valid_name_for_invalid_tag() {
+        Tag::is_valid_name("ab\012");
+    }
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let id = head.target().unwrap();
+        assert!(repo.find_tag(id).is_err());
+
+        let obj = repo.find_object(id, None).unwrap();
+        let sig = repo.signature().unwrap();
+        let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap();
+        let tag = repo.find_tag(tag_id).unwrap();
+        assert_eq!(tag.id(), tag_id);
+
+        let tags = repo.tag_names(None).unwrap();
+        assert_eq!(tags.len(), 1);
+        assert_eq!(tags.get(0), Some("foo"));
+
+        assert_eq!(tag.name(), Some("foo"));
+        assert_eq!(tag.message(), Some("msg"));
+        assert_eq!(tag.peel().unwrap().id(), obj.id());
+        assert_eq!(tag.target_id(), obj.id());
+        assert_eq!(tag.target_type(), Some(crate::ObjectType::Commit));
+
+        assert_eq!(tag.tagger().unwrap().name(), sig.name());
+        tag.target().unwrap();
+        tag.into_object();
+
+        repo.find_object(tag_id, None).unwrap().as_tag().unwrap();
+        repo.find_object(tag_id, None)
+            .unwrap()
+            .into_tag()
+            .ok()
+            .unwrap();
+
+        repo.tag_delete("foo").unwrap();
+    }
+
+    #[test]
+    fn lite() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = t!(repo.head());
+        let id = head.target().unwrap();
+        let obj = t!(repo.find_object(id, None));
+        let tag_id = t!(repo.tag_lightweight("foo", &obj, false));
+        assert!(repo.find_tag(tag_id).is_err());
+        assert_eq!(t!(repo.refname_to_id("refs/tags/foo")), id);
+
+        let tags = t!(repo.tag_names(Some("f*")));
+        assert_eq!(tags.len(), 1);
+        let tags = t!(repo.tag_names(Some("b*")));
+        assert_eq!(tags.len(), 0);
+    }
+}
diff --git a/git2/src/tagforeach.rs b/git2/src/tagforeach.rs
new file mode 100644 (file)
index 0000000..425eea5
--- /dev/null
@@ -0,0 +1,69 @@
+//! git_tag_foreach support
+//! see original: <https://libgit2.org/libgit2/#HEAD/group/tag/git_tag_foreach>
+
+use crate::{panic, raw, util::Binding, Oid};
+use libc::{c_char, c_int};
+use raw::git_oid;
+use std::ffi::{c_void, CStr};
+
+/// boxed callback type
+pub(crate) type TagForeachCB<'a> = Box<dyn FnMut(Oid, &[u8]) -> bool + 'a>;
+
+/// helper type to be able to pass callback to payload
+pub(crate) struct TagForeachData<'a> {
+    /// callback
+    pub(crate) cb: TagForeachCB<'a>,
+}
+
+/// c callback forwarding to rust callback inside `TagForeachData`
+/// see original: <https://libgit2.org/libgit2/#HEAD/group/callback/git_tag_foreach_cb>
+pub(crate) extern "C" fn tag_foreach_cb(
+    name: *const c_char,
+    oid: *mut git_oid,
+    payload: *mut c_void,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let id: Oid = Binding::from_raw(oid as *const _);
+
+        let name = CStr::from_ptr(name);
+        let name = name.to_bytes();
+
+        let payload = &mut *(payload as *mut TagForeachData<'_>);
+        let cb = &mut payload.cb;
+
+        let res = cb(id, name);
+
+        if res {
+            0
+        } else {
+            -1
+        }
+    })
+    .unwrap_or(-1)
+}
+
+#[cfg(test)]
+mod tests {
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = repo.head().unwrap();
+        let id = head.target().unwrap();
+        assert!(repo.find_tag(id).is_err());
+
+        let obj = repo.find_object(id, None).unwrap();
+        let sig = repo.signature().unwrap();
+        let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap();
+
+        let mut tags = Vec::new();
+        repo.tag_foreach(|id, name| {
+            tags.push((id, String::from_utf8(name.into()).unwrap()));
+            true
+        })
+        .unwrap();
+
+        assert_eq!(tags[0].0, tag_id);
+        assert_eq!(tags[0].1, "refs/tags/foo");
+    }
+}
diff --git a/git2/src/test.rs b/git2/src/test.rs
new file mode 100644 (file)
index 0000000..57a590f
--- /dev/null
@@ -0,0 +1,89 @@
+use std::fs::File;
+use std::io;
+use std::path::{Path, PathBuf};
+#[cfg(unix)]
+use std::ptr;
+use tempfile::TempDir;
+use url::Url;
+
+use crate::{Branch, Oid, Repository, RepositoryInitOptions};
+
+macro_rules! t {
+    ($e:expr) => {
+        match $e {
+            Ok(e) => e,
+            Err(e) => panic!("{} failed with {}", stringify!($e), e),
+        }
+    };
+}
+
+pub fn repo_init() -> (TempDir, Repository) {
+    let td = TempDir::new().unwrap();
+    let mut opts = RepositoryInitOptions::new();
+    opts.initial_head("main");
+    let repo = Repository::init_opts(td.path(), &opts).unwrap();
+    {
+        let mut config = repo.config().unwrap();
+        config.set_str("user.name", "name").unwrap();
+        config.set_str("user.email", "email").unwrap();
+        let mut index = repo.index().unwrap();
+        let id = index.write_tree().unwrap();
+
+        let tree = repo.find_tree(id).unwrap();
+        let sig = repo.signature().unwrap();
+        repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[])
+            .unwrap();
+    }
+    (td, repo)
+}
+
+pub fn commit(repo: &Repository) -> (Oid, Oid) {
+    let mut index = t!(repo.index());
+    let root = repo.path().parent().unwrap();
+    t!(File::create(&root.join("foo")));
+    t!(index.add_path(Path::new("foo")));
+
+    let tree_id = t!(index.write_tree());
+    let tree = t!(repo.find_tree(tree_id));
+    let sig = t!(repo.signature());
+    let head_id = t!(repo.refname_to_id("HEAD"));
+    let parent = t!(repo.find_commit(head_id));
+    let commit = t!(repo.commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]));
+    (commit, tree_id)
+}
+
+pub fn path2url(path: &Path) -> String {
+    Url::from_file_path(path).unwrap().to_string()
+}
+
+pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) {
+    let oid = repo.head().unwrap().target().unwrap();
+    let commit = repo.find_commit(oid).unwrap();
+    let branch = repo.branch("wt-branch", &commit, true).unwrap();
+    let wtdir = TempDir::new().unwrap();
+    (wtdir, branch)
+}
+
+#[cfg(windows)]
+pub fn realpath(original: &Path) -> io::Result<PathBuf> {
+    Ok(original.canonicalize()?.to_path_buf())
+}
+#[cfg(unix)]
+pub fn realpath(original: &Path) -> io::Result<PathBuf> {
+    use libc::c_char;
+    use std::ffi::{CStr, CString, OsString};
+    use std::os::unix::prelude::*;
+    extern "C" {
+        fn realpath(name: *const c_char, resolved: *mut c_char) -> *mut c_char;
+    }
+    unsafe {
+        let cstr = CString::new(original.as_os_str().as_bytes())?;
+        let ptr = realpath(cstr.as_ptr(), ptr::null_mut());
+        if ptr.is_null() {
+            return Err(io::Error::last_os_error());
+        }
+        let bytes = CStr::from_ptr(ptr).to_bytes().to_vec();
+        libc::free(ptr as *mut _);
+        Ok(PathBuf::from(OsString::from_vec(bytes)))
+    }
+}
diff --git a/git2/src/time.rs b/git2/src/time.rs
new file mode 100644 (file)
index 0000000..46b5bd3
--- /dev/null
@@ -0,0 +1,127 @@
+use std::cmp::Ordering;
+
+use libc::{c_char, c_int};
+
+use crate::raw;
+use crate::util::Binding;
+
+/// Time in a signature
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct Time {
+    raw: raw::git_time,
+}
+
+/// Time structure used in a git index entry.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct IndexTime {
+    raw: raw::git_index_time,
+}
+
+impl Time {
+    /// Creates a new time structure from its components.
+    pub fn new(time: i64, offset: i32) -> Time {
+        unsafe {
+            Binding::from_raw(raw::git_time {
+                time: time as raw::git_time_t,
+                offset: offset as c_int,
+                sign: if offset < 0 { '-' } else { '+' } as c_char,
+            })
+        }
+    }
+
+    /// Return the time, in seconds, from epoch
+    pub fn seconds(&self) -> i64 {
+        self.raw.time as i64
+    }
+
+    /// Return the timezone offset, in minutes
+    pub fn offset_minutes(&self) -> i32 {
+        self.raw.offset as i32
+    }
+
+    /// Return whether the offset was positive or negative. Primarily useful
+    /// in case the offset is specified as a negative zero.
+    pub fn sign(&self) -> char {
+        self.raw.sign as u8 as char
+    }
+}
+
+impl PartialOrd for Time {
+    fn partial_cmp(&self, other: &Time) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for Time {
+    fn cmp(&self, other: &Time) -> Ordering {
+        (self.raw.time, self.raw.offset).cmp(&(other.raw.time, other.raw.offset))
+    }
+}
+
+impl Binding for Time {
+    type Raw = raw::git_time;
+    unsafe fn from_raw(raw: raw::git_time) -> Time {
+        Time { raw }
+    }
+    fn raw(&self) -> raw::git_time {
+        self.raw
+    }
+}
+
+impl IndexTime {
+    /// Creates a new time structure from its components.
+    pub fn new(seconds: i32, nanoseconds: u32) -> IndexTime {
+        unsafe {
+            Binding::from_raw(raw::git_index_time {
+                seconds,
+                nanoseconds,
+            })
+        }
+    }
+
+    /// Returns the number of seconds in the second component of this time.
+    pub fn seconds(&self) -> i32 {
+        self.raw.seconds
+    }
+    /// Returns the nanosecond component of this time.
+    pub fn nanoseconds(&self) -> u32 {
+        self.raw.nanoseconds
+    }
+}
+
+impl Binding for IndexTime {
+    type Raw = raw::git_index_time;
+    unsafe fn from_raw(raw: raw::git_index_time) -> IndexTime {
+        IndexTime { raw }
+    }
+    fn raw(&self) -> raw::git_index_time {
+        self.raw
+    }
+}
+
+impl PartialOrd for IndexTime {
+    fn partial_cmp(&self, other: &IndexTime) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for IndexTime {
+    fn cmp(&self, other: &IndexTime) -> Ordering {
+        let me = (self.raw.seconds, self.raw.nanoseconds);
+        let other = (other.raw.seconds, other.raw.nanoseconds);
+        me.cmp(&other)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::Time;
+
+    #[test]
+    fn smoke() {
+        assert_eq!(Time::new(1608839587, -300).seconds(), 1608839587);
+        assert_eq!(Time::new(1608839587, -300).offset_minutes(), -300);
+        assert_eq!(Time::new(1608839587, -300).sign(), '-');
+        assert_eq!(Time::new(1608839587, 300).sign(), '+');
+    }
+}
diff --git a/git2/src/tracing.rs b/git2/src/tracing.rs
new file mode 100644 (file)
index 0000000..9872571
--- /dev/null
@@ -0,0 +1,140 @@
+use std::{
+    ffi::CStr,
+    sync::atomic::{AtomicPtr, Ordering},
+};
+
+use libc::{c_char, c_int};
+
+use crate::{panic, raw, util::Binding, Error};
+
+/// Available tracing levels.  When tracing is set to a particular level,
+/// callers will be provided tracing at the given level and all lower levels.
+#[derive(Copy, Clone, Debug)]
+pub enum TraceLevel {
+    /// No tracing will be performed.
+    None,
+
+    /// Severe errors that may impact the program's execution
+    Fatal,
+
+    /// Errors that do not impact the program's execution
+    Error,
+
+    /// Warnings that suggest abnormal data
+    Warn,
+
+    /// Informational messages about program execution
+    Info,
+
+    /// Detailed data that allows for debugging
+    Debug,
+
+    /// Exceptionally detailed debugging data
+    Trace,
+}
+
+impl Binding for TraceLevel {
+    type Raw = raw::git_trace_level_t;
+    unsafe fn from_raw(raw: raw::git_trace_level_t) -> Self {
+        match raw {
+            raw::GIT_TRACE_NONE => Self::None,
+            raw::GIT_TRACE_FATAL => Self::Fatal,
+            raw::GIT_TRACE_ERROR => Self::Error,
+            raw::GIT_TRACE_WARN => Self::Warn,
+            raw::GIT_TRACE_INFO => Self::Info,
+            raw::GIT_TRACE_DEBUG => Self::Debug,
+            raw::GIT_TRACE_TRACE => Self::Trace,
+            _ => panic!("Unknown git trace level"),
+        }
+    }
+    fn raw(&self) -> raw::git_trace_level_t {
+        match *self {
+            Self::None => raw::GIT_TRACE_NONE,
+            Self::Fatal => raw::GIT_TRACE_FATAL,
+            Self::Error => raw::GIT_TRACE_ERROR,
+            Self::Warn => raw::GIT_TRACE_WARN,
+            Self::Info => raw::GIT_TRACE_INFO,
+            Self::Debug => raw::GIT_TRACE_DEBUG,
+            Self::Trace => raw::GIT_TRACE_TRACE,
+        }
+    }
+}
+
+/// Callback type used to pass tracing events to the subscriber.
+/// see `trace_set` to register a subscriber.
+pub type TracingCb = fn(TraceLevel, &[u8]);
+
+/// Use an atomic pointer to store the global tracing subscriber function.
+static CALLBACK: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut());
+
+/// Set the global subscriber called when libgit2 produces a tracing message.
+pub fn trace_set(level: TraceLevel, cb: TracingCb) -> Result<(), Error> {
+    // Store the callback in the global atomic.
+    CALLBACK.store(cb as *mut (), Ordering::SeqCst);
+
+    // git_trace_set returns 0 if there was no error.
+    let return_code: c_int = unsafe { raw::git_trace_set(level.raw(), Some(tracing_cb_c)) };
+
+    if return_code != 0 {
+        Err(Error::last_error(return_code))
+    } else {
+        Ok(())
+    }
+}
+
+/// The tracing callback we pass to libgit2 (C ABI compatible).
+extern "C" fn tracing_cb_c(level: raw::git_trace_level_t, msg: *const c_char) {
+    // Load the callback function pointer from the global atomic.
+    let cb: *mut () = CALLBACK.load(Ordering::SeqCst);
+
+    // Transmute the callback pointer into the function pointer we know it to be.
+    //
+    // SAFETY: We only ever set the callback pointer with something cast from a TracingCb
+    // so transmuting back to a TracingCb is safe. This is notably not an integer-to-pointer
+    // transmute as described in the mem::transmute documentation and is in-line with the
+    // example in that documentation for casing between *const () to fn pointers.
+    let cb: TracingCb = unsafe { std::mem::transmute(cb) };
+
+    // If libgit2 passes us a message that is null, drop it and do not pass it to the callback.
+    // This is to avoid ever exposing rust code to a null ref, which would be Undefined Behavior.
+    if msg.is_null() {
+        return;
+    }
+
+    // Convert the message from a *const c_char to a &[u8] and pass it to the callback.
+    //
+    // SAFETY: We've just checked that the pointer is not null. The other safety requirements are left to
+    // libgit2 to enforce -- namely that it gives us a valid, nul-terminated, C string, that that string exists
+    // entirely in one allocation, that the string will not be mutated once passed to us, and that the nul-terminator is
+    // within isize::MAX bytes from the given pointers data address.
+    let msg: &CStr = unsafe { CStr::from_ptr(msg) };
+
+    // Convert from a CStr to &[u8] to pass to the rust code callback.
+    let msg: &[u8] = CStr::to_bytes(msg);
+
+    // Do the remaining part of this function in a panic wrapper, to catch any panics it produces.
+    panic::wrap(|| {
+        // Convert the raw trace level into a type we can pass to the rust callback fn.
+        //
+        // SAFETY: Currently the implementation of this function (above) may panic, but is only marked as unsafe to match
+        // the trait definition, thus we can consider this call safe.
+        let level: TraceLevel = unsafe { Binding::from_raw(level) };
+
+        // Call the user-supplied callback (which may panic).
+        (cb)(level, msg);
+    });
+}
+
+#[cfg(test)]
+mod tests {
+    use super::TraceLevel;
+
+    // Test that using the above function to set a tracing callback doesn't panic.
+    #[test]
+    fn smoke() {
+        super::trace_set(TraceLevel::Trace, |level, msg| {
+            dbg!(level, msg);
+        })
+        .expect("libgit2 can set global trace callback");
+    }
+}
diff --git a/git2/src/transaction.rs b/git2/src/transaction.rs
new file mode 100644 (file)
index 0000000..4f661f1
--- /dev/null
@@ -0,0 +1,285 @@
+use std::ffi::CString;
+use std::marker;
+
+use crate::{raw, util::Binding, Error, Oid, Reflog, Repository, Signature};
+
+/// A structure representing a transactional update of a repository's references.
+///
+/// Transactions work by locking loose refs for as long as the [`Transaction`]
+/// is held, and committing all changes to disk when [`Transaction::commit`] is
+/// called. Note that committing is not atomic: if an operation fails, the
+/// transaction aborts, but previous successful operations are not rolled back.
+pub struct Transaction<'repo> {
+    raw: *mut raw::git_transaction,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl Drop for Transaction<'_> {
+    fn drop(&mut self) {
+        unsafe { raw::git_transaction_free(self.raw) }
+    }
+}
+
+impl<'repo> Binding for Transaction<'repo> {
+    type Raw = *mut raw::git_transaction;
+
+    unsafe fn from_raw(ptr: *mut raw::git_transaction) -> Transaction<'repo> {
+        Transaction {
+            raw: ptr,
+            _marker: marker::PhantomData,
+        }
+    }
+
+    fn raw(&self) -> *mut raw::git_transaction {
+        self.raw
+    }
+}
+
+impl<'repo> Transaction<'repo> {
+    /// Lock the specified reference by name.
+    pub fn lock_ref(&mut self, refname: &str) -> Result<(), Error> {
+        let refname = CString::new(refname).unwrap();
+        unsafe {
+            try_call!(raw::git_transaction_lock_ref(self.raw, refname));
+        }
+
+        Ok(())
+    }
+
+    /// Set the target of the specified reference.
+    ///
+    /// The reference must have been locked via `lock_ref`.
+    ///
+    /// If `reflog_signature` is `None`, the [`Signature`] is read from the
+    /// repository config.
+    pub fn set_target(
+        &mut self,
+        refname: &str,
+        target: Oid,
+        reflog_signature: Option<&Signature<'_>>,
+        reflog_message: &str,
+    ) -> Result<(), Error> {
+        let refname = CString::new(refname).unwrap();
+        let reflog_message = CString::new(reflog_message).unwrap();
+        unsafe {
+            try_call!(raw::git_transaction_set_target(
+                self.raw,
+                refname,
+                target.raw(),
+                reflog_signature.map(|s| s.raw()),
+                reflog_message
+            ));
+        }
+
+        Ok(())
+    }
+
+    /// Set the target of the specified symbolic reference.
+    ///
+    /// The reference must have been locked via `lock_ref`.
+    ///
+    /// If `reflog_signature` is `None`, the [`Signature`] is read from the
+    /// repository config.
+    pub fn set_symbolic_target(
+        &mut self,
+        refname: &str,
+        target: &str,
+        reflog_signature: Option<&Signature<'_>>,
+        reflog_message: &str,
+    ) -> Result<(), Error> {
+        let refname = CString::new(refname).unwrap();
+        let target = CString::new(target).unwrap();
+        let reflog_message = CString::new(reflog_message).unwrap();
+        unsafe {
+            try_call!(raw::git_transaction_set_symbolic_target(
+                self.raw,
+                refname,
+                target,
+                reflog_signature.map(|s| s.raw()),
+                reflog_message
+            ));
+        }
+
+        Ok(())
+    }
+
+    /// Add a [`Reflog`] to the transaction.
+    ///
+    /// This commit the in-memory [`Reflog`] to disk when the transaction commits.
+    /// Note that atomicity is **not* guaranteed: if the transaction fails to
+    /// modify `refname`, the reflog may still have been committed to disk.
+    ///
+    /// If this is combined with setting the target, that update won't be
+    /// written to the log (i.e. the `reflog_signature` and `reflog_message`
+    /// parameters will be ignored).
+    pub fn set_reflog(&mut self, refname: &str, reflog: Reflog) -> Result<(), Error> {
+        let refname = CString::new(refname).unwrap();
+        unsafe {
+            try_call!(raw::git_transaction_set_reflog(
+                self.raw,
+                refname,
+                reflog.raw()
+            ));
+        }
+
+        Ok(())
+    }
+
+    /// Remove a reference.
+    ///
+    /// The reference must have been locked via `lock_ref`.
+    pub fn remove(&mut self, refname: &str) -> Result<(), Error> {
+        let refname = CString::new(refname).unwrap();
+        unsafe {
+            try_call!(raw::git_transaction_remove(self.raw, refname));
+        }
+
+        Ok(())
+    }
+
+    /// Commit the changes from the transaction.
+    ///
+    /// The updates will be made one by one, and the first failure will stop the
+    /// processing.
+    pub fn commit(self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_transaction_commit(self.raw));
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::{Error, ErrorClass, ErrorCode, Oid, Repository};
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut tx = t!(repo.transaction());
+
+        t!(tx.lock_ref("refs/heads/main"));
+        t!(tx.lock_ref("refs/heads/next"));
+
+        t!(tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero"));
+        t!(tx.set_symbolic_target(
+            "refs/heads/next",
+            "refs/heads/main",
+            None,
+            "set next to main",
+        ));
+
+        t!(tx.commit());
+
+        assert_eq!(repo.refname_to_id("refs/heads/main").unwrap(), Oid::zero());
+        assert_eq!(
+            repo.find_reference("refs/heads/next")
+                .unwrap()
+                .symbolic_target()
+                .unwrap(),
+            "refs/heads/main"
+        );
+    }
+
+    #[test]
+    fn locks_same_repo_handle() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut tx1 = t!(repo.transaction());
+        t!(tx1.lock_ref("refs/heads/seen"));
+
+        let mut tx2 = t!(repo.transaction());
+        assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked))
+    }
+
+    #[test]
+    fn locks_across_repo_handles() {
+        let (td, repo1) = crate::test::repo_init();
+        let repo2 = t!(Repository::open(&td));
+
+        let mut tx1 = t!(repo1.transaction());
+        t!(tx1.lock_ref("refs/heads/seen"));
+
+        let mut tx2 = t!(repo2.transaction());
+        assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked))
+    }
+
+    #[test]
+    fn drop_unlocks() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut tx = t!(repo.transaction());
+        t!(tx.lock_ref("refs/heads/seen"));
+        drop(tx);
+
+        let mut tx2 = t!(repo.transaction());
+        t!(tx2.lock_ref("refs/heads/seen"))
+    }
+
+    #[test]
+    fn commit_unlocks() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut tx = t!(repo.transaction());
+        t!(tx.lock_ref("refs/heads/seen"));
+        t!(tx.commit());
+
+        let mut tx2 = t!(repo.transaction());
+        t!(tx2.lock_ref("refs/heads/seen"));
+    }
+
+    #[test]
+    fn prevents_non_transactional_updates() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = t!(repo.refname_to_id("HEAD"));
+
+        let mut tx = t!(repo.transaction());
+        t!(tx.lock_ref("refs/heads/seen"));
+
+        assert!(matches!(
+            repo.reference("refs/heads/seen", head, true, "competing with lock"),
+            Err(e) if e.code() == ErrorCode::Locked
+        ));
+    }
+
+    #[test]
+    fn remove() {
+        let (_td, repo) = crate::test::repo_init();
+        let head = t!(repo.refname_to_id("HEAD"));
+        let next = "refs/heads/next";
+
+        t!(repo.reference(
+            next,
+            head,
+            true,
+            "refs/heads/next@{0}: branch: Created from HEAD"
+        ));
+
+        {
+            let mut tx = t!(repo.transaction());
+            t!(tx.lock_ref(next));
+            t!(tx.remove(next));
+            t!(tx.commit());
+        }
+        assert!(matches!(repo.refname_to_id(next), Err(e) if e.code() == ErrorCode::NotFound))
+    }
+
+    #[test]
+    fn must_lock_ref() {
+        let (_td, repo) = crate::test::repo_init();
+
+        // 🤷
+        fn is_not_locked_err(e: &Error) -> bool {
+            e.code() == ErrorCode::NotFound
+                && e.class() == ErrorClass::Reference
+                && e.message() == "the specified reference is not locked"
+        }
+
+        let mut tx = t!(repo.transaction());
+        assert!(matches!(
+            tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero"),
+            Err(e) if is_not_locked_err(&e)
+        ))
+    }
+}
diff --git a/git2/src/transport.rs b/git2/src/transport.rs
new file mode 100644 (file)
index 0000000..b1ca3f8
--- /dev/null
@@ -0,0 +1,421 @@
+//! Interfaces for adding custom transports to libgit2
+
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::ffi::{CStr, CString};
+use std::io;
+use std::io::prelude::*;
+use std::mem;
+use std::ptr;
+use std::slice;
+use std::str;
+
+use crate::util::Binding;
+use crate::{panic, raw, Error, Remote};
+
+/// A transport is a structure which knows how to transfer data to and from a
+/// remote.
+///
+/// This transport is a representation of the raw transport underneath it, which
+/// is similar to a trait object in Rust.
+#[allow(missing_copy_implementations)]
+pub struct Transport {
+    raw: *mut raw::git_transport,
+    owned: bool,
+}
+
+/// Interface used by smart transports.
+///
+/// The full-fledged definition of transports has to deal with lots of
+/// nitty-gritty details of the git protocol, but "smart transports" largely
+/// only need to deal with read() and write() of data over a channel.
+///
+/// A smart subtransport is contained within an instance of a smart transport
+/// and is delegated to in order to actually conduct network activity to push or
+/// pull data from a remote.
+pub trait SmartSubtransport: Send + 'static {
+    /// Indicates that this subtransport will be performing the specified action
+    /// on the specified URL.
+    ///
+    /// This function is responsible for making any network connections and
+    /// returns a stream which can be read and written from in order to
+    /// negotiate the git protocol.
+    fn action(&self, url: &str, action: Service)
+        -> Result<Box<dyn SmartSubtransportStream>, Error>;
+
+    /// Terminates a connection with the remote.
+    ///
+    /// Each subtransport is guaranteed a call to close() between calls to
+    /// action(), except for the following two natural progressions of actions
+    /// against a constant URL.
+    ///
+    /// 1. UploadPackLs -> UploadPack
+    /// 2. ReceivePackLs -> ReceivePack
+    fn close(&self) -> Result<(), Error>;
+}
+
+/// Actions that a smart transport can ask a subtransport to perform
+#[derive(Copy, Clone, PartialEq, Debug)]
+#[allow(missing_docs)]
+pub enum Service {
+    UploadPackLs,
+    UploadPack,
+    ReceivePackLs,
+    ReceivePack,
+}
+
+/// An instance of a stream over which a smart transport will communicate with a
+/// remote.
+///
+/// Currently this only requires the standard `Read` and `Write` traits. This
+/// trait also does not need to be implemented manually as long as the `Read`
+/// and `Write` traits are implemented.
+pub trait SmartSubtransportStream: Read + Write + Send + 'static {}
+
+impl<T: Read + Write + Send + 'static> SmartSubtransportStream for T {}
+
+type TransportFactory = dyn Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static;
+
+/// Boxed data payload used for registering new transports.
+///
+/// Currently only contains a field which knows how to create transports.
+struct TransportData {
+    factory: Box<TransportFactory>,
+}
+
+/// Instance of a `git_smart_subtransport`, must use `#[repr(C)]` to ensure that
+/// the C fields come first.
+#[repr(C)]
+struct RawSmartSubtransport {
+    raw: raw::git_smart_subtransport,
+    stream: Option<*mut raw::git_smart_subtransport_stream>,
+    rpc: bool,
+    obj: Box<dyn SmartSubtransport>,
+}
+
+/// Instance of a `git_smart_subtransport_stream`, must use `#[repr(C)]` to
+/// ensure that the C fields come first.
+#[repr(C)]
+struct RawSmartSubtransportStream {
+    raw: raw::git_smart_subtransport_stream,
+    obj: Box<dyn SmartSubtransportStream>,
+}
+
+/// Add a custom transport definition, to be used in addition to the built-in
+/// set of transports that come with libgit2.
+///
+/// This function is unsafe as it needs to be externally synchronized with calls
+/// to creation of other transports.
+pub unsafe fn register<F>(prefix: &str, factory: F) -> Result<(), Error>
+where
+    F: Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static,
+{
+    crate::init();
+    let mut data = Box::new(TransportData {
+        factory: Box::new(factory),
+    });
+    let prefix = CString::new(prefix)?;
+    let datap = (&mut *data) as *mut TransportData as *mut c_void;
+    let factory: raw::git_transport_cb = Some(transport_factory);
+    try_call!(raw::git_transport_register(prefix, factory, datap));
+    mem::forget(data);
+    Ok(())
+}
+
+impl Transport {
+    /// Creates a new transport which will use the "smart" transport protocol
+    /// for transferring data.
+    ///
+    /// A smart transport requires a *subtransport* over which data is actually
+    /// communicated, but this subtransport largely just needs to be able to
+    /// read() and write(). The subtransport provided will be used to make
+    /// connections which can then be read/written from.
+    ///
+    /// The `rpc` argument is `true` if the protocol is stateless, false
+    /// otherwise. For example `http://` is stateless but `git://` is not.
+    pub fn smart<S>(remote: &Remote<'_>, rpc: bool, subtransport: S) -> Result<Transport, Error>
+    where
+        S: SmartSubtransport,
+    {
+        let mut ret = ptr::null_mut();
+
+        let mut raw = Box::new(RawSmartSubtransport {
+            raw: raw::git_smart_subtransport {
+                action: Some(subtransport_action),
+                close: Some(subtransport_close),
+                free: Some(subtransport_free),
+            },
+            stream: None,
+            rpc,
+            obj: Box::new(subtransport),
+        });
+        let mut defn = raw::git_smart_subtransport_definition {
+            callback: Some(smart_factory),
+            rpc: rpc as c_uint,
+            param: &mut *raw as *mut _ as *mut _,
+        };
+
+        // Currently there's no way to pass a payload via the
+        // git_smart_subtransport_definition structure, but it's only used as a
+        // configuration for the initial creation of the smart transport (verified
+        // by reading the current code, hopefully it doesn't change!).
+        //
+        // We, however, need some state (gotta pass in our
+        // `RawSmartSubtransport`). This also means that this block must be
+        // entirely synchronized with a lock (boo!)
+        unsafe {
+            try_call!(raw::git_transport_smart(
+                &mut ret,
+                remote.raw(),
+                &mut defn as *mut _ as *mut _
+            ));
+            mem::forget(raw); // ownership transport to `ret`
+        }
+        return Ok(Transport {
+            raw: ret,
+            owned: true,
+        });
+
+        extern "C" fn smart_factory(
+            out: *mut *mut raw::git_smart_subtransport,
+            _owner: *mut raw::git_transport,
+            ptr: *mut c_void,
+        ) -> c_int {
+            unsafe {
+                *out = ptr as *mut raw::git_smart_subtransport;
+                0
+            }
+        }
+    }
+}
+
+impl Drop for Transport {
+    fn drop(&mut self) {
+        if self.owned {
+            unsafe { (*self.raw).free.unwrap()(self.raw) }
+        }
+    }
+}
+
+// callback used by register() to create new transports
+extern "C" fn transport_factory(
+    out: *mut *mut raw::git_transport,
+    owner: *mut raw::git_remote,
+    param: *mut c_void,
+) -> c_int {
+    struct Bomb<'a> {
+        remote: Option<Remote<'a>>,
+    }
+    impl<'a> Drop for Bomb<'a> {
+        fn drop(&mut self) {
+            // TODO: maybe a method instead?
+            mem::forget(self.remote.take());
+        }
+    }
+
+    panic::wrap(|| unsafe {
+        let remote = Bomb {
+            remote: Some(Binding::from_raw(owner)),
+        };
+        let data = &mut *(param as *mut TransportData);
+        match (data.factory)(remote.remote.as_ref().unwrap()) {
+            Ok(mut transport) => {
+                *out = transport.raw;
+                transport.owned = false;
+                0
+            }
+            Err(e) => e.raw_code() as c_int,
+        }
+    })
+    .unwrap_or(-1)
+}
+
+// callback used by smart transports to delegate an action to a
+// `SmartSubtransport` trait object.
+extern "C" fn subtransport_action(
+    stream: *mut *mut raw::git_smart_subtransport_stream,
+    raw_transport: *mut raw::git_smart_subtransport,
+    url: *const c_char,
+    action: raw::git_smart_service_t,
+) -> c_int {
+    panic::wrap(|| unsafe {
+        let url = CStr::from_ptr(url).to_bytes();
+        let url = match str::from_utf8(url).ok() {
+            Some(s) => s,
+            None => return -1,
+        };
+        let action = match action {
+            raw::GIT_SERVICE_UPLOADPACK_LS => Service::UploadPackLs,
+            raw::GIT_SERVICE_UPLOADPACK => Service::UploadPack,
+            raw::GIT_SERVICE_RECEIVEPACK_LS => Service::ReceivePackLs,
+            raw::GIT_SERVICE_RECEIVEPACK => Service::ReceivePack,
+            n => panic!("unknown action: {}", n),
+        };
+
+        let transport = &mut *(raw_transport as *mut RawSmartSubtransport);
+        // Note: we only need to generate if rpc is on. Else, for receive-pack and upload-pack
+        // libgit2 reuses the stream generated for receive-pack-ls or upload-pack-ls.
+        let generate_stream =
+            transport.rpc || action == Service::UploadPackLs || action == Service::ReceivePackLs;
+        if generate_stream {
+            let obj = match transport.obj.action(url, action) {
+                Ok(s) => s,
+                Err(e) => return e.raw_set_git_error(),
+            };
+            *stream = mem::transmute(Box::new(RawSmartSubtransportStream {
+                raw: raw::git_smart_subtransport_stream {
+                    subtransport: raw_transport,
+                    read: Some(stream_read),
+                    write: Some(stream_write),
+                    free: Some(stream_free),
+                },
+                obj,
+            }));
+            transport.stream = Some(*stream);
+        } else {
+            if transport.stream.is_none() {
+                return -1;
+            }
+            *stream = transport.stream.unwrap();
+        }
+        0
+    })
+    .unwrap_or(-1)
+}
+
+// callback used by smart transports to close a `SmartSubtransport` trait
+// object.
+extern "C" fn subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int {
+    let ret = panic::wrap(|| unsafe {
+        let transport = &mut *(transport as *mut RawSmartSubtransport);
+        transport.obj.close()
+    });
+    match ret {
+        Some(Ok(())) => 0,
+        Some(Err(e)) => e.raw_code() as c_int,
+        None => -1,
+    }
+}
+
+// callback used by smart transports to free a `SmartSubtransport` trait
+// object.
+extern "C" fn subtransport_free(transport: *mut raw::git_smart_subtransport) {
+    let _ = panic::wrap(|| unsafe {
+        mem::transmute::<_, Box<RawSmartSubtransport>>(transport);
+    });
+}
+
+// callback used by smart transports to read from a `SmartSubtransportStream`
+// object.
+extern "C" fn stream_read(
+    stream: *mut raw::git_smart_subtransport_stream,
+    buffer: *mut c_char,
+    buf_size: size_t,
+    bytes_read: *mut size_t,
+) -> c_int {
+    let ret = panic::wrap(|| unsafe {
+        let transport = &mut *(stream as *mut RawSmartSubtransportStream);
+        let buf = slice::from_raw_parts_mut(buffer as *mut u8, buf_size as usize);
+        match transport.obj.read(buf) {
+            Ok(n) => {
+                *bytes_read = n as size_t;
+                Ok(n)
+            }
+            e => e,
+        }
+    });
+    match ret {
+        Some(Ok(_)) => 0,
+        Some(Err(e)) => unsafe {
+            set_err_io(&e);
+            -2
+        },
+        None => -1,
+    }
+}
+
+// callback used by smart transports to write to a `SmartSubtransportStream`
+// object.
+extern "C" fn stream_write(
+    stream: *mut raw::git_smart_subtransport_stream,
+    buffer: *const c_char,
+    len: size_t,
+) -> c_int {
+    let ret = panic::wrap(|| unsafe {
+        let transport = &mut *(stream as *mut RawSmartSubtransportStream);
+        let buf = slice::from_raw_parts(buffer as *const u8, len as usize);
+        transport.obj.write_all(buf)
+    });
+    match ret {
+        Some(Ok(())) => 0,
+        Some(Err(e)) => unsafe {
+            set_err_io(&e);
+            -2
+        },
+        None => -1,
+    }
+}
+
+unsafe fn set_err_io(e: &io::Error) {
+    let s = CString::new(e.to_string()).unwrap();
+    raw::git_error_set_str(raw::GIT_ERROR_NET as c_int, s.as_ptr());
+}
+
+// callback used by smart transports to free a `SmartSubtransportStream`
+// object.
+extern "C" fn stream_free(stream: *mut raw::git_smart_subtransport_stream) {
+    let _ = panic::wrap(|| unsafe {
+        mem::transmute::<_, Box<RawSmartSubtransportStream>>(stream);
+    });
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{ErrorClass, ErrorCode};
+    use std::sync::Once;
+
+    struct DummyTransport;
+
+    // in lieu of lazy_static
+    fn dummy_error() -> Error {
+        Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh")
+    }
+
+    impl SmartSubtransport for DummyTransport {
+        fn action(
+            &self,
+            _url: &str,
+            _service: Service,
+        ) -> Result<Box<dyn SmartSubtransportStream>, Error> {
+            Err(dummy_error())
+        }
+
+        fn close(&self) -> Result<(), Error> {
+            Ok(())
+        }
+    }
+
+    #[test]
+    fn transport_error_propagates() {
+        static INIT: Once = Once::new();
+
+        unsafe {
+            INIT.call_once(|| {
+                register("dummy", move |remote| {
+                    Transport::smart(&remote, true, DummyTransport)
+                })
+                .unwrap();
+            })
+        }
+
+        let (_td, repo) = crate::test::repo_init();
+        t!(repo.remote("origin", "dummy://ball"));
+
+        let mut origin = t!(repo.find_remote("origin"));
+
+        match origin.fetch(&["main"], None, None) {
+            Ok(()) => unreachable!(),
+            Err(e) => assert_eq!(e, dummy_error()),
+        }
+    }
+}
diff --git a/git2/src/tree.rs b/git2/src/tree.rs
new file mode 100644 (file)
index 0000000..e683257
--- /dev/null
@@ -0,0 +1,617 @@
+use libc::{c_char, c_int, c_void};
+use std::cmp::Ordering;
+use std::ffi::{CStr, CString};
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr;
+use std::str;
+
+use crate::util::{c_cmp_to_ordering, path_to_repo_path, Binding};
+use crate::{panic, raw, Error, Object, ObjectType, Oid, Repository};
+
+/// A structure to represent a git [tree][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Tree<'repo> {
+    raw: *mut raw::git_tree,
+    _marker: marker::PhantomData<Object<'repo>>,
+}
+
+/// A structure representing an entry inside of a tree. An entry is borrowed
+/// from a tree.
+pub struct TreeEntry<'tree> {
+    raw: *mut raw::git_tree_entry,
+    owned: bool,
+    _marker: marker::PhantomData<&'tree raw::git_tree_entry>,
+}
+
+/// An iterator over the entries in a tree.
+pub struct TreeIter<'tree> {
+    range: Range<usize>,
+    tree: &'tree Tree<'tree>,
+}
+
+/// A binary indicator of whether a tree walk should be performed in pre-order
+/// or post-order.
+#[derive(Clone, Copy)]
+pub enum TreeWalkMode {
+    /// Runs the traversal in pre-order.
+    PreOrder = 0,
+    /// Runs the traversal in post-order.
+    PostOrder = 1,
+}
+
+/// Possible return codes for tree walking callback functions.
+#[repr(i32)]
+pub enum TreeWalkResult {
+    /// Continue with the traversal as normal.
+    Ok = 0,
+    /// Skip the current node (in pre-order mode).
+    Skip = 1,
+    /// Completely stop the traversal.
+    Abort = raw::GIT_EUSER,
+}
+
+impl Into<i32> for TreeWalkResult {
+    fn into(self) -> i32 {
+        self as i32
+    }
+}
+
+impl Into<raw::git_treewalk_mode> for TreeWalkMode {
+    #[cfg(target_env = "msvc")]
+    fn into(self) -> raw::git_treewalk_mode {
+        self as i32
+    }
+    #[cfg(not(target_env = "msvc"))]
+    fn into(self) -> raw::git_treewalk_mode {
+        self as u32
+    }
+}
+
+impl<'repo> Tree<'repo> {
+    /// Get the id (SHA1) of a repository object
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_tree_id(&*self.raw)) }
+    }
+
+    /// Get the number of entries listed in this tree.
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_tree_entrycount(&*self.raw) as usize }
+    }
+
+    /// Return `true` if there is not entry
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Returns an iterator over the entries in this tree.
+    pub fn iter(&self) -> TreeIter<'_> {
+        TreeIter {
+            range: 0..self.len(),
+            tree: self,
+        }
+    }
+
+    /// Traverse the entries in a tree and its subtrees in post or pre-order.
+    /// The callback function will be run on each node of the tree that's
+    /// walked. The return code of this function will determine how the walk
+    /// continues.
+    ///
+    /// libgit2 requires that the callback be an integer, where 0 indicates a
+    /// successful visit, 1 skips the node, and -1 aborts the traversal completely.
+    /// You may opt to use the enum [`TreeWalkResult`] instead.
+    ///
+    /// ```ignore
+    /// let mut ct = 0;
+    /// tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+    ///     assert_eq!(entry.name(), Some("foo"));
+    ///     ct += 1;
+    ///     TreeWalkResult::Ok
+    /// }).unwrap();
+    /// assert_eq!(ct, 1);
+    /// ```
+    ///
+    /// See [libgit2 documentation][1] for more information.
+    ///
+    /// [1]: https://libgit2.org/libgit2/#HEAD/group/tree/git_tree_walk
+    pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error>
+    where
+        C: FnMut(&str, &TreeEntry<'_>) -> T,
+        T: Into<i32>,
+    {
+        unsafe {
+            let mut data = TreeWalkCbData {
+                callback: &mut callback,
+            };
+            try_call!(raw::git_tree_walk(
+                self.raw(),
+                mode as raw::git_treewalk_mode,
+                treewalk_cb::<T>,
+                &mut data as *mut _ as *mut c_void
+            ));
+            Ok(())
+        }
+    }
+
+    /// Lookup a tree entry by SHA value.
+    pub fn get_id(&self, id: Oid) -> Option<TreeEntry<'_>> {
+        unsafe {
+            let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw());
+            if ptr.is_null() {
+                None
+            } else {
+                Some(entry_from_raw_const(ptr))
+            }
+        }
+    }
+
+    /// Lookup a tree entry by its position in the tree
+    pub fn get(&self, n: usize) -> Option<TreeEntry<'_>> {
+        unsafe {
+            let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t);
+            if ptr.is_null() {
+                None
+            } else {
+                Some(entry_from_raw_const(ptr))
+            }
+        }
+    }
+
+    /// Lookup a tree entry by its filename
+    pub fn get_name(&self, filename: &str) -> Option<TreeEntry<'_>> {
+        self.get_name_bytes(filename.as_bytes())
+    }
+
+    /// Lookup a tree entry by its filename, specified as bytes.
+    ///
+    /// This allows for non-UTF-8 filenames.
+    pub fn get_name_bytes(&self, filename: &[u8]) -> Option<TreeEntry<'_>> {
+        let filename = CString::new(filename).unwrap();
+        unsafe {
+            let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename));
+            if ptr.is_null() {
+                None
+            } else {
+                Some(entry_from_raw_const(ptr))
+            }
+        }
+    }
+
+    /// Retrieve a tree entry contained in a tree or in any of its subtrees,
+    /// given its relative path.
+    pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> {
+        let path = path_to_repo_path(path)?;
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Casts this Tree to be usable as an `Object`
+    pub fn as_object(&self) -> &Object<'repo> {
+        unsafe { &*(self as *const _ as *const Object<'repo>) }
+    }
+
+    /// Consumes this Tree to be returned as an `Object`
+    pub fn into_object(self) -> Object<'repo> {
+        assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
+        unsafe { mem::transmute(self) }
+    }
+}
+
+type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a;
+
+struct TreeWalkCbData<'a, T> {
+    callback: &'a mut TreeWalkCb<'a, T>,
+}
+
+extern "C" fn treewalk_cb<T: Into<i32>>(
+    root: *const c_char,
+    entry: *const raw::git_tree_entry,
+    payload: *mut c_void,
+) -> c_int {
+    match panic::wrap(|| unsafe {
+        let root = match CStr::from_ptr(root).to_str() {
+            Ok(value) => value,
+            _ => return -1,
+        };
+        let entry = entry_from_raw_const(entry);
+        let payload = &mut *(payload as *mut TreeWalkCbData<'_, T>);
+        let callback = &mut payload.callback;
+        callback(root, &entry).into()
+    }) {
+        Some(value) => value,
+        None => -1,
+    }
+}
+
+impl<'repo> Binding for Tree<'repo> {
+    type Raw = *mut raw::git_tree;
+
+    unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> {
+        Tree {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_tree {
+        self.raw
+    }
+}
+
+impl<'repo> std::fmt::Debug for Tree<'repo> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+        f.debug_struct("Tree").field("id", &self.id()).finish()
+    }
+}
+
+impl<'repo> Clone for Tree<'repo> {
+    fn clone(&self) -> Self {
+        self.as_object().clone().into_tree().ok().unwrap()
+    }
+}
+
+impl<'repo> Drop for Tree<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_tree_free(self.raw) }
+    }
+}
+
+impl<'repo, 'iter> IntoIterator for &'iter Tree<'repo> {
+    type Item = TreeEntry<'iter>;
+    type IntoIter = TreeIter<'iter>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+/// Create a new tree entry from the raw pointer provided.
+///
+/// The lifetime of the entry is tied to the tree provided and the function
+/// is unsafe because the validity of the pointer cannot be guaranteed.
+pub unsafe fn entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree> {
+    TreeEntry {
+        raw: raw as *mut raw::git_tree_entry,
+        owned: false,
+        _marker: marker::PhantomData,
+    }
+}
+
+impl<'tree> TreeEntry<'tree> {
+    /// Get the id of the object pointed by the entry
+    pub fn id(&self) -> Oid {
+        unsafe { Binding::from_raw(raw::git_tree_entry_id(&*self.raw)) }
+    }
+
+    /// Get the filename of a tree entry
+    ///
+    /// Returns `None` if the name is not valid utf-8
+    pub fn name(&self) -> Option<&str> {
+        str::from_utf8(self.name_bytes()).ok()
+    }
+
+    /// Get the filename of a tree entry
+    pub fn name_bytes(&self) -> &[u8] {
+        unsafe { crate::opt_bytes(self, raw::git_tree_entry_name(&*self.raw())).unwrap() }
+    }
+
+    /// Convert a tree entry to the object it points to.
+    pub fn to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_tree_entry_to_object(
+                &mut ret,
+                repo.raw(),
+                &*self.raw()
+            ));
+            Ok(Binding::from_raw(ret))
+        }
+    }
+
+    /// Get the type of the object pointed by the entry
+    pub fn kind(&self) -> Option<ObjectType> {
+        ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) })
+    }
+
+    /// Get the UNIX file attributes of a tree entry
+    pub fn filemode(&self) -> i32 {
+        unsafe { raw::git_tree_entry_filemode(&*self.raw) as i32 }
+    }
+
+    /// Get the raw UNIX file attributes of a tree entry
+    pub fn filemode_raw(&self) -> i32 {
+        unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 }
+    }
+
+    /// Convert this entry of any lifetime into an owned signature with a static
+    /// lifetime.
+    ///
+    /// This will use the `Clone::clone` implementation under the hood.
+    pub fn to_owned(&self) -> TreeEntry<'static> {
+        unsafe {
+            let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self);
+            me.clone()
+        }
+    }
+}
+
+impl<'a> Binding for TreeEntry<'a> {
+    type Raw = *mut raw::git_tree_entry;
+    unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a> {
+        TreeEntry {
+            raw,
+            owned: true,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_tree_entry {
+        self.raw
+    }
+}
+
+impl<'a> Clone for TreeEntry<'a> {
+    fn clone(&self) -> TreeEntry<'a> {
+        let mut ret = ptr::null_mut();
+        unsafe {
+            assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0);
+            Binding::from_raw(ret)
+        }
+    }
+}
+
+impl<'a> PartialOrd for TreeEntry<'a> {
+    fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+impl<'a> Ord for TreeEntry<'a> {
+    fn cmp(&self, other: &TreeEntry<'a>) -> Ordering {
+        c_cmp_to_ordering(unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) })
+    }
+}
+
+impl<'a> PartialEq for TreeEntry<'a> {
+    fn eq(&self, other: &TreeEntry<'a>) -> bool {
+        self.cmp(other) == Ordering::Equal
+    }
+}
+impl<'a> Eq for TreeEntry<'a> {}
+
+impl<'a> Drop for TreeEntry<'a> {
+    fn drop(&mut self) {
+        if self.owned {
+            unsafe { raw::git_tree_entry_free(self.raw) }
+        }
+    }
+}
+
+impl<'tree> Iterator for TreeIter<'tree> {
+    type Item = TreeEntry<'tree>;
+    fn next(&mut self) -> Option<TreeEntry<'tree>> {
+        self.range.next().and_then(|i| self.tree.get(i))
+    }
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.range.size_hint()
+    }
+    fn nth(&mut self, n: usize) -> Option<TreeEntry<'tree>> {
+        self.range.nth(n).and_then(|i| self.tree.get(i))
+    }
+}
+impl<'tree> DoubleEndedIterator for TreeIter<'tree> {
+    fn next_back(&mut self) -> Option<TreeEntry<'tree>> {
+        self.range.next_back().and_then(|i| self.tree.get(i))
+    }
+}
+impl<'tree> FusedIterator for TreeIter<'tree> {}
+impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
+
+#[cfg(test)]
+mod tests {
+    use super::{TreeWalkMode, TreeWalkResult};
+    use crate::{Object, ObjectType, Repository, Tree, TreeEntry};
+    use std::fs::File;
+    use std::io::prelude::*;
+    use std::path::Path;
+    use tempfile::TempDir;
+
+    pub struct TestTreeIter<'a> {
+        entries: Vec<TreeEntry<'a>>,
+        repo: &'a Repository,
+    }
+
+    impl<'a> Iterator for TestTreeIter<'a> {
+        type Item = TreeEntry<'a>;
+
+        fn next(&mut self) -> Option<TreeEntry<'a>> {
+            if self.entries.is_empty() {
+                None
+            } else {
+                let entry = self.entries.remove(0);
+
+                match entry.kind() {
+                    Some(ObjectType::Tree) => {
+                        let obj: Object<'a> = entry.to_object(self.repo).unwrap();
+
+                        let tree: &Tree<'a> = obj.as_tree().unwrap();
+
+                        for entry in tree.iter() {
+                            self.entries.push(entry.to_owned());
+                        }
+                    }
+                    _ => {}
+                }
+
+                Some(entry)
+            }
+        }
+    }
+
+    fn tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo> {
+        let mut initial = vec![];
+
+        for entry in tree.iter() {
+            initial.push(entry.to_owned());
+        }
+
+        TestTreeIter {
+            entries: initial,
+            repo: repo,
+        }
+    }
+
+    #[test]
+    fn smoke_tree_iter() {
+        let (td, repo) = crate::test::repo_init();
+
+        setup_repo(&td, &repo);
+
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+        assert_eq!(tree.id(), commit.tree_id());
+        assert_eq!(tree.len(), 8);
+
+        for entry in tree_iter(&tree, &repo) {
+            println!("iter entry {:?}", entry.name());
+        }
+    }
+
+    #[test]
+    fn smoke_tree_nth() {
+        let (td, repo) = crate::test::repo_init();
+
+        setup_repo(&td, &repo);
+
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+        assert_eq!(tree.id(), commit.tree_id());
+        assert_eq!(tree.len(), 8);
+        let mut it = tree.iter();
+        let e = it.nth(4).unwrap();
+        assert_eq!(e.name(), Some("f4"));
+    }
+
+    fn setup_repo(td: &TempDir, repo: &Repository) {
+        let mut index = repo.index().unwrap();
+        for n in 0..8 {
+            let name = format!("f{n}");
+            File::create(&td.path().join(&name))
+                .unwrap()
+                .write_all(name.as_bytes())
+                .unwrap();
+            index.add_path(Path::new(&name)).unwrap();
+        }
+        let id = index.write_tree().unwrap();
+        let sig = repo.signature().unwrap();
+        let tree = repo.find_tree(id).unwrap();
+        let parent = repo
+            .find_commit(repo.head().unwrap().target().unwrap())
+            .unwrap();
+        repo.commit(
+            Some("HEAD"),
+            &sig,
+            &sig,
+            "another commit",
+            &tree,
+            &[&parent],
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn smoke() {
+        let (td, repo) = crate::test::repo_init();
+
+        setup_repo(&td, &repo);
+
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+        assert_eq!(tree.id(), commit.tree_id());
+        assert_eq!(tree.len(), 8);
+        {
+            let e0 = tree.get(0).unwrap();
+            assert!(e0 == tree.get_id(e0.id()).unwrap());
+            assert!(e0 == tree.get_name("f0").unwrap());
+            assert!(e0 == tree.get_name_bytes(b"f0").unwrap());
+            assert!(e0 == tree.get_path(Path::new("f0")).unwrap());
+            assert_eq!(e0.name(), Some("f0"));
+            e0.to_object(&repo).unwrap();
+
+            let e1 = tree.get(1).unwrap();
+            assert!(e1 == tree.get_id(e1.id()).unwrap());
+            assert!(e1 == tree.get_name("f1").unwrap());
+            assert!(e1 == tree.get_name_bytes(b"f1").unwrap());
+            assert!(e1 == tree.get_path(Path::new("f1")).unwrap());
+            assert_eq!(e1.name(), Some("f1"));
+            e1.to_object(&repo).unwrap();
+        }
+        tree.into_object();
+
+        repo.find_object(commit.tree_id(), None)
+            .unwrap()
+            .as_tree()
+            .unwrap();
+        repo.find_object(commit.tree_id(), None)
+            .unwrap()
+            .into_tree()
+            .ok()
+            .unwrap();
+    }
+
+    #[test]
+    fn tree_walk() {
+        let (td, repo) = crate::test::repo_init();
+
+        setup_repo(&td, &repo);
+
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+
+        let mut ct = 0;
+        tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+            assert_eq!(entry.name(), Some(format!("f{ct}").as_str()));
+            ct += 1;
+            0
+        })
+        .unwrap();
+        assert_eq!(ct, 8);
+
+        let mut ct = 0;
+        tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+            assert_eq!(entry.name(), Some(format!("f{ct}").as_str()));
+            ct += 1;
+            TreeWalkResult::Ok
+        })
+        .unwrap();
+        assert_eq!(ct, 8);
+    }
+
+    #[test]
+    fn tree_walk_error() {
+        let (td, repo) = crate::test::repo_init();
+
+        setup_repo(&td, &repo);
+
+        let head = repo.head().unwrap();
+        let target = head.target().unwrap();
+        let commit = repo.find_commit(target).unwrap();
+        let tree = repo.find_tree(commit.tree_id()).unwrap();
+        let e = tree.walk(TreeWalkMode::PreOrder, |_, _| -1).unwrap_err();
+        assert_eq!(e.class(), crate::ErrorClass::Callback);
+    }
+}
diff --git a/git2/src/treebuilder.rs b/git2/src/treebuilder.rs
new file mode 100644 (file)
index 0000000..1548a04
--- /dev/null
@@ -0,0 +1,234 @@
+use std::marker;
+use std::ptr;
+
+use libc::{c_int, c_void};
+
+use crate::util::{Binding, IntoCString};
+use crate::{panic, raw, tree, Error, Oid, Repository, TreeEntry};
+
+/// Constructor for in-memory trees (low-level)
+///
+/// You probably want to use [`build::TreeUpdateBuilder`] instead.
+///
+/// This is the more raw of the two tree update facilities.  It
+/// handles only one level of a nested tree structure at a time.  Each
+/// path passed to `insert` etc. must be a single component.
+///
+/// [`build::TreeUpdateBuilder`]: crate::build::TreeUpdateBuilder
+pub struct TreeBuilder<'repo> {
+    raw: *mut raw::git_treebuilder,
+    _marker: marker::PhantomData<&'repo Repository>,
+}
+
+impl<'repo> TreeBuilder<'repo> {
+    /// Clear all the entries in the builder
+    pub fn clear(&mut self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_treebuilder_clear(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Get the number of entries
+    pub fn len(&self) -> usize {
+        unsafe { raw::git_treebuilder_entrycount(self.raw) as usize }
+    }
+
+    /// Return `true` if there is no entry
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Get en entry from the builder from its filename
+    pub fn get<P>(&self, filename: P) -> Result<Option<TreeEntry<'_>>, Error>
+    where
+        P: IntoCString,
+    {
+        let filename = filename.into_c_string()?;
+        unsafe {
+            let ret = raw::git_treebuilder_get(self.raw, filename.as_ptr());
+            if ret.is_null() {
+                Ok(None)
+            } else {
+                Ok(Some(tree::entry_from_raw_const(ret)))
+            }
+        }
+    }
+
+    /// Add or update an entry in the builder
+    ///
+    /// No attempt is made to ensure that the provided Oid points to
+    /// an object of a reasonable type (or any object at all).
+    ///
+    /// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or
+    /// 0o160000 currently.
+    pub fn insert<P: IntoCString>(
+        &mut self,
+        filename: P,
+        oid: Oid,
+        filemode: i32,
+    ) -> Result<TreeEntry<'_>, Error> {
+        let filename = filename.into_c_string()?;
+        let filemode = filemode as raw::git_filemode_t;
+
+        let mut ret = ptr::null();
+        unsafe {
+            try_call!(raw::git_treebuilder_insert(
+                &mut ret,
+                self.raw,
+                filename,
+                oid.raw(),
+                filemode
+            ));
+            Ok(tree::entry_from_raw_const(ret))
+        }
+    }
+
+    /// Remove an entry from the builder by its filename
+    pub fn remove<P: IntoCString>(&mut self, filename: P) -> Result<(), Error> {
+        let filename = filename.into_c_string()?;
+        unsafe {
+            try_call!(raw::git_treebuilder_remove(self.raw, filename));
+        }
+        Ok(())
+    }
+
+    /// Selectively remove entries from the tree
+    ///
+    /// Values for which the filter returns `true` will be kept.  Note
+    /// that this behavior is different from the libgit2 C interface.
+    pub fn filter<F>(&mut self, mut filter: F) -> Result<(), Error>
+    where
+        F: FnMut(&TreeEntry<'_>) -> bool,
+    {
+        let mut cb: &mut FilterCb<'_> = &mut filter;
+        let ptr = &mut cb as *mut _;
+        let cb: raw::git_treebuilder_filter_cb = Some(filter_cb);
+        unsafe {
+            try_call!(raw::git_treebuilder_filter(self.raw, cb, ptr as *mut _));
+            panic::check();
+        }
+        Ok(())
+    }
+
+    /// Write the contents of the TreeBuilder as a Tree object and
+    /// return its Oid
+    pub fn write(&self) -> Result<Oid, Error> {
+        let mut raw = raw::git_oid {
+            id: [0; raw::GIT_OID_RAWSZ],
+        };
+        unsafe {
+            try_call!(raw::git_treebuilder_write(&mut raw, self.raw()));
+            Ok(Binding::from_raw(&raw as *const _))
+        }
+    }
+}
+
+type FilterCb<'a> = dyn FnMut(&TreeEntry<'_>) -> bool + 'a;
+
+extern "C" fn filter_cb(entry: *const raw::git_tree_entry, payload: *mut c_void) -> c_int {
+    let ret = panic::wrap(|| unsafe {
+        // There's no way to return early from git_treebuilder_filter.
+        if panic::panicked() {
+            true
+        } else {
+            let entry = tree::entry_from_raw_const(entry);
+            let payload = payload as *mut &mut FilterCb<'_>;
+            (*payload)(&entry)
+        }
+    });
+    if ret == Some(false) {
+        1
+    } else {
+        0
+    }
+}
+
+impl<'repo> Binding for TreeBuilder<'repo> {
+    type Raw = *mut raw::git_treebuilder;
+
+    unsafe fn from_raw(raw: *mut raw::git_treebuilder) -> TreeBuilder<'repo> {
+        TreeBuilder {
+            raw,
+            _marker: marker::PhantomData,
+        }
+    }
+    fn raw(&self) -> *mut raw::git_treebuilder {
+        self.raw
+    }
+}
+
+impl<'repo> Drop for TreeBuilder<'repo> {
+    fn drop(&mut self) {
+        unsafe { raw::git_treebuilder_free(self.raw) }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ObjectType;
+
+    #[test]
+    fn smoke() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut builder = repo.treebuilder(None).unwrap();
+        assert_eq!(builder.len(), 0);
+        let blob = repo.blob(b"data").unwrap();
+        {
+            let entry = builder.insert("a", blob, 0o100644).unwrap();
+            assert_eq!(entry.kind(), Some(ObjectType::Blob));
+        }
+        builder.insert("b", blob, 0o100644).unwrap();
+        assert_eq!(builder.len(), 2);
+        builder.remove("a").unwrap();
+        assert_eq!(builder.len(), 1);
+        assert_eq!(builder.get("b").unwrap().unwrap().id(), blob);
+        builder.clear().unwrap();
+        assert_eq!(builder.len(), 0);
+    }
+
+    #[test]
+    fn write() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut builder = repo.treebuilder(None).unwrap();
+        let data = repo.blob(b"data").unwrap();
+        builder.insert("name", data, 0o100644).unwrap();
+        let tree = builder.write().unwrap();
+        let tree = repo.find_tree(tree).unwrap();
+        let entry = tree.get(0).unwrap();
+        assert_eq!(entry.name(), Some("name"));
+        let blob = entry.to_object(&repo).unwrap();
+        let blob = blob.as_blob().unwrap();
+        assert_eq!(blob.content(), b"data");
+
+        let builder = repo.treebuilder(Some(&tree)).unwrap();
+        assert_eq!(builder.len(), 1);
+    }
+
+    #[test]
+    fn filter() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let mut builder = repo.treebuilder(None).unwrap();
+        let blob = repo.blob(b"data").unwrap();
+        let tree = {
+            let head = repo.head().unwrap().peel(ObjectType::Commit).unwrap();
+            let head = head.as_commit().unwrap();
+            head.tree_id()
+        };
+        builder.insert("blob", blob, 0o100644).unwrap();
+        builder.insert("dir", tree, 0o040000).unwrap();
+        builder.insert("dir2", tree, 0o040000).unwrap();
+
+        builder.filter(|_| true).unwrap();
+        assert_eq!(builder.len(), 3);
+        builder
+            .filter(|e| e.kind().unwrap() != ObjectType::Blob)
+            .unwrap();
+        assert_eq!(builder.len(), 2);
+        builder.filter(|_| false).unwrap();
+        assert_eq!(builder.len(), 0);
+    }
+}
diff --git a/git2/src/util.rs b/git2/src/util.rs
new file mode 100644 (file)
index 0000000..4065492
--- /dev/null
@@ -0,0 +1,341 @@
+use libc::{c_char, c_int, size_t};
+use std::cmp::Ordering;
+use std::ffi::{CString, OsStr, OsString};
+use std::path::{Component, Path, PathBuf};
+
+use crate::{raw, Error};
+
+#[doc(hidden)]
+pub trait IsNull {
+    fn is_ptr_null(&self) -> bool;
+}
+impl<T> IsNull for *const T {
+    fn is_ptr_null(&self) -> bool {
+        self.is_null()
+    }
+}
+impl<T> IsNull for *mut T {
+    fn is_ptr_null(&self) -> bool {
+        self.is_null()
+    }
+}
+
+#[doc(hidden)]
+pub trait Binding: Sized {
+    type Raw;
+
+    unsafe fn from_raw(raw: Self::Raw) -> Self;
+    fn raw(&self) -> Self::Raw;
+
+    unsafe fn from_raw_opt<T>(raw: T) -> Option<Self>
+    where
+        T: Copy + IsNull,
+        Self: Binding<Raw = T>,
+    {
+        if raw.is_ptr_null() {
+            None
+        } else {
+            Some(Binding::from_raw(raw))
+        }
+    }
+}
+
+/// Converts an iterator of repo paths into a git2-compatible array of cstrings.
+///
+/// Only use this for repo-relative paths or pathspecs.
+///
+/// See `iter2cstrs` for more details.
+pub fn iter2cstrs_paths<T, I>(
+    iter: I,
+) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
+where
+    T: IntoCString,
+    I: IntoIterator<Item = T>,
+{
+    let cstrs = iter
+        .into_iter()
+        .map(|i| fixup_windows_path(i.into_c_string()?))
+        .collect::<Result<Vec<CString>, _>>()?;
+    iter2cstrs(cstrs)
+}
+
+/// Converts an iterator of things into a git array of c-strings.
+///
+/// Returns a tuple `(cstrings, pointers, git_strarray)`. The first two values
+/// should not be dropped before `git_strarray`.
+pub fn iter2cstrs<T, I>(
+    iter: I,
+) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
+where
+    T: IntoCString,
+    I: IntoIterator<Item = T>,
+{
+    let cstrs = iter
+        .into_iter()
+        .map(|i| i.into_c_string())
+        .collect::<Result<Vec<CString>, _>>()?;
+    let ptrs = cstrs.iter().map(|i| i.as_ptr()).collect::<Vec<_>>();
+    let raw = raw::git_strarray {
+        strings: ptrs.as_ptr() as *mut _,
+        count: ptrs.len() as size_t,
+    };
+    Ok((cstrs, ptrs, raw))
+}
+
+#[cfg(unix)]
+pub fn bytes2path(b: &[u8]) -> &Path {
+    use std::os::unix::prelude::*;
+    Path::new(OsStr::from_bytes(b))
+}
+#[cfg(windows)]
+pub fn bytes2path(b: &[u8]) -> &Path {
+    use std::str;
+    Path::new(str::from_utf8(b).unwrap())
+}
+
+/// A class of types that can be converted to C strings.
+///
+/// These types are represented internally as byte slices and it is quite rare
+/// for them to contain an interior 0 byte.
+pub trait IntoCString {
+    /// Consume this container, converting it into a CString
+    fn into_c_string(self) -> Result<CString, Error>;
+}
+
+impl<'a, T: IntoCString + Clone> IntoCString for &'a T {
+    fn into_c_string(self) -> Result<CString, Error> {
+        self.clone().into_c_string()
+    }
+}
+
+impl<'a> IntoCString for &'a str {
+    fn into_c_string(self) -> Result<CString, Error> {
+        Ok(CString::new(self)?)
+    }
+}
+
+impl IntoCString for String {
+    fn into_c_string(self) -> Result<CString, Error> {
+        Ok(CString::new(self.into_bytes())?)
+    }
+}
+
+impl IntoCString for CString {
+    fn into_c_string(self) -> Result<CString, Error> {
+        Ok(self)
+    }
+}
+
+impl<'a> IntoCString for &'a Path {
+    fn into_c_string(self) -> Result<CString, Error> {
+        let s: &OsStr = self.as_ref();
+        s.into_c_string()
+    }
+}
+
+impl IntoCString for PathBuf {
+    fn into_c_string(self) -> Result<CString, Error> {
+        let s: OsString = self.into();
+        s.into_c_string()
+    }
+}
+
+impl<'a> IntoCString for &'a OsStr {
+    fn into_c_string(self) -> Result<CString, Error> {
+        self.to_os_string().into_c_string()
+    }
+}
+
+impl IntoCString for OsString {
+    #[cfg(unix)]
+    fn into_c_string(self) -> Result<CString, Error> {
+        use std::os::unix::prelude::*;
+        let s: &OsStr = self.as_ref();
+        Ok(CString::new(s.as_bytes())?)
+    }
+    #[cfg(windows)]
+    fn into_c_string(self) -> Result<CString, Error> {
+        match self.to_str() {
+            Some(s) => s.into_c_string(),
+            None => Err(Error::from_str(
+                "only valid unicode paths are accepted on windows",
+            )),
+        }
+    }
+}
+
+impl<'a> IntoCString for &'a [u8] {
+    fn into_c_string(self) -> Result<CString, Error> {
+        Ok(CString::new(self)?)
+    }
+}
+
+impl IntoCString for Vec<u8> {
+    fn into_c_string(self) -> Result<CString, Error> {
+        Ok(CString::new(self)?)
+    }
+}
+
+pub fn into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error>
+where
+    S: IntoCString,
+{
+    match opt_s {
+        None => Ok(None),
+        Some(s) => Ok(Some(s.into_c_string()?)),
+    }
+}
+
+pub fn c_cmp_to_ordering(cmp: c_int) -> Ordering {
+    match cmp {
+        0 => Ordering::Equal,
+        n if n < 0 => Ordering::Less,
+        _ => Ordering::Greater,
+    }
+}
+
+/// Converts a path to a CString that is usable by the libgit2 API.
+///
+/// Checks if it is a relative path.
+///
+/// On Windows, this also requires the path to be valid Unicode, and translates
+/// back slashes to forward slashes.
+pub fn path_to_repo_path(path: &Path) -> Result<CString, Error> {
+    macro_rules! err {
+        ($msg:literal, $path:expr) => {
+            return Err(Error::from_str(&format!($msg, $path.display())))
+        };
+    }
+    match path.components().next() {
+        None => return Err(Error::from_str("repo path should not be empty")),
+        Some(Component::Prefix(_)) => err!(
+            "repo path `{}` should be relative, not a windows prefix",
+            path
+        ),
+        Some(Component::RootDir) => err!("repo path `{}` should be relative", path),
+        Some(Component::CurDir) => err!("repo path `{}` should not start with `.`", path),
+        Some(Component::ParentDir) => err!("repo path `{}` should not start with `..`", path),
+        Some(Component::Normal(_)) => {}
+    }
+    #[cfg(windows)]
+    {
+        match path.to_str() {
+            None => {
+                return Err(Error::from_str(
+                    "only valid unicode paths are accepted on windows",
+                ))
+            }
+            Some(s) => return fixup_windows_path(s),
+        }
+    }
+    #[cfg(not(windows))]
+    {
+        path.into_c_string()
+    }
+}
+
+pub fn cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error> {
+    fixup_windows_path(path.into_c_string()?)
+}
+
+#[cfg(windows)]
+fn fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error> {
+    let mut bytes: Vec<u8> = path.into();
+    for i in 0..bytes.len() {
+        if bytes[i] == b'\\' {
+            bytes[i] = b'/';
+        }
+    }
+    Ok(CString::new(bytes)?)
+}
+
+#[cfg(not(windows))]
+fn fixup_windows_path(path: CString) -> Result<CString, Error> {
+    Ok(path)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    macro_rules! assert_err {
+        ($path:expr, $msg:expr) => {
+            match path_to_repo_path(Path::new($path)) {
+                Ok(_) => panic!("expected `{}` to err", $path),
+                Err(e) => assert_eq!(e.message(), $msg),
+            }
+        };
+    }
+
+    macro_rules! assert_repo_path_ok {
+        ($path:expr) => {
+            assert_repo_path_ok!($path, $path)
+        };
+        ($path:expr, $expect:expr) => {
+            assert_eq!(
+                path_to_repo_path(Path::new($path)),
+                Ok(CString::new($expect).unwrap())
+            );
+        };
+    }
+
+    #[test]
+    #[cfg(windows)]
+    fn path_to_repo_path_translate() {
+        assert_repo_path_ok!("foo");
+        assert_repo_path_ok!("foo/bar");
+        assert_repo_path_ok!(r"foo\bar", "foo/bar");
+        assert_repo_path_ok!(r"foo\bar\", "foo/bar/");
+    }
+
+    #[test]
+    fn path_to_repo_path_no_weird() {
+        assert_err!("", "repo path should not be empty");
+        assert_err!("./foo", "repo path `./foo` should not start with `.`");
+        assert_err!("../foo", "repo path `../foo` should not start with `..`");
+    }
+
+    #[test]
+    #[cfg(not(windows))]
+    fn path_to_repo_path_no_absolute() {
+        assert_err!("/", "repo path `/` should be relative");
+        assert_repo_path_ok!("foo/bar");
+    }
+
+    #[test]
+    #[cfg(windows)]
+    fn path_to_repo_path_no_absolute() {
+        assert_err!(
+            r"c:",
+            r"repo path `c:` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"c:\",
+            r"repo path `c:\` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"c:temp",
+            r"repo path `c:temp` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"\\?\UNC\a\b\c",
+            r"repo path `\\?\UNC\a\b\c` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"\\?\c:\foo",
+            r"repo path `\\?\c:\foo` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"\\.\COM42",
+            r"repo path `\\.\COM42` should be relative, not a windows prefix"
+        );
+        assert_err!(
+            r"\\a\b",
+            r"repo path `\\a\b` should be relative, not a windows prefix"
+        );
+        assert_err!(r"\", r"repo path `\` should be relative");
+        assert_err!(r"/", r"repo path `/` should be relative");
+        assert_err!(r"\foo", r"repo path `\foo` should be relative");
+        assert_err!(r"/foo", r"repo path `/foo` should be relative");
+    }
+}
diff --git a/git2/src/version.rs b/git2/src/version.rs
new file mode 100644 (file)
index 0000000..b5dd4fb
--- /dev/null
@@ -0,0 +1,95 @@
+use crate::raw;
+use libc::c_int;
+use std::fmt;
+
+/// Version information about libgit2 and the capabilities it supports.
+pub struct Version {
+    major: c_int,
+    minor: c_int,
+    rev: c_int,
+    features: c_int,
+}
+
+macro_rules! flag_test {
+    ($features:expr, $flag:expr) => {
+        ($features as u32 & $flag as u32) != 0
+    };
+}
+
+impl Version {
+    /// Returns a [`Version`] which provides information about libgit2.
+    pub fn get() -> Version {
+        let mut v = Version {
+            major: 0,
+            minor: 0,
+            rev: 0,
+            features: 0,
+        };
+        unsafe {
+            raw::git_libgit2_version(&mut v.major, &mut v.minor, &mut v.rev);
+            v.features = raw::git_libgit2_features();
+        }
+        v
+    }
+
+    /// Returns the version of libgit2.
+    ///
+    /// The return value is a tuple of `(major, minor, rev)`
+    pub fn libgit2_version(&self) -> (u32, u32, u32) {
+        (self.major as u32, self.minor as u32, self.rev as u32)
+    }
+
+    /// Returns the version of the libgit2-sys crate.
+    pub fn crate_version(&self) -> &'static str {
+        env!("CARGO_PKG_VERSION")
+    }
+
+    /// Returns true if this was built with the vendored version of libgit2.
+    pub fn vendored(&self) -> bool {
+        raw::vendored()
+    }
+
+    /// Returns true if libgit2 was built thread-aware and can be safely used
+    /// from multiple threads.
+    pub fn threads(&self) -> bool {
+        flag_test!(self.features, raw::GIT_FEATURE_THREADS)
+    }
+
+    /// Returns true if libgit2 was built with and linked against a TLS implementation.
+    ///
+    /// Custom TLS streams may still be added by the user to support HTTPS
+    /// regardless of this.
+    pub fn https(&self) -> bool {
+        flag_test!(self.features, raw::GIT_FEATURE_HTTPS)
+    }
+
+    /// Returns true if libgit2 was built with and linked against libssh2.
+    ///
+    /// A custom transport may still be added by the user to support libssh2
+    /// regardless of this.
+    pub fn ssh(&self) -> bool {
+        flag_test!(self.features, raw::GIT_FEATURE_SSH)
+    }
+
+    /// Returns true if libgit2 was built with support for sub-second
+    /// resolution in file modification times.
+    pub fn nsec(&self) -> bool {
+        flag_test!(self.features, raw::GIT_FEATURE_NSEC)
+    }
+}
+
+impl fmt::Debug for Version {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+        let mut f = f.debug_struct("Version");
+        f.field("major", &self.major)
+            .field("minor", &self.minor)
+            .field("rev", &self.rev)
+            .field("crate_version", &self.crate_version())
+            .field("vendored", &self.vendored())
+            .field("threads", &self.threads())
+            .field("https", &self.https())
+            .field("ssh", &self.ssh())
+            .field("nsec", &self.nsec());
+        f.finish()
+    }
+}
diff --git a/git2/src/worktree.rs b/git2/src/worktree.rs
new file mode 100644 (file)
index 0000000..fc32902
--- /dev/null
@@ -0,0 +1,337 @@
+use crate::buf::Buf;
+use crate::reference::Reference;
+use crate::repo::Repository;
+use crate::util::{self, Binding};
+use crate::{raw, Error};
+use std::os::raw::c_int;
+use std::path::Path;
+use std::ptr;
+use std::str;
+use std::{marker, mem};
+
+/// An owned git worktree
+///
+/// This structure corresponds to a `git_worktree` in libgit2.
+//
+pub struct Worktree {
+    raw: *mut raw::git_worktree,
+}
+
+/// Options which can be used to configure how a worktree is initialized
+pub struct WorktreeAddOptions<'a> {
+    raw: raw::git_worktree_add_options,
+    _marker: marker::PhantomData<Reference<'a>>,
+}
+
+/// Options to configure how worktree pruning is performed
+pub struct WorktreePruneOptions {
+    raw: raw::git_worktree_prune_options,
+}
+
+/// Lock Status of a worktree
+#[derive(PartialEq, Debug)]
+pub enum WorktreeLockStatus {
+    /// Worktree is Unlocked
+    Unlocked,
+    /// Worktree is locked with the optional message
+    Locked(Option<String>),
+}
+
+impl Worktree {
+    /// Open a worktree of a the repository
+    ///
+    /// If a repository is not the main tree but a worktree, this
+    /// function will look up the worktree inside the parent
+    /// repository and create a new `git_worktree` structure.
+    pub fn open_from_repository(repo: &Repository) -> Result<Worktree, Error> {
+        let mut raw = ptr::null_mut();
+        unsafe {
+            try_call!(raw::git_worktree_open_from_repository(&mut raw, repo.raw()));
+            Ok(Binding::from_raw(raw))
+        }
+    }
+
+    /// Retrieves the name of the worktree
+    ///
+    /// This is the name that can be passed to repo::Repository::find_worktree
+    /// to reopen the worktree. This is also the name that would appear in the
+    /// list returned by repo::Repository::worktrees
+    pub fn name(&self) -> Option<&str> {
+        unsafe {
+            crate::opt_bytes(self, raw::git_worktree_name(self.raw))
+                .and_then(|s| str::from_utf8(s).ok())
+        }
+    }
+
+    /// Retrieves the path to the worktree
+    ///
+    /// This is the path to the top-level of the source and not the path to the
+    /// .git file within the worktree. This path can be passed to
+    /// repo::Repository::open.
+    pub fn path(&self) -> &Path {
+        unsafe {
+            util::bytes2path(crate::opt_bytes(self, raw::git_worktree_path(self.raw)).unwrap())
+        }
+    }
+
+    /// Validates the worktree
+    ///
+    /// This checks that it still exists on the
+    /// filesystem and that the metadata is correct
+    pub fn validate(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_worktree_validate(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Locks the worktree
+    pub fn lock(&self, reason: Option<&str>) -> Result<(), Error> {
+        let reason = crate::opt_cstr(reason)?;
+        unsafe {
+            try_call!(raw::git_worktree_lock(self.raw, reason));
+        }
+        Ok(())
+    }
+
+    /// Unlocks the worktree
+    pub fn unlock(&self) -> Result<(), Error> {
+        unsafe {
+            try_call!(raw::git_worktree_unlock(self.raw));
+        }
+        Ok(())
+    }
+
+    /// Checks if worktree is locked
+    pub fn is_locked(&self) -> Result<WorktreeLockStatus, Error> {
+        let buf = Buf::new();
+        unsafe {
+            match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) {
+                0 => Ok(WorktreeLockStatus::Unlocked),
+                _ => {
+                    let v = buf.to_vec();
+                    Ok(WorktreeLockStatus::Locked(match v.len() {
+                        0 => None,
+                        _ => Some(String::from_utf8(v).unwrap()),
+                    }))
+                }
+            }
+        }
+    }
+
+    /// Prunes the worktree
+    pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> {
+        // When successful the worktree should be removed however the backing structure
+        // of the git_worktree should still be valid.
+        unsafe {
+            try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw())));
+        }
+        Ok(())
+    }
+
+    /// Checks if the worktree is prunable
+    pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<bool, Error> {
+        unsafe {
+            let rv = try_call!(raw::git_worktree_is_prunable(
+                self.raw,
+                opts.map(|o| o.raw())
+            ));
+            Ok(rv != 0)
+        }
+    }
+}
+
+impl<'a> WorktreeAddOptions<'a> {
+    /// Creates a default set of add options.
+    ///
+    /// By default this will not lock the worktree
+    pub fn new() -> WorktreeAddOptions<'a> {
+        unsafe {
+            let mut raw = mem::zeroed();
+            assert_eq!(
+                raw::git_worktree_add_options_init(&mut raw, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION),
+                0
+            );
+            WorktreeAddOptions {
+                raw,
+                _marker: marker::PhantomData,
+            }
+        }
+    }
+
+    /// If enabled, this will cause the newly added worktree to be locked
+    pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
+        self.raw.lock = enabled as c_int;
+        self
+    }
+
+    /// If enabled, this will checkout the existing branch matching the worktree name.
+    pub fn checkout_existing(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> {
+        self.raw.checkout_existing = enabled as c_int;
+        self
+    }
+
+    /// reference to use for the new worktree HEAD
+    pub fn reference(
+        &mut self,
+        reference: Option<&'a Reference<'_>>,
+    ) -> &mut WorktreeAddOptions<'a> {
+        self.raw.reference = if let Some(reference) = reference {
+            reference.raw()
+        } else {
+            ptr::null_mut()
+        };
+        self
+    }
+
+    /// Get a set of raw add options to be used with `git_worktree_add`
+    pub fn raw(&self) -> *const raw::git_worktree_add_options {
+        &self.raw
+    }
+}
+
+impl WorktreePruneOptions {
+    /// Creates a default set of pruning options
+    ///
+    /// By defaults this will prune only worktrees that are no longer valid
+    /// unlocked and not checked out
+    pub fn new() -> WorktreePruneOptions {
+        unsafe {
+            let mut raw = mem::zeroed();
+            assert_eq!(
+                raw::git_worktree_prune_options_init(
+                    &mut raw,
+                    raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION
+                ),
+                0
+            );
+            WorktreePruneOptions { raw }
+        }
+    }
+
+    /// Controls whether valid (still existing on the filesystem) worktrees
+    /// will be pruned
+    ///
+    /// Defaults to false
+    pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions {
+        self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid)
+    }
+
+    /// Controls whether locked worktrees will be pruned
+    ///
+    /// Defaults to false
+    pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions {
+        self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked)
+    }
+
+    /// Controls whether the actual working tree on the filesystem is recursively removed
+    ///
+    /// Defaults to false
+    pub fn working_tree(&mut self, working_tree: bool) -> &mut WorktreePruneOptions {
+        self.flag(raw::GIT_WORKTREE_PRUNE_WORKING_TREE, working_tree)
+    }
+
+    fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions {
+        if on {
+            self.raw.flags |= flag as u32;
+        } else {
+            self.raw.flags &= !(flag as u32);
+        }
+        self
+    }
+
+    /// Get a set of raw prune options to be used with `git_worktree_prune`
+    pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options {
+        &mut self.raw
+    }
+}
+
+impl Binding for Worktree {
+    type Raw = *mut raw::git_worktree;
+    unsafe fn from_raw(ptr: *mut raw::git_worktree) -> Worktree {
+        Worktree { raw: ptr }
+    }
+    fn raw(&self) -> *mut raw::git_worktree {
+        self.raw
+    }
+}
+
+impl Drop for Worktree {
+    fn drop(&mut self) {
+        unsafe { raw::git_worktree_free(self.raw) }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::WorktreeAddOptions;
+    use crate::WorktreeLockStatus;
+
+    use tempfile::TempDir;
+
+    #[test]
+    fn smoke_add_no_ref() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let wtdir = TempDir::new().unwrap();
+        let wt_path = wtdir.path().join("tree-no-ref-dir");
+        let opts = WorktreeAddOptions::new();
+
+        let wt = repo.worktree("tree-no-ref", &wt_path, Some(&opts)).unwrap();
+        assert_eq!(wt.name(), Some("tree-no-ref"));
+        assert_eq!(
+            wt.path().canonicalize().unwrap(),
+            wt_path.canonicalize().unwrap()
+        );
+        let status = wt.is_locked().unwrap();
+        assert_eq!(status, WorktreeLockStatus::Unlocked);
+    }
+
+    #[test]
+    fn smoke_add_locked() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let wtdir = TempDir::new().unwrap();
+        let wt_path = wtdir.path().join("locked-tree");
+        let mut opts = WorktreeAddOptions::new();
+        opts.lock(true);
+
+        let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap();
+        // shouldn't be able to lock a worktree that was created locked
+        assert!(wt.lock(Some("my reason")).is_err());
+        assert_eq!(wt.name(), Some("locked-tree"));
+        assert_eq!(
+            wt.path().canonicalize().unwrap(),
+            wt_path.canonicalize().unwrap()
+        );
+        assert_eq!(wt.is_locked().unwrap(), WorktreeLockStatus::Locked(None));
+        assert!(wt.unlock().is_ok());
+        assert!(wt.lock(Some("my reason")).is_ok());
+        assert_eq!(
+            wt.is_locked().unwrap(),
+            WorktreeLockStatus::Locked(Some("my reason".to_string()))
+        );
+    }
+
+    #[test]
+    fn smoke_add_from_branch() {
+        let (_td, repo) = crate::test::repo_init();
+
+        let (wt_top, branch) = crate::test::worktrees_env_init(&repo);
+        let wt_path = wt_top.path().join("test");
+        let mut opts = WorktreeAddOptions::new();
+        let reference = branch.into_reference();
+        opts.reference(Some(&reference));
+
+        let wt = repo
+            .worktree("test-worktree", &wt_path, Some(&opts))
+            .unwrap();
+        assert_eq!(wt.name(), Some("test-worktree"));
+        assert_eq!(
+            wt.path().canonicalize().unwrap(),
+            wt_path.canonicalize().unwrap()
+        );
+        let status = wt.is_locked().unwrap();
+        assert_eq!(status, WorktreeLockStatus::Unlocked);
+    }
+}
diff --git a/git2/tests/add_extensions.rs b/git2/tests/add_extensions.rs
new file mode 100644 (file)
index 0000000..d49c33c
--- /dev/null
@@ -0,0 +1,30 @@
+//! Test for `set_extensions`, which writes a global state maintained by libgit2
+
+use git2::opts::{get_extensions, set_extensions};
+use git2::Error;
+
+#[test]
+fn test_add_extensions() -> Result<(), Error> {
+    unsafe {
+        set_extensions(&["custom"])?;
+    }
+
+    let extensions = unsafe { get_extensions() }?;
+    let extensions: Vec<_> = extensions.iter().collect();
+
+    assert_eq!(
+        extensions,
+        [
+            Some("custom"),
+            Some("noop"),
+            // The objectformat extension was added in 1.6
+            Some("objectformat"),
+            // The preciousobjects extension was added in 1.9
+            Some("preciousobjects"),
+            // The worktreeconfig extension was added in 1.8
+            Some("worktreeconfig")
+        ]
+    );
+
+    Ok(())
+}
diff --git a/git2/tests/get_extensions.rs b/git2/tests/get_extensions.rs
new file mode 100644 (file)
index 0000000..2ac362d
--- /dev/null
@@ -0,0 +1,25 @@
+//! Test for `get_extensions`, which reads a global state maintained by libgit2
+
+use git2::opts::get_extensions;
+use git2::Error;
+
+#[test]
+fn test_get_extensions() -> Result<(), Error> {
+    let extensions = unsafe { get_extensions() }?;
+    let extensions: Vec<_> = extensions.iter().collect();
+
+    assert_eq!(
+        extensions,
+        [
+            Some("noop"),
+            // The objectformat extension was added in 1.6
+            Some("objectformat"),
+            // The preciousobjects extension was added in 1.9
+            Some("preciousobjects"),
+            // The worktreeconfig extension was added in 1.8
+            Some("worktreeconfig")
+        ]
+    );
+
+    Ok(())
+}
diff --git a/git2/tests/global_state.rs b/git2/tests/global_state.rs
new file mode 100644 (file)
index 0000000..192acdb
--- /dev/null
@@ -0,0 +1,47 @@
+//! Test for some global state set up by libgit2's `git_libgit2_init` function
+//! that need to be synchronized within a single process.
+
+use git2::opts;
+use git2::{ConfigLevel, IntoCString};
+
+// Test for mutating configuration file search path which is set during
+// initialization in libgit2's `git_sysdir_global_init` function.
+#[test]
+fn search_path() -> Result<(), Box<dyn std::error::Error>> {
+    use std::env::join_paths;
+
+    let path = "fake_path";
+    let original = unsafe { opts::get_search_path(ConfigLevel::Global) };
+    assert_ne!(original, Ok(path.into_c_string()?));
+
+    // Set
+    unsafe {
+        opts::set_search_path(ConfigLevel::Global, &path)?;
+    }
+    assert_eq!(
+        unsafe { opts::get_search_path(ConfigLevel::Global) },
+        Ok(path.into_c_string()?)
+    );
+
+    // Append
+    let paths = join_paths(["$PATH", path].iter())?;
+    let expected_paths = join_paths([path, path].iter())?.into_c_string()?;
+    unsafe {
+        opts::set_search_path(ConfigLevel::Global, paths)?;
+    }
+    assert_eq!(
+        unsafe { opts::get_search_path(ConfigLevel::Global) },
+        Ok(expected_paths)
+    );
+
+    // Reset
+    unsafe {
+        opts::reset_search_path(ConfigLevel::Global)?;
+    }
+    assert_eq!(
+        unsafe { opts::get_search_path(ConfigLevel::Global) },
+        original
+    );
+
+    Ok(())
+}
diff --git a/git2/tests/remove_extensions.rs b/git2/tests/remove_extensions.rs
new file mode 100644 (file)
index 0000000..3e54b42
--- /dev/null
@@ -0,0 +1,26 @@
+//! Test for `set_extensions`, which writes a global state maintained by libgit2
+
+use git2::opts::{get_extensions, set_extensions};
+use git2::Error;
+
+#[test]
+fn test_remove_extensions() -> Result<(), Error> {
+    unsafe {
+        set_extensions(&[
+            "custom",
+            "!ignore",
+            "!noop",
+            "!objectformat",
+            "!preciousobjects",
+            "!worktreeconfig",
+            "other",
+        ])?;
+    }
+
+    let extensions = unsafe { get_extensions() }?;
+    let extensions: Vec<_> = extensions.iter().collect();
+
+    assert_eq!(extensions, [Some("custom"), Some("other")]);
+
+    Ok(())
+}
diff --git a/libgit2-sys/.cargo-checksum.json b/libgit2-sys/.cargo-checksum.json
new file mode 100644 (file)
index 0000000..9dcb7de
--- /dev/null
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"239e09ac517677e56e328552756812f3450e678976f64d2d9c826d2de1939f02","Cargo.toml":"2c4902c9fdc45b8be7ac49aba35880a65832957644922938e9d3797ff8d9344e","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"378f5840b258e2779c39418f3f2d7b2ba96f1c7917dd6be0713f88305dbda397","build.rs":"0ecd9d34196f254bedcbbf7181c4e495aa1a8f6766a7baf3d6a73678fd73eee6","lib.rs":"d3654c08b1b9b020374df8d10b13f4624b0a71b004f927ecc2e54835e0f36383","libgit2/AUTHORS":"56a6299291549dcd4d247dc78329e367df2e509336f6457b50946af55ec37a33","libgit2/CMakeLists.txt":"bf5daee877bca7801a8a20d8b7ecee4f78128e094acdf2d0b352229260edc39f","libgit2/COPYING":"e3712465634e97cfd850822a4eb5ac7d2f8a10f753189366d5a2060046f28288","libgit2/FUNDING.json":"cadc5a95ab8b3065020b9db155081c5949110bc70ba2d68fa74e7b98fb212fc0","libgit2/README.md":"b95462dd7737099aa1f5db59c19cc04bbf4ce9c8e11bd4a6b29d1f6687707385","libgit2/SECURITY.md":"991b73e51ce6f6036351fd00d7cfb3918e8cb884acee446712948e9f25c50ce3","libgit2/api.docurium":"c77dc54504945864b3270877a9ddcabe85d6d1a742c07232f6faf847958c0dd2","libgit2/cmake/AddCFlagIfSupported.cmake":"4be37a9752e3859c9ab8525f365fcb4392d1e5135ea222795c304b42dcf11b13","libgit2/cmake/AddClarTest.cmake":"022f6d1a498bead384338fd1b17ddcf72ce18d6ce54b6c0298ee3ea61c0d63ea","libgit2/cmake/CheckPrototypeDefinitionSafe.cmake":"24781f975f8d49cd044c07cf010050593317e66575ea1002b4b896e8b96bb45f","libgit2/cmake/DefaultCFlags.cmake":"a2b5a645f29bb3be74aea7bf5f5d83a77f68d3190363e83b43f9ba70a41c5963","libgit2/cmake/EnableWarnings.cmake":"f5690cb37a672c92696f618890da93d44eaa07f3cdad9a3515bb08125909ac0f","libgit2/cmake/ExperimentalFeatures.cmake":"5722077113489154402f9ba758aff0e351c47887110e1ec2bb7da3ca50f63652","libgit2/cmake/FindCoreFoundation.cmake":"5f71227384a4914df198dc7766b6b8474942730a16c22de0c987c9548808f1c6","libgit2/cmake/FindGSSAPI.cmake":"6704673fcbc0c94c02395a3e4fd96f41fbe0111397f0f06dd9a912d11c1ddb91","libgit2/cmake/FindGSSFramework.cmake":"136f3282657cd83aa5a9ea4a3d11ffd14d839e637c6e2669aa5f0c0eb9d9daab","libgit2/cmake/FindHTTP_Parser.cmake":"ca1d95edef14b1e05449d45dcbdd93c386025c408cbf27a00cb4af4ab0d62a7f","libgit2/cmake/FindIntlIconv.cmake":"75a64438565daa580681a4b856046debbdb38f52abe4db8b689192e5209f53e4","libgit2/cmake/FindLLHTTP.cmake":"08e5b8c62230535abf9f8eeac76ce60e254956b1318c5303b30b0bffc9565bdb","libgit2/cmake/FindLibSSH2.cmake":"0cfe918a5d00dbceeae55ffeb5546f8c5595eb48558c2e9584561db17f29b91f","libgit2/cmake/FindPCRE.cmake":"2b1e7b45cf0410320e706573ecce75b8be4eeda6b9b4fa19893a248c57096a71","libgit2/cmake/FindPCRE2.cmake":"d8045c217a1856963badd52548406cab2881d2ae304608d6b40bd77bcb590f0e","libgit2/cmake/FindPkgLibraries.cmake":"e50a58f9d39ed58d5bfc39e6f18066b436cf92ccabd6683fed4f30cee9689627","libgit2/cmake/FindSecurity.cmake":"1c16ac77018f28efac83cd848c130d5127c22999dc48efe44bc8ee9b501deb29","libgit2/cmake/FindStatNsec.cmake":"4b3bb2337ce205580703704a2725eba085044810d54a0e0cc5680b6f71eb494f","libgit2/cmake/Findfutimens.cmake":"281fa0a75648482e3b13841d92db2c5332147fa8c7f96afe7ee11eae77a6bfd3","libgit2/cmake/FindmbedTLS.cmake":"23d2c96094b525b7a333427776cf51f0a1466a70d9358af48c8d851e20ebad64","libgit2/cmake/IdeSplitSources.cmake":"0c1725438bac95a6c56627408b81017a88748944b0ff83cc45c0824421e3aeee","libgit2/cmake/PkgBuildConfig.cmake":"fdf17c32ee3621b671695f29a1373f4e2d05606dad03a658a751e94a37385f91","libgit2/cmake/SanitizeBool.cmake":"6d93d776331f1cc9d4052ed53731789d6e141bba9ac8681dba70a80abb2cdc00","libgit2/cmake/SelectGSSAPI.cmake":"fdd233669f3d426adf1a773e021e2761e56f49d073a2af56652b230ddc4d949e","libgit2/cmake/SelectHTTPParser.cmake":"c083d30eaa75b7fa5d6dd2c71b6b0207f62264abc7b10fa63ee3f1657810e9b6","libgit2/cmake/SelectHTTPSBackend.cmake":"bb62b26210aaf9cec0f745aee9fccccfbd751f6d78ecdf009d3e62064966033b","libgit2/cmake/SelectHashes.cmake":"d146e3544637032d3afb1d2d8b282ddfead8d882241244f9dffb70e074e708aa","libgit2/cmake/SelectRegex.cmake":"b3e6fd581fd93a0e5e52c18ebb7ffc08b50bce8f97507e3bcfc32e49523dc709","libgit2/cmake/SelectSSH.cmake":"41fa2238d92a33da0b59d16d4cd71c02311fc3ab276d3b66057ec750f79b768f","libgit2/cmake/SelectXdiff.cmake":"aede153b2634f2485d3717896d453b0f87cf26873ea02e89cc5aa31106ecd6c4","libgit2/cmake/SelectZlib.cmake":"b95582c741f759625ac8590e7728c00693397d9813bd41ec97aec889a63a5f19","libgit2/deps/chromium-zlib/CMakeLists.txt":"f8c70a5cede31712527ba84f3eb0c0a8319cbf7643c6afb63c1fdfbed1e757d7","libgit2/deps/llhttp/CMakeLists.txt":"1d96268d8a4ca2cbf0f40b0211e66c4c046badf51ffc8e80a4f074b72b965752","libgit2/deps/llhttp/LICENSE-MIT":"ebca854e0134cd256d673627c20499f42577eb74bacf08b9f25b626a73c91277","libgit2/deps/llhttp/api.c":"97f40be464a6d72731673f8f0dca19f5f54feec6bbbdcce0646d1d85b64084be","libgit2/deps/llhttp/http.c":"a1f2b23168f8e9b5bfa464c89027d3ca259fc649ae3d7e72bfff997d1aeb5d72","libgit2/deps/llhttp/llhttp.c":"d853660d90a48b370fc3aac5fd9ae664a79860522fbac9cf5c40f70e40af03c1","libgit2/deps/llhttp/llhttp.h":"066ec0eea575a2dddad32ab9ca39b2805b69b32187ed78e1231cfee24b786f3c","libgit2/deps/ntlmclient/CMakeLists.txt":"25fc0e36038a920f14bb7b439b8f1317a33a32666a9d1e9e993183d68c90cb54","libgit2/deps/ntlmclient/compat.h":"f27a8e91c75ed75a71140c597548b43fae19b28eda1a09ab28e367a196bdad84","libgit2/deps/ntlmclient/crypt.h":"38647af40b21a8baf2c2e73e80ff631718b7fb0db51b0c2079d4bc85793a2032","libgit2/deps/ntlmclient/crypt_builtin_md4.c":"2a18370c597317819359886a54062820048a0621d4e38b89e9a5e5760c69777e","libgit2/deps/ntlmclient/crypt_commoncrypto.c":"c00d8ab310df82c32f51ac11ed02ba69cb4a13a56d0848e8bfd8b928ecb60c0c","libgit2/deps/ntlmclient/crypt_commoncrypto.h":"55a6fe82280421e5fd61f20be1938cc92efef1a211147072790eecc91eb87e6f","libgit2/deps/ntlmclient/crypt_mbedtls.c":"1c2e053e86b4e706a40654b2cdd5e027c022cd9590a508068ccc173db15ee1f4","libgit2/deps/ntlmclient/crypt_mbedtls.h":"d10c8dee39844ad68a6f408d4134300b984279da9dfb9c7ff070a7065e5ecc26","libgit2/deps/ntlmclient/crypt_openssl.c":"528baa27ff9e538a855a812071158ee28d3792565c670d7034dcb05f9d7a67db","libgit2/deps/ntlmclient/crypt_openssl.h":"8c87194b5fd0220f4b40ec5ddaed151c8a189a157ed3b3ab3595f5358cf4a62d","libgit2/deps/ntlmclient/ntlm.c":"adf2d486d8c5a22227e5f1b67da2f81021c2df9c85512c18b67f26121f6c465e","libgit2/deps/ntlmclient/ntlm.h":"5328219af7251999ea78a2d2d648a6d98943af7ace5fbce0d8cb5ad4b0bb4c60","libgit2/deps/ntlmclient/ntlmclient.h":"fa223a164392f53822703c80dfb1299d3483bab3b9ad02fec1058a0f50a55eca","libgit2/deps/ntlmclient/unicode.h":"ddb0cbb25cca1a2f413865070fd47c354f87caf66f868022dd288b90fe93711d","libgit2/deps/ntlmclient/unicode_builtin.c":"639717b403f73b8c16de91456360e78343da62721c242555033f65e36ed3e3ee","libgit2/deps/ntlmclient/unicode_builtin.h":"fb1674d49fc240f33d8120fea10504021a61f69967d933ed386d57d0b267127a","libgit2/deps/ntlmclient/unicode_iconv.c":"9e04ea305d04ac91b4c302ffcb06a4f8b8aa6dd8167a28e3dce6b37c5aa35a01","libgit2/deps/ntlmclient/unicode_iconv.h":"7dd5cfe97dbbbcec94f3e1dcd6edf51785f6eb5510ef96607fce45ce1cc818a3","libgit2/deps/ntlmclient/utf8.h":"8f7396ce0f4e6a4288982bf9a98aced8621bb8997aba77510fe87431de1165b6","libgit2/deps/ntlmclient/util.c":"94ec747cd810c90ccc966688d89ef8dbfb91e41aea2f665a13db5fa18ac26443","libgit2/deps/ntlmclient/util.h":"2eed4228416cd6286500c966ed5fbaa2bba7d548b75dfff254a4e45feda85cdb","libgit2/deps/pcre/CMakeLists.txt":"8dd12e8681df34e4755194e937b1d733380d97ac28b448941c872e71f4959029","libgit2/deps/pcre/COPYING":"17abe1dbb92b21ab173cf9757dd57b0b15cd8d863b2ccdef635fdbef03077fb0","libgit2/deps/pcre/LICENCE":"51b3dea44f63338b84b9c97b3d793826a8397309068cf9379a423216ab8ea5b2","libgit2/deps/pcre/cmake/COPYING-CMAKE-SCRIPTS":"46cde7dc11e64c78d650b4851b88f6704b4665ff60f22a1caf68ceb15e217e5b","libgit2/deps/pcre/cmake/FindEditline.cmake":"44788ac3e7c8c4b4da3e5e0f467c7ee49de7ba9e1c13024dcf1e2501f8fe9684","libgit2/deps/pcre/cmake/FindPackageHandleStandardArgs.cmake":"aa3ef1f1c8742da54813aab0ac58c71edd1e58cd3b6b157b856bfd525adc2e5d","libgit2/deps/pcre/cmake/FindReadline.cmake":"055e1df8bd29e6837d8ebb8c15dd5dcb28c88e23aabda8538b76a249dff829b0","libgit2/deps/pcre/config.h.in":"480e3e1a1eea810516e59f19375575e83f62980d20b9cfc990ecf8e8cb17f79f","libgit2/deps/pcre/pcre.h":"a0cf7c5c7659779e198e31d856fc3d2cbeb73b0caed8d8a95a1b15ce1af28880","libgit2/deps/pcre/pcre_byte_order.c":"4030a1156da8690352226b5de2c9c5f52cc6955409e4a7ab9ba4d6b223e74b3c","libgit2/deps/pcre/pcre_chartables.c":"3386fd60b4a4175a7baf474223522540abd6e006e8507a04d3485f84973424ae","libgit2/deps/pcre/pcre_compile.c":"d0493e6abaa2108a3edf3469052f3f93818699aec21f5ddde03f0e406f2ff9ae","libgit2/deps/pcre/pcre_config.c":"fb9e1e766291b2b4b3066ecfd0795db398762b9597fb2af23cc784617984609e","libgit2/deps/pcre/pcre_dfa_exec.c":"1cbf3a680388110bddff833983d3653630eedf6e6e9d9e656a737e3b85de9dd0","libgit2/deps/pcre/pcre_exec.c":"2cc612c9d8020aae0339f292cf28a268838a9ab90658cdff06b5f3ee914e4fcf","libgit2/deps/pcre/pcre_fullinfo.c":"a84fc4cb4d22b2ddfcd4d0f0a0ff333cc9f623e1fbf2a7c90623a212e0bde54d","libgit2/deps/pcre/pcre_get.c":"c93ded768f96cc392f911776e6d993b565114936e08247dfa79080b35dada4e7","libgit2/deps/pcre/pcre_globals.c":"8b2fda23b42715eff2f01188c4911dd8453868ff77fccfe34b99126aa775fa5d","libgit2/deps/pcre/pcre_internal.h":"f713de1fa2c20b5414f6746c80d10878ab48c9186462b074c5bb6a74a822fa80","libgit2/deps/pcre/pcre_jit_compile.c":"76ed39027b25f2bdab581c0bb12b95fa3659baa53c81e10f8f17819d6255199b","libgit2/deps/pcre/pcre_maketables.c":"8564fab861c7eb4037ffcd53fda789a747a30c4a462f460a5ef824f534fb06dc","libgit2/deps/pcre/pcre_newline.c":"3163ed2193fa74d8cc9e1db2bba672bcebf8efe85c2465cc0aefda51966bd929","libgit2/deps/pcre/pcre_ord2utf8.c":"fa926e32ae8d6e5610c500e7b6971a5149765a1881f798b18904a0a88bf549de","libgit2/deps/pcre/pcre_printint.c":"71751d151efbe935e9d8bbf58bbe143d617b34acd13d1bf237330c7c7736c422","libgit2/deps/pcre/pcre_refcount.c":"0dd8b7273243545fdafab585890fa12e210cf526030eb6b877325ce89250fa39","libgit2/deps/pcre/pcre_string_utils.c":"b83225f9cf2658654d6a0af01199050f6fb36d903041b33cd4d0c06e762b66df","libgit2/deps/pcre/pcre_study.c":"f5285714b59af9503dacff268cb37ab4944fbdf74a78c5a7b9bc49043a00ba40","libgit2/deps/pcre/pcre_tables.c":"fcdd9f705a7a1640ae5e5e5b7148761fc5e82896d84b1a287dd7e55b8b87eb15","libgit2/deps/pcre/pcre_ucd.c":"b35ad7e532f52c5fb1f4ab1d48f5d80618abff2a115099220d95cd13ac64c346","libgit2/deps/pcre/pcre_valid_utf8.c":"4265abf04c03acba9ea90351da662eb89aaed79e7eef03329ccf030ef28ef907","libgit2/deps/pcre/pcre_version.c":"5bb67c3373a934a2d9263ba9f9fe3cd79e381e6aed8507e5319202330bc5275e","libgit2/deps/pcre/pcre_xclass.c":"b2c3b2c7600e18e562a333df017ee35c69233e9427b866726d944122a9560e28","libgit2/deps/pcre/pcreposix.c":"49e996bbf43cb2d4acec350410ab40f811a9fefe21fc68004c6182a8da884aba","libgit2/deps/pcre/pcreposix.h":"85a6a09b806d8506e5710e9862b6716b2b88761abe0d05aeda5071257537d9c3","libgit2/deps/pcre/ucp.h":"ea98e4eb999d8e777f2ba709e68b5aff7108a4eaa169f4eefa4510056551b724","libgit2/deps/winhttp/CMakeLists.txt":"e9012f9e9812df5a744551a9b57982fe8c8ffbdd3ddf41a8d96e58546fcb72c6","libgit2/deps/winhttp/COPYING.GPL":"d9a8038088df84fde493fa33a0f1e537252eeb9642122aa4b862690197152813","libgit2/deps/winhttp/COPYING.LGPL":"dc626520dcd53a22f727af3ee42c770e56c97a64fe3adb063799d8ab032fe551","libgit2/deps/winhttp/urlmon.h":"6cdb0f5ce5f0aea2daefc44c4806697ed31ad759f8baa18fb38a5b220ddc7d7f","libgit2/deps/winhttp/winhttp.def":"89601b95ac2515619426ea52ca093fac1a16ef0bfb5586c1385a028947ab6513","libgit2/deps/winhttp/winhttp.h":"a51828d65b1b260a727f596c41cf3257cac2ef6cfebb11726234fd7ccb1537f6","libgit2/deps/winhttp/winhttp64.def":"7cd0bc8dd2c06d288c241d175da06146e940c28d7201fb63de8a75730d09fcd4","libgit2/deps/xdiff/CMakeLists.txt":"566633d8b9bf4674f8b13605c8c77f42cf0e29e1ec82bede60a1559c092837fe","libgit2/deps/xdiff/git-xdiff.h":"8c39998f8a58de5b9401f3f5b12fc8b4dbc5cb3314a1ee3ce00da3b9339ef723","libgit2/deps/xdiff/xdiff.h":"6459a65d5e0f910f6bdcba30cd5332a58fbb75118acb5757f5596bd22c00a6cb","libgit2/deps/xdiff/xdiffi.c":"89c9015af166c491be55d2ed75de5be2475c3e6c9a657697c838c77da42d8958","libgit2/deps/xdiff/xdiffi.h":"89917373f37fab79ec7da56e6829c2bf05962ab1fb9e9e4f0950adbb31265614","libgit2/deps/xdiff/xemit.c":"e31c12a09dff4a9dca32166d2eee915b80294b408e86012f72f03528f69a4282","libgit2/deps/xdiff/xemit.h":"0a8c569eec74bb4d6e8b7b9a50b457532550800e0566cbba6d185974613a2e6c","libgit2/deps/xdiff/xhistogram.c":"74bc7e7b8926418d20d2772fe8aa5c3f4c525530305c09cfe81712c80ee92890","libgit2/deps/xdiff/xinclude.h":"cd17076d3909d1750044114719daa2acc0ee4b53afd9c2f3864cdba453f5f0fa","libgit2/deps/xdiff/xmacros.h":"f6db27fb8b3c39fbfa1b06bf7a1af45bec4b125614147353fc98798aa89c0bf2","libgit2/deps/xdiff/xmerge.c":"6741b89ba5ad54aaf4c2adecc03f5b7b60d5b9aeff30fc7546be15c26572c252","libgit2/deps/xdiff/xpatience.c":"ff098106ced16724ffe55d4a371af314fedf356576d5ae2c3ed4e617938152ea","libgit2/deps/xdiff/xprepare.c":"44ade0e142a1dc1bd3b9cb25ef4df415213f1f85cf2a43580af25c297bc7c6da","libgit2/deps/xdiff/xprepare.h":"4945e8fffe620cd4d687c12618d1a4b2aa95a7a8097abf3e5e341abf96c76e1b","libgit2/deps/xdiff/xtypes.h":"3336d046bf60e0dc99f3686dfe9ecf098456f0aebe96486be5d1cd64b5d9cefa","libgit2/deps/xdiff/xutils.c":"82ea4546497bd7212ff445537b58a9c64dcae297b839c2fa9b374aa40cd5c862","libgit2/deps/xdiff/xutils.h":"383027bd378a757ec1a96f2825976f817a066b6468774ef703b35c9e1c75ed73","libgit2/deps/zlib/CMakeLists.txt":"bb881a0b24a2800ceb8a624c5165d80e7e8fd451b3bb40f8a73790b188ea6144","libgit2/deps/zlib/LICENSE":"845efc77857d485d91fb3e0b884aaa929368c717ae8186b66fe1ed2495753243","libgit2/deps/zlib/adler32.c":"9cd1443a24ff2a3053961695bd432035c58347386a420d3388232376ebabe211","libgit2/deps/zlib/crc32.c":"8fd16f0a7714d51c89c2eb37eb98ec15e8a4dc57ba343e7b7398b19144039fda","libgit2/deps/zlib/crc32.h":"9a2223575183ac2ee8a247f20bf3ac066e8bd0140369556bdbdffc777435749e","libgit2/deps/zlib/deflate.c":"3b956337350f94c34987750f785587ef33d9c89ceaebb7c2afb189c956360cbe","libgit2/deps/zlib/deflate.h":"48baf016326d8d5e3e32ac8153cc7e22f854b8e6834830b167b998a7fb1e7989","libgit2/deps/zlib/gzguts.h":"716fa648aca1bb06c219d7b97ad4846d8479206143bc39557bfd8283f5783e04","libgit2/deps/zlib/infback.c":"62df9a6dd3eef126f1d81d0ad7a534504610dec44482b0a472b61c93cbab6554","libgit2/deps/zlib/inffast.c":"e6ef64ce5dc0a4cd5c7ad08ceeb2b2a698b8447f6bd156057caeb2edab68c0cb","libgit2/deps/zlib/inffast.h":"05cc5dc9ff1da7b8b52a4bd8bda0d8a5c236a2f39efe84b941516ea13857e6c5","libgit2/deps/zlib/inffixed.h":"237ba710f090e432b62ebf963bee8b302867e9691406b2d3f8ee89ee7bfef9b0","libgit2/deps/zlib/inflate.c":"34c998ce0037c0537c04b03b276f680b945f9b2c9d1e01b287605bd6879f7fd2","libgit2/deps/zlib/inflate.h":"e8d4a51b07694bf48cb91979c19974cf6a5ab0b8a09d26ec0d14df349230673e","libgit2/deps/zlib/inftrees.c":"5d4f335221d2dc76f17abd2577d92c2d7baf68fa6d7f23373b360830493d1563","libgit2/deps/zlib/inftrees.h":"0a0fcaf2ae2fae57426bdc06637270e9bba974f35202cadbdba479d946e6409d","libgit2/deps/zlib/trees.c":"f63c68c16c05fcd196050529d1a0e7657960e4136b9987d90a6ac3e58a964b0f","libgit2/deps/zlib/trees.h":"bb0a9d3ca88ee00c81adb7c636e73b97085f6ef1b52d6d58edbe2b6dc3adeb4d","libgit2/deps/zlib/zconf.h":"f5134250a67d57459234b63858f0d9d3ef8dcc48e9e1028d3f4fdcf6eae677ae","libgit2/deps/zlib/zlib.h":"8a5579af72ea4f427ff00a4150f0ccb3fc5c1e4379f726e101133b1ab9fc600c","libgit2/deps/zlib/zutil.c":"8ced40d8c88588811edd2bdb35b7439983d5e1f8e9e32b8a3b244731f3c317b7","libgit2/deps/zlib/zutil.h":"dddb2dc7a1dc339ecf2c8e089b366f08bb731c0839c7110240d17ce731bb4fea","libgit2/git.git-authors":"807ee76d5d1f87f87bb4deff8196b7854530521ebe52bde5d52b9e2bb82a75e4","libgit2/include/git2.h":"04b93d6a4dac32d661a56c3ca222ba62e8896f8148774ed52565d631ebf8d0e0","libgit2/include/git2/annotated_commit.h":"db6ae80abe996c772311f8f922c95107e5da2d34e3cfb7eb5516614b011d56ee","libgit2/include/git2/apply.h":"ffb13a162ad33bfa60840c4062669d8e752c6eb2f16b02ec5ef01e4a2f3ef8b2","libgit2/include/git2/attr.h":"781cdbcb3e11892c9df464d3750689edf17063136d1e674b8c5dbce22da1097b","libgit2/include/git2/blame.h":"2e7000a338de982a120716175b8e3e5d82270e9d0108d67c3bfb32b8b54c9c3d","libgit2/include/git2/blob.h":"6f8b1da0fcffd5f8a5c40a51704397741b820981d49d363990f0526145ef3992","libgit2/include/git2/branch.h":"b9644e8e34610f59b749091e883360dc33ca83a911fe99bca8200337d4221d05","libgit2/include/git2/buffer.h":"6a137704c0ba3e1fb332a4a432923b1ad32e392c36bfb38141f951b814bd6523","libgit2/include/git2/cert.h":"c41d6db041e351994433588643498bee37ac660f345c7ae1d7dbd7da105f01a9","libgit2/include/git2/checkout.h":"98378b75321206229ca5cf8e171a4ed666e586f4b94e7e8968da57305bb83207","libgit2/include/git2/cherrypick.h":"733fd3e446ca33212921c34c067e489d0ffd91531db8b84e99a5108e726db1c6","libgit2/include/git2/clone.h":"3cf212c27bbdd1680bbf8af8008a2114d9d5e029350c0e0a7e22272a3e9b1814","libgit2/include/git2/commit.h":"561667b10a97bec70c5a267e55692c212c763dec8bd86f8e5a4fafa169b495d9","libgit2/include/git2/common.h":"f96f0ea4a8dd71b8ba25180428fa8080a34284d6a796ab0edb4ae07ef536d2ae","libgit2/include/git2/config.h":"7e239b766b6772de63081dbe20342e916b223ea445f57631fb6e732b714111ba","libgit2/include/git2/cred_helpers.h":"4478aa5e3f82cd754333311939c3c0d5deeff7be636f68e03a7f5b1c5f4bf73b","libgit2/include/git2/credential.h":"9066b84291e63c78bc69ccf46b3197a530bbd7fd82507e8d3d4a70237d56e88f","libgit2/include/git2/credential_helpers.h":"98e54a11f950e0451a692f1a989199a0d6f7de28684fe9a1e6c0dd67561465fb","libgit2/include/git2/deprecated.h":"c77395e0d38172997a15f484c2d00e9ab5a18730ab401ad0e535e85edb5f880f","libgit2/include/git2/describe.h":"2f63fec11bc7f70ed11f7a66eaa1c58e8a2554ca274be5d97727d4fc62e3347b","libgit2/include/git2/diff.h":"4bbcd6c341919c6605c7905be3dacd40da901b8328d0b9e5beeb9acfc9cdf463","libgit2/include/git2/email.h":"1872a47a91d43979c6edc4f17154f30d8e9ed0516a30682718bf15b9845a6e04","libgit2/include/git2/errors.h":"c68d33d2d41bf595ef7544c6dd2af4c5ffccc8fea8b5a7379a3cb084df451ca0","libgit2/include/git2/experimental.h":"11218571e33eaced42fc23331994179c25faaa6bb918a75087afea89d07ae3f0","libgit2/include/git2/filter.h":"920e19f75f4fdcc2eb08601e0611df3b22e701f473d4ad7dc513f60b968a3a69","libgit2/include/git2/global.h":"377aab0df9a552b93c8deca8e7bbc34d14e8662b9b6846107d8c60bf5b136f31","libgit2/include/git2/graph.h":"99a947707841139b90ff0c67668af8dce107e6af8ce985b73b6829b3f9b8884f","libgit2/include/git2/ignore.h":"381176cddebdda0e1568773903f11dd795abd5b7a2cecc8ddccc058d3268488e","libgit2/include/git2/index.h":"58e392fa587b9a68e8c0fdeecb33250aa4e246e7d08be906457e98834848b2d3","libgit2/include/git2/indexer.h":"07a15611b6d3149a81ada7dd45ac9dd1e3866d062a17e557d42c690c058373d7","libgit2/include/git2/mailmap.h":"25df25bf0291ef856c5807f2cd352607e9329b3da2bd80b3fca507967dbce266","libgit2/include/git2/merge.h":"952034c1ede3c0ebbc9ca95123e22f10a63edad2e5f58722d68c4547d93839bc","libgit2/include/git2/message.h":"770606ab6c9fcdc58a863bb541b262c113c3d5b516e13a1225dac1c913c1ca0c","libgit2/include/git2/net.h":"1e7c746f12540b1073f0c0159c12336fa83e532f40a5e2851702d303cbea138b","libgit2/include/git2/notes.h":"76968a7990cc7d85df09e98d5604d64be64d77f399c45067e1f6b9b08dff1518","libgit2/include/git2/object.h":"0547af2fd02aa756e94b3ad682434ddcfe430224b563273be506135b0b1c9107","libgit2/include/git2/odb.h":"49ef671eb0a06b2e421e75d2e952583014925deb0098e170b108a808eed86046","libgit2/include/git2/odb_backend.h":"ce8aa6a659e9b0b3d6cff7042a7ddce1ef1610ee608a671423c60715335ef2f9","libgit2/include/git2/oid.h":"3040665c20c1119635a8ecc528b19342d907adca4b6d5673b2f088e108e218fb","libgit2/include/git2/oidarray.h":"8d8b1f317c7cc78195ffc20d75b92960792ce08286f3cd80db4d1323e9f7a590","libgit2/include/git2/pack.h":"8da6a48d1ed248e725e2ba641d5f0a0c024ce45d5ac8768d9b7967aad8cb3fb3","libgit2/include/git2/patch.h":"6a549b6821dcb147761a1596203c12fdb4abbd6038aee94f8a23525593bc652e","libgit2/include/git2/pathspec.h":"28c479d635aaf55007d026ead6336ef4043c4005d0d2a20eb78b61f27bbc0768","libgit2/include/git2/proxy.h":"960651fc189fa6b5ebede460dd33df12e2072a35edcdb65ff207554194eef3db","libgit2/include/git2/rebase.h":"11eabd700cedbd186453e8e1874d836b85386106fdb614e0c7496a697c73b2a3","libgit2/include/git2/refdb.h":"c2785560d893133745efd344fa853660f0cd0d15e25dda56373d98d0f00fd73e","libgit2/include/git2/reflog.h":"fe4c09ecc76d4515cdfe810e144ae2df6d9d142f37f88e72e6488950b08325f1","libgit2/include/git2/refs.h":"c7e315fcb44045195a0163c7b2644e39a0d8a2901648025317e7e627de199a8e","libgit2/include/git2/refspec.h":"5ee144d4e5a15f0f3f2db0d827f0be5ee77bf20a6aff88a4bd383221d6354473","libgit2/include/git2/remote.h":"c565e65e5477aa9a373783e9ff0a508e46b54811e14f0855da7745fbe3716a07","libgit2/include/git2/repository.h":"947f01e699aad61723a517ef0802bbef6750efaadc71f0db54f14ee6fc2e1e05","libgit2/include/git2/reset.h":"a117417c2d07e9d3325dad337c4463e66db837505a27a35bd606fb8e2bc4ab51","libgit2/include/git2/revert.h":"28b4f1d47c2a59a5190efe742dc5754c3cbe62138dd258412ae99b34dd1551e8","libgit2/include/git2/revparse.h":"8fc12d979a3bf00c3bf736d767921cd78ae8d890bdc603d9bb143c2d8ef62154","libgit2/include/git2/revwalk.h":"d7edf83b9cbfa7ae6dd9c7a2685df718873b99bb1d599749a538dcee818e0027","libgit2/include/git2/signature.h":"07a715960dad62424fcad3b6902b00de237867e52b47b049adc60a2647f09d65","libgit2/include/git2/stash.h":"c9955a519348a335afa2af3b12e5683a35c6f8f40689b3139a25bc1d46ed002f","libgit2/include/git2/status.h":"a7821a0c23b8645e02bf5a98c312e1b180dd69efa41d22561429700fdcc80cc3","libgit2/include/git2/stdint.h":"051a031fc8a0845c7951fa154b7efeb5a6252669856bb33a71d5ceeebb9d5406","libgit2/include/git2/strarray.h":"fee029c468fb4d5b55a2c7664494275a2d9a8dc9d4d0bfde69b8dfbdf86ff1bf","libgit2/include/git2/submodule.h":"3b8d3488a7367dbe310baeaf9536f4adcb94dcfcf53916c72cc79a561d4abfc3","libgit2/include/git2/sys/alloc.h":"fba245b560edc88b371c841a898aa588b043fb9f165b0c71c1355104fd6d2416","libgit2/include/git2/sys/commit.h":"d34bc09c34953177487aa987b11d92ae486d17a6e5ca7caaec935ebf949f6fdd","libgit2/include/git2/sys/commit_graph.h":"602a79d8502f4461e924ee6655e55db01c90919ba3581f30d6d6a1a723f7de34","libgit2/include/git2/sys/config.h":"cc8010d313368f7a5c599b6dea8ab90c8a35e54fa19d8e19d208e8e509922199","libgit2/include/git2/sys/cred.h":"b2ea956401c5554f26cec0f428dd9d0d9661d1ef3d9368769b415193df98ed1a","libgit2/include/git2/sys/credential.h":"c7b9d66f19c7b28134f7c00b1e1c6ac8d0694e2a921d0219492d33c03b8873fd","libgit2/include/git2/sys/diff.h":"b4e9d4e4d47c3071d1d21f8e44613f3c457e3d9d36972d5b19a6df6ed7543d09","libgit2/include/git2/sys/email.h":"505654bfa9d46c8dbdb75631b44fc430808a767529905460311a597912c201f4","libgit2/include/git2/sys/errors.h":"f9f08dcc38782986a8e4078ffa9023d449e4f8fb118ed6847195b4aeea6ae11e","libgit2/include/git2/sys/filter.h":"0508bf325e3035bfa28692231495292999ccb1ad4e3f977726287b615e76ad84","libgit2/include/git2/sys/hashsig.h":"184e8124f282ab80909212ed25c62f04b5547b03c8df15141f999bb052b9f20c","libgit2/include/git2/sys/index.h":"a491832bac3948bfa19962f7bb1bc96e74dd2590a45afd7196284ace94f8727b","libgit2/include/git2/sys/mempack.h":"3f902801f8dfd587abb000cf62b9af76f0b2e8da75dbb6f23660472bbeb8c265","libgit2/include/git2/sys/merge.h":"c901a5bba256e2cc7f4965555e43d3a34adc491d89d95d9ec37fd84c16afa4f5","libgit2/include/git2/sys/midx.h":"63754c3c51a5924bc191c4f02262b0962990610382c5ff741891c58b166efbc1","libgit2/include/git2/sys/odb_backend.h":"29c2aa4efc79d3dd8818dfbfb4b7381556d64a282dac1021c9189af5de709792","libgit2/include/git2/sys/openssl.h":"e94117488e34bf5b0b32ec75c934f5d8fd56ef991a22d221b84c631f9336f85a","libgit2/include/git2/sys/path.h":"ecf909282490bc331e81996f37356f45f99bfb1c255383ce062dfdb961e08ae8","libgit2/include/git2/sys/refdb_backend.h":"c90fa95e6d0fa09c697748d7996c51b3f168719c409bb052c18ff169ce9910a0","libgit2/include/git2/sys/refs.h":"64bf32e8e5174ef4d65050a940b8153e62126d40da59c5b4b6becb0382d85cdf","libgit2/include/git2/sys/remote.h":"cdc4bafbdd64d4fe23be24b54fa12957f48d0d3da092f2e6f8ff9e36c37e3801","libgit2/include/git2/sys/repository.h":"322b056f5d5d259f01c35c6a8455f2898e2f547b8a914c93ca2df032f807ebf4","libgit2/include/git2/sys/stream.h":"f740ce0c770740a66abab39932eb876fb95962d881e3b80ba6e5fbba47dc088d","libgit2/include/git2/sys/transport.h":"1c2eb079df00942b22693cec746bb45636a36e468078f358f5b845a71a1afc16","libgit2/include/git2/tag.h":"a032086d2aa97c02858904e96918cff8a1a47ad7df054e421259f9006830f02f","libgit2/include/git2/trace.h":"c7fee0bc30268081aa1cef033bb38e37179b09eb07afea9fec8e63529ebe85b3","libgit2/include/git2/transaction.h":"55602cc816a57436aba35438a32f4d534d04bb31afdf751cdd5c2db61b1b4644","libgit2/include/git2/transport.h":"a6b3f6b26c0c1640c5e88fd161e5cf4574e9c3cd9b2c22ac61045e2c63faebfe","libgit2/include/git2/tree.h":"129542fdaa60301ced5150afe2b43a4649d654159ba6a152f608d00f852c0d28","libgit2/include/git2/types.h":"48c399475b08a7296b754eaf62664512c425252da8dcc9f9bff9460c4072d989","libgit2/include/git2/version.h":"ebf5569dd3023d8c8e62695881fdfe69f0ca6ba062bc677b997cfcf8ea0d8188","libgit2/include/git2/worktree.h":"b74eae1f0f41140112578dd48602ac5cb73759f0e7847a2a1987665c6d8ea6a0","libgit2/package.json":"4fda7dd425626617c3118e25f02f5a9b9f4be2f17d33cef22cd23d5022f7ae3a","libgit2/script/api-docs/README.md":"9b7048890c4d1204532a4de94c17aa0f661442581aee9de79ad8d5eb99eb1103","libgit2/script/api-docs/api-generator.js":"6bae0247a550ebcb811b2b478f5e4c53bd5e5579adff6f07ab73fea1fc3cf806","libgit2/script/api-docs/docs-generator.js":"38894fc8cc204d974f76ac4b45a852a17bc81558fd84d4c9c8d6269b783f8374","libgit2/script/api-docs/generate":"ecdc102048da88c20b827fb25a0ebc8167e381db01e76b510c9468a59c56d1c9","libgit2/script/api-docs/package-lock.json":"6e50e118eef2d19ebd0cb1086948701d46795f6360dbbd2aa10a6fc1145e1e11","libgit2/script/api-docs/package.json":"82672c5dd592b4106165a2afd1a1e1bfcac10043eeba649368ca613991a8e01a","libgit2/script/api-docs/search-generator.js":"b9536113c7df568c460110284fc2435e091eb73e0e8ed24c141ea5eea6b88a0e","libgit2/script/backport.sh":"6ef93a8c4a15ef74d2639638db3b24d20a76fc31faa53b1c34b07e3759c78fe6","libgit2/script/leaks.sh":"62532838555750cfdbff91709c40b1c1356e399238fc29379a45802922530a51","libgit2/script/release.py":"28a113a377422d7aebab4ce25c672f134ccb8a81cbbcece4d6af4354c44c4711","libgit2/script/sanitizers.supp":"8ac23fc907490c5ba1dd641f97201878e195c3bec0c1f224a9a4dda1fbd1f5b9","libgit2/script/thread-sanitizer.supp":"6497c98a2c0c83d867b0d88c36095a609731e7f6f4ad5a73dfe5287d6f2ba0d2","libgit2/script/user_model.c":"073e0b631f2d50af9c326dc009c626dfb9d31707d36bc61396ad9960160652bf","libgit2/script/user_nodefs.h":"4287333a6d7484a5a5796e6deadea53ec1ef587e4c571351e3fab61cf8badb45","libgit2/script/valgrind.sh":"beb7ccea1140bd25684216aefcb7ab44ff42fa9a4d0376ec18f47060d84688c5","libgit2/script/valgrind.supp":"03369931efdcbf06380b0deebba763014f2b0d8296325c07442b44dac6a0c7e7","libgit2/src/CMakeLists.txt":"7db897128511c4646ccd2e7e657e000483d506d7c0806233d66ba5007cfce7ea","libgit2/src/README.md":"acfbe0a4ed6cd7d806b30582cde06b871fe2b16f5a9eec3af717ff819030c6d3","libgit2/src/cli/CMakeLists.txt":"87d87cb6440f0efe0341d16dedb754a460364a8715f0c40e8594ffc2ac9c06fc","libgit2/src/cli/README.md":"84ea472e6be8c7e14558749a6b9207b8acf45ce78ce2278a525813d6c66c09d8","libgit2/src/cli/cmd.c":"1b7ec8535865b00b3fa705c9afc141f013696da493c63bce065a8070572b7d56","libgit2/src/cli/cmd.h":"3167f21423f29220c77e754509c45697cf14683b78a9686e61d18a5848852d93","libgit2/src/cli/cmd_blame.c":"bf413b163796058d86ec3ddf9a416bedaf6beb913a57e6748727b54668aa8f2d","libgit2/src/cli/cmd_cat_file.c":"9dede87d869a3cab92ad6ec80131c7c1949479bf20e4990aa72808e065553602","libgit2/src/cli/cmd_clone.c":"70b26aeb066f5e45f475fbdca2658dca59755cba7b960547712c058f79339a92","libgit2/src/cli/cmd_config.c":"143388bfcc4e0daba623f74fda0fa340d78b43fd2eb9e862fd3ef1cc5fa72a27","libgit2/src/cli/cmd_hash_object.c":"c001187712655693fb3eca074e1e62cb2871c872087bebc8e47ee25abcc3235e","libgit2/src/cli/cmd_help.c":"686da2d4e168a84d69c8a252a5cb48b7376fd26c1d4b979bc687a38dd60892e1","libgit2/src/cli/cmd_index_pack.c":"3285f0fabf2c136eca8b1d3d25efe8ae12e5b39da6953379a229d3dee3bff473","libgit2/src/cli/cmd_init.c":"3ae629e46362a9e1e5ce18661e14752bf7a0d8277c002991a1d7e76d15090c1e","libgit2/src/cli/common.c":"82a815a6fad27a231c8f7370d84c94ba4dbb6287b601a1ceb077ee80e9861560","libgit2/src/cli/common.h":"126bd9b643f68209cfa0fc0c77125bf81a14ba18e677d6fca604d0458b1c54b5","libgit2/src/cli/error.h":"ebd8eadd5527dc381acbd08c45aab5a21e2d4d2beff0a4baf462389b1cb0aeab","libgit2/src/cli/main.c":"a66f33d50fe852e901080476e79a42c29cf9d0eda9238c20f417e9054448ef5a","libgit2/src/cli/opt.c":"025483e0666aaa8158241ee0b4c06b632e3d10e2e60ccd4e0cf137629fe3b53d","libgit2/src/cli/opt.h":"f56c0a3e1b624b695083a3b88b8cba2f2c0c80871bd447cff1bb18ce4af14d4d","libgit2/src/cli/opt_usage.c":"f2065d08e19f46aad0b9ba8eb336297fbddd89b7bd3ca990d565fe92925815b7","libgit2/src/cli/opt_usage.h":"7369f7f69e9ada3da113d861ed000d235adb3d2d335284bdc9a6036fd51ed726","libgit2/src/cli/progress.c":"29944809d3c1ec31bd0891ac6b15697d227ebaf371099b07736f52e3f5b5a59e","libgit2/src/cli/progress.h":"c3027b4e1e4ca439009e2b692ad5702966de62c6a84a58ccb9844ea22b4496d5","libgit2/src/cli/sighandler.h":"4ebd5db9e9368f8001bffd2e43f8cd7e2ba7428d0a56eea8255c1d1252c50d88","libgit2/src/cli/unix/sighandler.c":"76509f4c00d99fdb9fdc275a5be2ab0e45964340a865d723a7359e094df9b674","libgit2/src/cli/win32/precompiled.c":"4dff04101bd64b95c8f708d1accd1bedc39e95a263444290f796c63f4734d4cd","libgit2/src/cli/win32/precompiled.h":"0e580ecd400170374b474681c84817c8d70e416fb97f22d027be1d9698a63341","libgit2/src/cli/win32/sighandler.c":"56027bdf1bb5232f9bcb619ebfa56bcf3477122a86e592eabb8c0f1503ea93dd","libgit2/src/libgit2/CMakeLists.txt":"179602152cb83f3460eafb13e8e29bc8850346add521db11d60b0903f845f424","libgit2/src/libgit2/annotated_commit.c":"9672606a57bd3ba7e94872182187d0850a7e298528765b493358ae9f40c3af70","libgit2/src/libgit2/annotated_commit.h":"fa0d7abeb786002f70a764b0f08efbcd4b4d6133ed7d0e184d39c60a93df0e62","libgit2/src/libgit2/apply.c":"05adc90b841a2d440bb081dbab5485631d8dab994ac634bec4bc229a7d30c282","libgit2/src/libgit2/apply.h":"4b3cccfd8030ab006fe78a89bd6ded5e1d89f7122630da6efa792c1a5b6874ae","libgit2/src/libgit2/attr.c":"32969883dc55dbb71b08c75231c7266e75f24245cfd8b5b10dbcc5f11cf2e455","libgit2/src/libgit2/attr.h":"c940426d88f00d1510d2698897d5fd1b9270d91ec0c86a7df10b9d07f598171e","libgit2/src/libgit2/attr_file.c":"2011d17d836f990158c9e8e67a47823e69f2eab336fc3aab4a43f44dd2419d91","libgit2/src/libgit2/attr_file.h":"9240f1de4e650472dd3389eaef2e2b28e84041fe33d96dc4cfc8a4763406bd94","libgit2/src/libgit2/attrcache.c":"38f1c7dd23ca0caff8d513309504962d2d7dd49d3a86403e9b76c0be4d262b5d","libgit2/src/libgit2/attrcache.h":"71ece32af260911e20212d046b198af3a02bc68897c0b98d5ed9421f0f90e12c","libgit2/src/libgit2/blame.c":"d11bb0c374424ac31d6c24bf148185341631af219c482c195e2231f89ba491c6","libgit2/src/libgit2/blame.h":"f4bc967c877560bb56a5f297454c7f3f60437cabfd430fedcbb9993ee3e6494e","libgit2/src/libgit2/blame_git.c":"eb0045fb97386393986037a8e4a6ee1840ddc1da915e807e32666c981f0f2e18","libgit2/src/libgit2/blame_git.h":"9b813f16b93512d27d93648e53d1438d5b931024701273b5976c1da41868e286","libgit2/src/libgit2/blob.c":"5dba150f29f75e92dd68ac92736342fe751b5e0d2fcd724523a93dbf5561ddf8","libgit2/src/libgit2/blob.h":"0d8836e7d5d286e1f5c37e342ae83b4543930c4c07de652e8eaf431e4c760ae1","libgit2/src/libgit2/branch.c":"e615d2eb9a2853bf496204aacf1fe8d89f1dea31d328a5a0342a9e0aacbc2a8d","libgit2/src/libgit2/branch.h":"a7512fb6c578721a8d0c47a250fee9ddbbd08fa5c53460420d8158d39511e042","libgit2/src/libgit2/buf.c":"491511777e2c68cbbb6e350e469da80fd240286d71b953230e688b920474ae27","libgit2/src/libgit2/buf.h":"4ed1bd58f01790c6b8505b9ce3f1cca443e8952c64611a36516d9c5d1cb96ac2","libgit2/src/libgit2/cache.c":"f1b58f140ae2cdcc6b3172591a6fd1731b22ba5561d5f651cc6daa2297bcad03","libgit2/src/libgit2/cache.h":"14ef07936091acd0c058ace1ae7a9c0d69b262239a9932432bb36b97ad44ca5d","libgit2/src/libgit2/checkout.c":"8fc768f21766267523b0ad6d09a73e256878ab9118c9c7e3c21d8e2fab83adc7","libgit2/src/libgit2/checkout.h":"86cd4012dc0bf97df74fe49140ac2b686e472d5a930ae69d537b23cbfd2f1b3e","libgit2/src/libgit2/cherrypick.c":"d8370d2ff8abe50b9a4083079eb47955787c35bd62d00fb598736610897aed32","libgit2/src/libgit2/clone.c":"8d7d12fea4ab54c2d24da94e00bd63bb140160125b4e6154c43a957e6639811e","libgit2/src/libgit2/clone.h":"780109b39b33c44122aed97d58c4d86f85b136e6c865b2e82c9601e308ed5533","libgit2/src/libgit2/commit.c":"ab095f9ffddaf8036df852c523893a86eea9e0ee1fd28e22ce7cbff9f9373a18","libgit2/src/libgit2/commit.h":"1cd14919ffd28c7625fd0cff5a8e7d9250c5ff74370393484c92615aeadfd497","libgit2/src/libgit2/commit_graph.c":"93c808473a199cd9c45b912b46af091eb3913d877a7d310005edf177c145d4a0","libgit2/src/libgit2/commit_graph.h":"6911f4c532336cfce7dcbd359a85ba3d22a0a3e68c66d6e6e08088f784dcf079","libgit2/src/libgit2/commit_list.c":"398a42dec95f6257d3cb8d06fe93986a162a2fdc184d1592c6ae9159b3d1329d","libgit2/src/libgit2/commit_list.h":"cc616cd4e8206a95c6368d7c4f317855b09dfe324638f999e8f2ecb6db3c720a","libgit2/src/libgit2/common.h":"6c3fb69d1dd0a3656b76dccbae2f2ae11d67675c3695cf0de4521daa422c28ae","libgit2/src/libgit2/config.c":"3bd7e316d37585238c806d26430fd54cf5f0cff50720b418d1c526e0fca0c071","libgit2/src/libgit2/config.cmake.in":"02042970005b621d49b62945230242bb46eb56502fa948806287baa19f70b98b","libgit2/src/libgit2/config.h":"3322700eebb0ff80f7d81e84894ea50f47f517de9826e784dd48e7d89481134b","libgit2/src/libgit2/config_backend.h":"5573f855a3cfd354538bf117465e37d167fee4e5df35ec4cb091e675732026ef","libgit2/src/libgit2/config_cache.c":"628d762865d701eeb9658706b5414be2366155812a60cad1da5a888ec44125d5","libgit2/src/libgit2/config_file.c":"df8498bfa7de04f7a76b6be8e5f007b1826f3d9870f7749ecf70f85028b6e216","libgit2/src/libgit2/config_list.c":"7d2036e72f515235f62091c6acce48351c59c88da4375de59f043818c8d99e98","libgit2/src/libgit2/config_list.h":"ef65fd05e8ebd3e52a1559cbe2354af4b0ed15355152678c60dd751d5be9c247","libgit2/src/libgit2/config_mem.c":"58fb549a3064776d40e4765d44fa5149d7f1d6bb3645b7a2fa40ed5296d6cf61","libgit2/src/libgit2/config_parse.c":"3f0923957f5f1b01f3d90244792d8eadbe2d2c4ef5ec1c2154f1745c7b6b6352","libgit2/src/libgit2/config_parse.h":"ed2e0138e13fc0115fba047c80687eb20778984bb2ee13c261ae69893e11e5a9","libgit2/src/libgit2/config_snapshot.c":"80eb0c23f4b8b0fc09bd7eec351f8099293c53c1b4f44fd81098b17da648931f","libgit2/src/libgit2/crlf.c":"2b5a392b269eb2af132f790a7764271dfd8523c47d18ac0f2d9fca1bd631363e","libgit2/src/libgit2/delta.c":"3e5d84b81f8fa3b752f75e0853a548ec3863ac3f1070bf99a9367bf6ae8ab87c","libgit2/src/libgit2/delta.h":"c757526292144083a0e96e7ab259080e83158e28c4819c4fa8bc2523aec12a59","libgit2/src/libgit2/describe.c":"f1a16c9c2e5f74752e1526a41f42c78ee4904b62245e0380a9d36e04d4dfcf56","libgit2/src/libgit2/diff.c":"41cb8c28752d133277f2448d7ac3968690cae653f93e7fd6cc173b3424682edf","libgit2/src/libgit2/diff.h":"6756796efbe24bc502dd337a9bb64de256d24a07e814254cf87808a753cc1cd5","libgit2/src/libgit2/diff_driver.c":"330b8718034a6a8fffae4ace4a4988a57b630474a208a08be57913acd6c50761","libgit2/src/libgit2/diff_driver.h":"f628f60d2679dd45dc7270fc0e8fcb271b4286cc1b13dcd0eab80df1d98be1b0","libgit2/src/libgit2/diff_file.c":"9d6033a915d9c4050d19f3f6d0f1e34e93650f00908cc247c0d2c512d506a248","libgit2/src/libgit2/diff_file.h":"f19e2a17d089591596325cfede708846c8df05a711e349ff84394db4af560c1c","libgit2/src/libgit2/diff_generate.c":"9a6fb642c4d624bb8d91ac2ed62ebe4becfb1015ee6783e32472aed53ca080c2","libgit2/src/libgit2/diff_generate.h":"a856792a6febd862984ac01bd41f998a848a60cd5d72b4c04a94099d3ffc5c6b","libgit2/src/libgit2/diff_parse.c":"5d7801826eb978de9f668a0350c783bedc408b2eb8b0a0176fc66d5d8d8bd062","libgit2/src/libgit2/diff_parse.h":"8902d9ba9102f10898c14d0a41a8d5823450527266908cbbb06e676309e23f56","libgit2/src/libgit2/diff_print.c":"0ffa654c7544f160c0887078091a5f2f255b8a9840be1df99a5009305cc2a0f0","libgit2/src/libgit2/diff_stats.c":"63e5e55d9f59992927409b3fafbb2c203a4098e84b0b747617e1a53a327384ba","libgit2/src/libgit2/diff_stats.h":"4ce359d523ffa03df7c0592ec0b905dd693054b44f62c4d1a91af464ace119dd","libgit2/src/libgit2/diff_tform.c":"dc65829e9abfccbaabedddf04ce84e42e34cf7968c8bde42e137fa3b7752e67e","libgit2/src/libgit2/diff_tform.h":"a4a7433036cefffaa9d968ff45244afb4957313cd9c28980cc102f172f9eaf5a","libgit2/src/libgit2/diff_xdiff.c":"6980509ccc58b82b36c0ea42e319c402a19002bbe59c2c4ad5e7accec4cfec44","libgit2/src/libgit2/diff_xdiff.h":"90cd3845e8da92354752904547ec9e53cee328f9cb2fb43e554e7d7047f6e182","libgit2/src/libgit2/email.c":"faf57f6ee54c8d27463a0a01703f2e4f746b56d3851d38e897ed5b06d3d134f3","libgit2/src/libgit2/email.h":"6b4a9dfb85b367e3423c2967f9cc257cf498fb9dd973c4400cea2be183065d2b","libgit2/src/libgit2/experimental.h.in":"9675b72f4b942cb66c2d1bb2c39e2d8f3c2c7ec562c456c5fa3ffc3119e0d900","libgit2/src/libgit2/fetch.c":"151943586ce55f9d0e654220d6b7cf07f940cf5961c21d4543ba7f6531f0b9e0","libgit2/src/libgit2/fetch.h":"e133482c55b6245e18cb64cf9b4f0afd308202c4a56e315631ba2a2cbc7f9f03","libgit2/src/libgit2/fetchhead.c":"ae489ea30536a2fb55dd24e69c3d0f4488d8967f783c0997e8b0db7377171122","libgit2/src/libgit2/fetchhead.h":"65681769629f670afcf739d582bb7ae90f702f0c6085de3f26b79fb3293d1dc7","libgit2/src/libgit2/filter.c":"703a2fed473ffc81b8a1c1f99ef294a40068bd9342c74190a25b31d6404d4e73","libgit2/src/libgit2/filter.h":"b716cb38e4af7b9adca4e9b1538eb546ebf15f279906561fa8fa504bcc62ed24","libgit2/src/libgit2/git2.rc":"28d3ec420d917a93ec62ec83f5fb8a4bfa988c4a42409ce9a2fc4aa241029fd5","libgit2/src/libgit2/grafts.c":"e2d6347db7e82258f50594e727e8855df5c5e5d2cafdce2c6b5fafffe99bcc71","libgit2/src/libgit2/grafts.h":"d77692a9dfddeee0754e80add4a7f435a20f75dfe75a03e40e584731ad01e2e9","libgit2/src/libgit2/graph.c":"929811ed381a0ef69f1181b09a61b8e08398c49bc16b8bbdb926585a675fe384","libgit2/src/libgit2/hashmap_oid.h":"a297bf7971f2461c09853775a1bd5320be7b4c228ca8c480e9e7eaa242c7666e","libgit2/src/libgit2/hashsig.c":"fbe29bf41024a9bf9b556756a23bea451d1ca2aced680fe666d24b651a719a7a","libgit2/src/libgit2/ident.c":"5c14c2e3bc8899244018645089cc2ee0e3786bd538434cffebc25532500eecee","libgit2/src/libgit2/ignore.c":"0e5be19897672f9815879010a3331e6a3f0cbdc2261a0c7cc9953c6f88ff81a6","libgit2/src/libgit2/ignore.h":"131aa84f9e474111db0f03797969e99493e67ef287cf8801606a889765e96ece","libgit2/src/libgit2/index.c":"a524e6d8c90522b1e75e11b9d7f914d66c0ac090e2a54684db3c2686581ca730","libgit2/src/libgit2/index.h":"a8769ffb64f8347f6a33c2a5679ffadeb95fc85df2403d4151fb313e4995c520","libgit2/src/libgit2/index_map.c":"9c47ad9f6b75812ec18b96a37c3dad104c4abde464236cb0310526b73cba2201","libgit2/src/libgit2/index_map.h":"799ef3dc0f4c99b8f126397481f6ba0b11904d9f25b6f6cf62826f97a8eb80b7","libgit2/src/libgit2/indexer.c":"fdc1463b01b76f7d594ae0cf6a5383296ee3158b653e9da71b46aa73e00ed8e7","libgit2/src/libgit2/indexer.h":"bd32fd65a3a7c6014e3e9846477b060033102c8c7baeb097506074f99c50a434","libgit2/src/libgit2/iterator.c":"5f579f125f16242c9dda2a28110760eb6dc6df424fcdc73a88442cb1b340f7a6","libgit2/src/libgit2/iterator.h":"5b2e297b9746a37110b2113a878564e3d46705735e500ad7e77bdca9541e85ff","libgit2/src/libgit2/libgit2.c":"a633bc64d5788f829d7f248bc1523781aa788bad1883e76b9bd4c807d2fc413f","libgit2/src/libgit2/mailmap.c":"fd2035a692293ca3111f115cc6dfa0974d7a26139790c3103d244d146cd62585","libgit2/src/libgit2/mailmap.h":"edd8723b4c861c855708f2d7f9ed5e37cbb7523534349b0f69258a3557b93b36","libgit2/src/libgit2/merge.c":"8775ea5fe68db1824ce0f04a516760d6d44c438f680b1057c66a98ab174b2285","libgit2/src/libgit2/merge.h":"ee2d6ebc3328e33115a69f60f0323be4fcf7a586a6308d76b5d6b6451a2e06dc","libgit2/src/libgit2/merge_driver.c":"42e8d0bac2100b9dd3b8f73ef2118db360f3680e884f8594f37af25471e37fb5","libgit2/src/libgit2/merge_driver.h":"4f56c2965330a5cb08b9f26d6b83dc4e15fced9419540d2d793f2e75049bc121","libgit2/src/libgit2/merge_file.c":"11e61c5320eec5290d76efc3b808e08de722cdff6f12612ca76983bbc6f8848b","libgit2/src/libgit2/message.c":"12b4d518b4f77b20d76e6b7062f56ddc7a7a666ee97f7f33909c9010ca3b5b0f","libgit2/src/libgit2/midx.c":"ad3c612a22ef17a4efd58d52e98ba5489afa6ce41c87f727e46a4e0c4e6a4869","libgit2/src/libgit2/midx.h":"e120c7c79d150095fc32add058bc82e3a7403cfaa84ca4d32994031d353194a5","libgit2/src/libgit2/mwindow.c":"4ca1ada058b4b1e7cee34ded71bf9ce2fbc9f087d7c65dde448ee03ee2a22191","libgit2/src/libgit2/mwindow.h":"2f11d808807b9eabf99f8f70309f51b216f1b127d4f6559b238bab20541ed8a4","libgit2/src/libgit2/notes.c":"974285fadfff6caa5bd79a903689853de9cd2bbc3ab55ecce74bb5c845a6c38a","libgit2/src/libgit2/notes.h":"650f92bbf875ab194b9e1d041f06fb8332bf5f402ea49cde528a81d2eac05694","libgit2/src/libgit2/object.c":"13de0bc0d5bf6ba0bd3c0a354e51b1e26a51824865b0b01551412af46db19be5","libgit2/src/libgit2/object.h":"b0b160cf112a26d4edafd937ba016e8a54f5654184b622e5c6b0a42e7ef461fa","libgit2/src/libgit2/object_api.c":"ac963762a903ea36adb20f0d4317388159c23828a6e26e9a1b1333b7a9fa1317","libgit2/src/libgit2/odb.c":"fbce4561920638766219299ae55167805f1d7d496bacf6032f643647ceada77e","libgit2/src/libgit2/odb.h":"505d85dd41198f41da28cad298eb880e4a671d281756de0d8205b53ab40f1106","libgit2/src/libgit2/odb_loose.c":"01e99c92d96eee62de939acd4c8027df7e574b0fe6a6bab09e8befa12f6e27b8","libgit2/src/libgit2/odb_mempack.c":"f5ecbf377f03b7cce05912ef6704739ce080b896b7f6c6fe2059c46aad96bd1c","libgit2/src/libgit2/odb_pack.c":"c66bf4e1d9d5be5608d0076853c3a8826843f56aa69c63f0c34108e6970f4d69","libgit2/src/libgit2/oid.c":"5a7db6c5b20fdc47ae1016052abf8bd858ebd11df8aab47f0cb2143a905c69ba","libgit2/src/libgit2/oid.h":"ab449b781a4c818a38325729780263c70359ce4b8eecd13ccf3d21e21eb60005","libgit2/src/libgit2/oidarray.c":"97eb0acabdaded7927caaa15b4e46571a8ed5e76423a5e6f9a6134f3fae4a86d","libgit2/src/libgit2/oidarray.h":"c109a93dab1485df8adc272db24be0bb6e07f849002dce2c55ce3e03ea138c76","libgit2/src/libgit2/pack-objects.c":"69012808e14777b49bc7d29e4b3a3f5c86df87c29b010ec645da40f3f318dd25","libgit2/src/libgit2/pack-objects.h":"9a833982603d33402224574dae2d840b5d2ef8d56b878d9090ce3e7f6721dfd5","libgit2/src/libgit2/pack.c":"ab8d2431d1e531d9ef848f845b5bacaf1da039f8893f80a191b782380ecef550","libgit2/src/libgit2/pack.h":"fa11f85657130b655b94ae6bb00811c1a5aa0162deafc4de3eb887f7df380e43","libgit2/src/libgit2/parse.c":"ae1738e409c0844d74cd054623195727a725505de8e3284d12f3d3c9e98f3f0d","libgit2/src/libgit2/parse.h":"45b8d736b9b103a6971b3dc3d95e0300a0384c9a62a6916000e1cc149b5294d1","libgit2/src/libgit2/patch.c":"48eceeb2b8fd25f24f830f7abda25853f4ede673b38698b671a9aac8d84562ad","libgit2/src/libgit2/patch.h":"05d7a5336612380fb4850aea817750a163fdf9dfbf5845cb81e9e2246d2619fc","libgit2/src/libgit2/patch_generate.c":"b64154b5b9b0ea69c90e119a47a24c18122055c53926eb6b437f9b59d383d450","libgit2/src/libgit2/patch_generate.h":"c83a9d70e860b13940f70140199ee40e8cad310350a764e726672ed7133e2999","libgit2/src/libgit2/patch_parse.c":"b068961f082fc5906c6658171f37b895e55c429594e58dfddf28072278fe9bf2","libgit2/src/libgit2/patch_parse.h":"6883a184830f4c59427177eba5462d27737706a1be899db35423c5ad9d6c8c4a","libgit2/src/libgit2/path.c":"c009cf85bbc8ca3ffdeb5cdc62362e368ef211612ad550bdc6dceabe988d6f16","libgit2/src/libgit2/path.h":"e5a2b8a9f717ebeafde09b3fb0330eac85c9d52213f334fedebaffefbe3575cb","libgit2/src/libgit2/pathspec.c":"b3e851575462400cc55e2d0e911c88f8e60c7c7b696920015c57329bb76d1d93","libgit2/src/libgit2/pathspec.h":"ab2ba851d4cd815aa25a910b792102230224ef7bb079879277d77f2984fe64cd","libgit2/src/libgit2/proxy.c":"2e979107904bc593f4d0a4a911a20efbf0956ab3441e21b10eff8661c8c7fc2a","libgit2/src/libgit2/proxy.h":"3758a64bea3691eb59a3c4d0bc70f7e1660b2a7f52e33513b03e9d7dc02bb617","libgit2/src/libgit2/push.c":"0c592ecedb5bc419df01e3650e4719c608f4d61321ae40ae845d6f204da22edd","libgit2/src/libgit2/push.h":"e7e9b90154bdfc624cd43e41f955ad4824567d5bf2b9589dd052f181f23d350d","libgit2/src/libgit2/reader.c":"d3d41922e7935be996d33ade27b471512a9e35f3f0468c6dfe5a6fe679751e9b","libgit2/src/libgit2/reader.h":"de0410d529d888feb13a8db4221e4d73f3575e3c815e6bb4ab9a4caf4d822e32","libgit2/src/libgit2/rebase.c":"1afe5afc9b1f9e4508109422a441ac970903c82a9ca708d297536e4467dbff63","libgit2/src/libgit2/refdb.c":"40cd4c55b0d0296bfa674541fb16936cb7c5a785be9afdbfca5e96b54adb7424","libgit2/src/libgit2/refdb.h":"ad8127049a89e43f49ffed64e82ee343e7c8acee31f7cb900b555193667cb99a","libgit2/src/libgit2/refdb_fs.c":"6ef130be842461dca3acf9082c408de3144ebe7cfa041b7de18af3d2914a2a81","libgit2/src/libgit2/reflog.c":"30b898d5c35265d1624ce947a27ad956fa6406e8446f26186d061af522157b12","libgit2/src/libgit2/reflog.h":"cb143a995aeefee11d3da15b4e6547f269d671645fd9b1c786abb8fb2276119d","libgit2/src/libgit2/refs.c":"e394dbdbef8a494a442dd1867e1e2dd53fe682988a02ac77edfd058a96af8606","libgit2/src/libgit2/refs.h":"a6fa957bcbf3116bbba96259db1fcd7c6dabd3cd0f556f723e8ddd77d01b02a0","libgit2/src/libgit2/refspec.c":"72d5238dd530fd0a77d7989ff80e465a2e2bc3a2347d0447ad77d1619d8a81b7","libgit2/src/libgit2/refspec.h":"80c4e67c0a3078ac8902ed294327f37948a10af7123d397c90bc58f3d98205af","libgit2/src/libgit2/remote.c":"39551c4a4edb9b42ff63c3e2973a7dae8fd4866b6fd2af17874e1baea9debf41","libgit2/src/libgit2/remote.h":"0746f3f51138a42234eec42ae5cfa417c05b641c514103b407fe602e2837d7b4","libgit2/src/libgit2/repo_template.h":"bec227c595d193802723f81765487da76beaffafae40ec05b76a4db7de153c01","libgit2/src/libgit2/repository.c":"2d7ce5f5914816dd369c5336ee0e19fb11d7a0b9c3223696006344b26e133852","libgit2/src/libgit2/repository.h":"bb743b720936fe2c0e6b4e7f76e23e86df97b5629f9293c4a8a8f5954bd4c63d","libgit2/src/libgit2/reset.c":"415f1163f1348ac033cdd45feb27097c8a698047b678b94ff1656899248948b8","libgit2/src/libgit2/revert.c":"5fb9db8b9d79a21a260967215ed02c88b49897db88cdeaef3dc292b72ae643f1","libgit2/src/libgit2/revparse.c":"0768bb468d0f39247df5fb269bcd80eff5d8818cd9efa999e8bc03d982ae8bd0","libgit2/src/libgit2/revwalk.c":"9b264bf60344cf5b5386e17cc1262b2088518f5e0baba2bcd6a22a92bc6fde4e","libgit2/src/libgit2/revwalk.h":"b79fb6a2b90b708cc8f4f951e7128eae01cb30d91c84ebc0b5539958c1e20ddc","libgit2/src/libgit2/settings.c":"ced3f9b63888f2ae93fa226ec795f2958ef9f1069658849f90984076ddfcddf5","libgit2/src/libgit2/settings.h":"be6c7b556e30b676f45f6653e1b0311958ce58f03f54cab39ba2b0c30838be54","libgit2/src/libgit2/signature.c":"188116fbc3b74e206516525d39ded34bacb4a58e3b7fa107072f1ac58f2e4342","libgit2/src/libgit2/signature.h":"612835d2c5067740534d2c26664b8e38fcae2db3b4d7c584c248bedb8bca399d","libgit2/src/libgit2/stash.c":"00bb03f18903f844618ce2a7bf8e3eb82374d387919af9994e0d3c5f381d92c3","libgit2/src/libgit2/status.c":"b864b001ad0ab0057f90cea2f0d947555700580e826eccb910a2b889a8555fd0","libgit2/src/libgit2/status.h":"68ed612f65430563ad5b3f50973b360f583c5865e54c9b9298eb082d441c4e79","libgit2/src/libgit2/strarray.c":"94987bed6dad5529d2020d2be626b3c99cc93df1b71e081388eeb267518716b7","libgit2/src/libgit2/strarray.h":"a406e07d6100e223cf1faa06ec8cb1e451bba75bfb081cc4d2fc737a6d35243a","libgit2/src/libgit2/stream.h":"a7755d211e19ea4a29d7d6151f71efd92bb92906d1668960e076ced663961784","libgit2/src/libgit2/streams/mbedtls.c":"940f300bbcde4f133cdb6185d793f39060f50db772ac4f6d0e273e44684cbd54","libgit2/src/libgit2/streams/mbedtls.h":"4aab96dac336790b4b172a7f355eb3fa6db35d31765edfa6524c581c10896ec3","libgit2/src/libgit2/streams/openssl.c":"9f123ff490757e23cdc5b93119d79507ed90edb64ce9257635ad69676c0df4b7","libgit2/src/libgit2/streams/openssl.h":"39379d387e6b07bb83f6ec5602da4ca7bb7218c85c1f18ce61ed3353ce3c4d7c","libgit2/src/libgit2/streams/openssl_dynamic.c":"3ea7fbd6f938f63a08563d953deb2a61c1fc1afca0dfffdf7f470b478b112eb4","libgit2/src/libgit2/streams/openssl_dynamic.h":"51b1485e838f06146553dd7894c90d9be6d80211ee29a72ed018553ce10b96e5","libgit2/src/libgit2/streams/openssl_legacy.c":"1c0643b0ea8cdd9d2f37dda38d777dc6aa6d3af8c631671f413da53b62470066","libgit2/src/libgit2/streams/openssl_legacy.h":"5a0f3da348dd5fb0cf4ddf7b9f65234bb5cf7017dfb084e5aad48e8d33d818a3","libgit2/src/libgit2/streams/registry.c":"079e2c8807d0cab10ab4363599e8a82e31042ba5ba2802cb52cdca0afad8222c","libgit2/src/libgit2/streams/registry.h":"42a887dd1fff029efa00a04a4e8716905149d3639a6881dc53254170cb8e18be","libgit2/src/libgit2/streams/schannel.c":"5bb78d33ef0be34431bf525384b7a7c0ebab3b7a13a68023ff6b83534e68ce5c","libgit2/src/libgit2/streams/schannel.h":"34eb7f2a0f56c80fef790cad5d50916cf9222494ccd7ccb129132cbbb9a66aa7","libgit2/src/libgit2/streams/socket.c":"5e595d05c71967de79b9e516ec9664e10e51067b89c9a7478253d61084f2fc16","libgit2/src/libgit2/streams/socket.h":"59cd164dec6960d8af55859b0d1504b29c2b29f489618fb57f81e57ed3de9ad2","libgit2/src/libgit2/streams/stransport.c":"7c2d6640ac8e16db85f1ad2e048fc66578a9451a66d4740007dbcec95fe28be5","libgit2/src/libgit2/streams/stransport.h":"01c4555417713c415de10ea16222d44cd9c0c0db4ad5d7e3d5e9863d62f49eb4","libgit2/src/libgit2/streams/tls.c":"3da24376a8e7e077e8aeefbb5c13e9e75444334f802185b3d2dfb3eb7d6be47b","libgit2/src/libgit2/streams/tls.h":"c989f0a996ba7a11f8732336da77266905ebbc371e5e5969955d096ae16b64ad","libgit2/src/libgit2/submodule.c":"6a54c3b701cf0cd28bff1abf2d7fcc074351e72254a0b519d5ff80af3cc4c4a7","libgit2/src/libgit2/submodule.h":"1d4bab3a0268fe2bbbb6fd4e6524727f2868923d3ce8341b3486a0b32707483b","libgit2/src/libgit2/sysdir.c":"bc7368efe6db818f64a8d8b4178455da7b8f8e28b157dc99ed95b094d3e0170f","libgit2/src/libgit2/sysdir.h":"a46088fb2ab6cfa011708576800e982123e9dcf6dbcffbae1ea1cb578ab0c8c5","libgit2/src/libgit2/tag.c":"52b5c50844db1ac4cd34fb802886d99f58f73bdd349c93208b85587d986059b3","libgit2/src/libgit2/tag.h":"9a1c517ed65f7f8576bd03aef9093d911d0dcb9a6d3e62510e38d3a52972cee9","libgit2/src/libgit2/trace.c":"f2e5041c5e688cf4e2c8ccc024ffa6a9c06b894cacfd1f967edfb5e11b513ce5","libgit2/src/libgit2/trace.h":"dc582e64181bbd55683b5afa6b400c9729a8daff22571c0ace7a3aada3b179bf","libgit2/src/libgit2/trailer.c":"6db9292ec295e18dd8b5afa9d02ecc61057a2c97017dcc64701c9768fb9d524f","libgit2/src/libgit2/transaction.c":"7d7a158835fb7b999c40cd990cd1973d44b92ea4f43534cb2a558ae56df5c0bb","libgit2/src/libgit2/transaction.h":"9f713178b226fa474dda766104e4387aa4c23d35938724b374eaf35bb8399919","libgit2/src/libgit2/transport.c":"0bf1b0fba69ed2594267833db264c210911324ce0cbed149a5b8bc1adb32cb5a","libgit2/src/libgit2/transports/auth.c":"e208690ebdec130e9737530a57835df13f5344906fbd9391aed1f665d3821458","libgit2/src/libgit2/transports/auth.h":"db03c74c487403b291248c7be2be837e673d6fddb6867a1782a6a86b3da8d410","libgit2/src/libgit2/transports/auth_gssapi.c":"b572e5aabdc25c1f98f0eac6e558196d2f8bac51cba8c5541cd778b8e84f6d1d","libgit2/src/libgit2/transports/auth_negotiate.h":"9b0b1046193596bf5dbac17eaf94e51ee8f67882f7af8fa099d323cccceda0d1","libgit2/src/libgit2/transports/auth_ntlm.h":"2eab6b8e47b975afbaf721437ddf05d238cad3d80be98ba6d38861bc476f2ab5","libgit2/src/libgit2/transports/auth_ntlmclient.c":"75488a8b39303f0d34a82fc653be8dafc66148e4b064b269f2736d4c189ffd37","libgit2/src/libgit2/transports/auth_sspi.c":"69e78e40d30c16a6d51d23344153113c8b9d4803bcff5c87b65dfedf2d21be93","libgit2/src/libgit2/transports/credential.c":"480f5e35438cf567fecbaa6ae4819725b6c90664f1a1bca20ab2e9718a6f5cd3","libgit2/src/libgit2/transports/credential_helpers.c":"e6ce81166b05d5f66c74c24086ec2025758ced7d8b85b041e190c2dab57e181b","libgit2/src/libgit2/transports/git.c":"723302003d7e77f7a659ac9e9f67c222c4875e5a251afb56c0d3feb52657511a","libgit2/src/libgit2/transports/http.c":"5ae353a7ec87004ce3c195506d7a8ffc8ff93720c3507047b76887a45aa38c1e","libgit2/src/libgit2/transports/http.h":"94e51b28d536bede476bc74b08acef383736e5c2b728459b5fc29760c3da96c1","libgit2/src/libgit2/transports/httpclient.c":"799b5b4806c89bafed2fd1f3d3203df43c7544c83d3e6d01b6e01b0d68d9565b","libgit2/src/libgit2/transports/httpclient.h":"8977ed1e072bd38ee12a42f31907696e0f69c8fcd1a4ed828f9fe7f2ac6903f1","libgit2/src/libgit2/transports/httpparser.c":"eabcf32232ad130c00bfbfb9fb0da4a1d2430459b25d894b53e534c2e6626f0e","libgit2/src/libgit2/transports/httpparser.h":"ee5a08ba448744f5f6dc5738ca43668afbe908d78a5dac2ab9cdae5e9f8024af","libgit2/src/libgit2/transports/local.c":"c0b77a798f73c96a40ae52dfed051a3c34d2b5b7d20b7005d04629f92bbc85c9","libgit2/src/libgit2/transports/smart.c":"28649d6972aec5f2a19343749a316fc6dbf5ef2e6ce26ce183b385c55cd789c2","libgit2/src/libgit2/transports/smart.h":"e1b33350076338b2eb37b0e08fee522893d7ab1fc592743c99c4c0e810542a21","libgit2/src/libgit2/transports/smart_pkt.c":"5be6fedfe4583503b9a0455c49d4fedb25261a85d04ee6824a844c1916652aba","libgit2/src/libgit2/transports/smart_protocol.c":"85beac2c67f766863e701083b4aae3c747b56e23fcf72b70c297845f7503fb7d","libgit2/src/libgit2/transports/ssh.c":"deb2355ef48566d3075df8652f2e6ca1c20331a1a5b62229925a4e89cd0b469e","libgit2/src/libgit2/transports/ssh_exec.c":"b5264b515dbbcf47e5ede81fcd40345a81d423c4d67b8215544f04724db93714","libgit2/src/libgit2/transports/ssh_exec.h":"2774dca45f7267272d4b5e354cee69f464ea323c580dd638313a55ef96718dbb","libgit2/src/libgit2/transports/ssh_libssh2.c":"6fe6117d0109f639d1b371941d113e7d771b26fdf43401fc626654b83fc51d83","libgit2/src/libgit2/transports/ssh_libssh2.h":"cb3202d3ae34a06b380f1778e13c32b40fb86a3113c11ac40f43229fbd304b97","libgit2/src/libgit2/transports/winhttp.c":"6410ba0a7dfe430e58505b643ab017a29a002ce2d886262a340103599b130e88","libgit2/src/libgit2/tree-cache.c":"c99ea22ab8fc2bfc85b762c1792f9a377da757a292ea245d74e444da189d7cde","libgit2/src/libgit2/tree-cache.h":"81ee821b7d1a95b6beb1dbb2e2f51ad7339ecdb97ad99abe4fd35b9be0f7e54e","libgit2/src/libgit2/tree.c":"0754361f28e01c019ab604c82f5b01ed20560d0efbdb01ef8b71ad729ad5fa5d","libgit2/src/libgit2/tree.h":"f2f3be4c1dad7ba9b69aa43543e8463f05dddc25f270f26f7f65011e02cfd1cd","libgit2/src/libgit2/userdiff.h":"f623acbe67ce809a21541d0d665536f68df9e0ed8a4aa4556cbffd070960fc88","libgit2/src/libgit2/worktree.c":"4bfb0f4432168363189af0e828ae2d64cbc064afc2a1b2cbe11ad7947d57eac9","libgit2/src/libgit2/worktree.h":"6962d0da3dc1ff1e967672fcbe84b8b09807924521e5d113df811ebee8e02664","libgit2/src/util/CMakeLists.txt":"b701235a39f8d750fe129677c22672c615c3ceb70e5e86d70b3d2632370d0048","libgit2/src/util/alloc.c":"d647f270fdcaec18eef23a64adeb2b73ea89e4cbe2d06b7205cd999cdc24dd42","libgit2/src/util/alloc.h":"71bb23ad8325f9c90b9bd5cc2e33c29d6f30c7f6e69f12df049e621a75d5b921","libgit2/src/util/allocators/debugalloc.c":"f3f30085a454bedc2d632dd938e8e986d0cef82475377cc27aabce24055c9c8b","libgit2/src/util/allocators/debugalloc.h":"22971098ded1e5bfcce6d24ece72ac64e62eadcbf5f096b5d3b4255c8cef3f54","libgit2/src/util/allocators/failalloc.c":"a5954443a6b20bee10ac45e5cbd6318a033ed8db05b225957bb4d02c387fc54f","libgit2/src/util/allocators/failalloc.h":"c4d7fe2b71894ccfe0571e779a6e00c11f0e4ae53de98492499f75e92ce72f6b","libgit2/src/util/allocators/stdalloc.c":"b7c180ffdb52e8c5bf5000b7e6fa66f68ecbdd4bddaaa9e56eab3ba416aead70","libgit2/src/util/allocators/stdalloc.h":"e140c3b240622f36d7eeea232dd41711fa2c01e8b557a9d3393a5c2b416a5891","libgit2/src/util/allocators/win32_leakcheck.c":"4f7a15ba2fdf29d739214e466c928adc31ffbc0f07b40036a94979126ef1feb0","libgit2/src/util/allocators/win32_leakcheck.h":"1de31b13678ea2dcdae5177f5cb1b7d2d7560ff4069b8f77f07501bec4979913","libgit2/src/util/array.h":"80c16c10150bab526850fc06c363cff4d4319ddd3627f7ad768d86d689c86fbb","libgit2/src/util/assert_safe.h":"0f2b8fa61e0dedbedbc6299618b98bc2c60dd78c386f2b9b8f7b8d747a2daf78","libgit2/src/util/bitvec.h":"281444c865be87104cff9c1b3998877a67bfd92af4b0e5b9b034fd48f6569f7a","libgit2/src/util/cc-compat.h":"c4807cd6963c470be476b3b2dd52b29b93a202db509540710ea32d81f6fb24de","libgit2/src/util/ctype_compat.h":"03fc885c9e4bd4a1b7b994d86702be50bba1889e3a6c47ab985c0d38005ec8b9","libgit2/src/util/date.c":"4324e7b1cdeb8364b7760f0f112fd89281095b9e2e67b77fd321907aa6ffb9c9","libgit2/src/util/date.h":"3d99d9140365e84dfb1240f14fec29dc5600728c94287616eecec02505dc3bce","libgit2/src/util/errors.c":"ffabe99d635d8dbbd6fcae5bcdf18dff990fcbf370af0e8e0f17b62227cc279e","libgit2/src/util/errors.h":"ccf61cde1b16ecdc843d9b5a0eba2e18deb15bab5d0877f1d9103b95cbc08216","libgit2/src/util/filebuf.c":"5ee4b7e2adae5ccf2b751851af8cde8762c473154dda4203fba2bf8080fbcfde","libgit2/src/util/filebuf.h":"f98eada1c23529ddf9a942c252abd5310a8e1e965c72b21d082a2a58cd552786","libgit2/src/util/fs_path.c":"f00858c426c4d680a6868af30386a1df99c27d45db5ddb975c1f11e79d50a547","libgit2/src/util/fs_path.h":"0a2fb2eff8adacc9985b1474592dfa50410b045d4ae81815ff43086a82f7cc35","libgit2/src/util/futils.c":"c8d225d2f335745fb9d4236b8c29055fbe0249fe23ef5c3d313162bb397a744b","libgit2/src/util/futils.h":"1e5d989463ea53e2cea0677ca2bbc975692fc61259a0566c111a65c3f4d7a038","libgit2/src/util/git2_features.h.in":"5fd9c7d8bd78459d7028011d3714f21344e8cf764a9e58ca34389dbc1b8a4b69","libgit2/src/util/git2_util.h":"f88550b3c5f9e1380714693ad0865c6f5e91b2ddd66f8ebf3889ae87fcd4fd31","libgit2/src/util/hash.c":"0c88b789d33f29229471a9dfba3da0a989214a08a8ea727582d81e7118c561ea","libgit2/src/util/hash.h":"6fc0c22c0a1ee5a26ef29c8512c951db23f440dad2b7a31403abc214d1d7447b","libgit2/src/util/hash/builtin.c":"fe213c6a61b923cbd3acd6e55322f4d60c531617f3b9d5d281d2050c6e0a9524","libgit2/src/util/hash/builtin.h":"bbf1ce668c7d38412a5c7351b41da31352f5eba696ed453daaf442e97c225f00","libgit2/src/util/hash/collisiondetect.c":"50be16f2b8736ca61bbe657751a0e66b847affd6030a582f3366c27659e5d245","libgit2/src/util/hash/collisiondetect.h":"21d7b15068a89ed1537a6cdb5d1a3b78e5bb3bba318839579f9b20aa796518b6","libgit2/src/util/hash/common_crypto.c":"241d19fef0e60f08189405bb8ef3426438505bea0752ac57f10db06be6dc8dcb","libgit2/src/util/hash/common_crypto.h":"5b89841130c9d4b09cf90ff25874cec68d18df3f4883ad74b15c408bbaf77b9f","libgit2/src/util/hash/mbedtls.c":"97b33e8b549e9b1da4ae558db570679feed0f91e504b339e4be697084c721343","libgit2/src/util/hash/mbedtls.h":"42dc9f2db495d9440f0ec5dcca33b28395be7bce55d2560c33cc3a4c474623cc","libgit2/src/util/hash/openssl.c":"6bffa802f0c6d3f9e89c0c048011db663c630933a4805b5be3fb348d68516893","libgit2/src/util/hash/openssl.h":"d20fe2ff5a8ea2023ce1e6db742743b17aea02e5bd83eddb241d71242603c5b3","libgit2/src/util/hash/rfc6234/sha.h":"137fbf064d7168c033551d8302bce635721cceb9423b8d4bd5182afbaec12af7","libgit2/src/util/hash/rfc6234/sha224-256.c":"b01cf69e8bdb01a83465ec0d6551633d97c96307406d1595887aeea3f5512136","libgit2/src/util/hash/sha.h":"11683cd719ff7185a248d06c7caddb2e6fceb1d49f4f03f349215bf13d0c3963","libgit2/src/util/hash/sha1dc/sha1.c":"fa40cc0830d58d340eb8942499181de72fd4cb4ad4c1d1216755b0367499661a","libgit2/src/util/hash/sha1dc/sha1.h":"78f97f092c20329d1fa8d9a8cbb3d53bb90be19cbc49f1917605a9ddf520de83","libgit2/src/util/hash/sha1dc/ubc_check.c":"7b0db83569ba82965dc0d16e51e9ad85167cdfdab343f9a02c2c475bfdd93956","libgit2/src/util/hash/sha1dc/ubc_check.h":"4a140693701da167b4709c4c1b330800a0c29f2a065d0d819567a27b3171a09f","libgit2/src/util/hash/win32.c":"fed73763276bc187e2f109eb4aff19653e5fc4fb092066a3be8fd02c9e3cb77b","libgit2/src/util/hash/win32.h":"43b90e70705dcfc28cd19b1ca2ef206a364db60af390d3dd73a8ec13d2a79e69","libgit2/src/util/hashmap.h":"bff15bebd2e7a995de36de1c7115f466be3074017f000601570d60f5fa357e02","libgit2/src/util/hashmap_str.h":"202f61cebe12bbde1858cb449abb736c6029d1d2cda528fb4ba00d8a0fa13618","libgit2/src/util/integer.h":"78c97ef23562d2bb46648272683186cd5b2e4671bb808745a0f08118ab73e3c5","libgit2/src/util/map.h":"f678e1aab0799a95a2152aa6cf6ef6b306eb783ba2a6b2b0e3df147e5596a917","libgit2/src/util/net.c":"422191d20e04f17a1fc20d41b35b4c353b0e72db448defeac1acf393c12229bc","libgit2/src/util/net.h":"72adee72e7ff998874869e71015728bc2849a2767bcc8a7cced64fdadf575609","libgit2/src/util/pool.c":"519fb7489c80b4abb2b842123f46d723088ad0bb11647448189601f242dda7ed","libgit2/src/util/pool.h":"3c19d4a3cfe5c97bebb3669c00ab4df1e999bac22b96e35dbe5a6db7066ead07","libgit2/src/util/posix.c":"a36bc1f5a1af765f383865666d2eaabf0347810e6b4cf39af538b26ea5cac17d","libgit2/src/util/posix.h":"a7f36bda0c34725cd657844e61d0e08367beb9c8088bdd8fd59efa6c221f7490","libgit2/src/util/pqueue.c":"d5cd8f6c5639b50898fe0f9354dbb29de1a0be1d20c879242c8423907466c7c2","libgit2/src/util/pqueue.h":"6fb6944e1d5200ddb837e24356e419f03586d5947193ff929c3a5980dc228161","libgit2/src/util/process.h":"b0477c92e923549f8587f631c4f5e965906711228425503f9d91d447f4aa7063","libgit2/src/util/rand.c":"1b374bc351c569dd3ee4566fed5dcb605563436ddaf3917dcb734375e2f0d08e","libgit2/src/util/rand.h":"102ce327cbbd6dcb9bdbe4c35addc7efa0095f335873860b06d85c4a070ebeac","libgit2/src/util/regexp.c":"2871d762d7b02224505309845e57c44e66e7e4db112d4cf91c4f9ec5b4b7faf5","libgit2/src/util/regexp.h":"855b271c6c8f5e1f3e5dc6648e9364d0448b2a9c1bc72cc3de0d1742e2905b32","libgit2/src/util/runtime.c":"b287e2f3d0a686b77736b725a86b8d6bea904b93c2f987513c608663b818a1d9","libgit2/src/util/runtime.h":"9aac7e20b2b879c49a423ef537bb0f51b8371c24503281b283b1fb74a8944f49","libgit2/src/util/sortedcache.c":"1c628c9b96899091117dcbaf81e4dfb048b6cff88ef58d855bbdccba423ac86e","libgit2/src/util/sortedcache.h":"e3255937db47e162df9c290cce9aef6030cc9d6ebb14128c568f97f52ba242d1","libgit2/src/util/staticstr.h":"d9d6abc1dd70f297f8c51ce23a52d8c62d43b306d05779d42a5bc1703eb11a36","libgit2/src/util/str.c":"cc41608f2c8e140929ea480170932f2cce61a2dd3e1199b3371d2ceb8d14a4e3","libgit2/src/util/str.h":"5c26b120517aa1b05ab78af401fa0e1f5848498100bc4204ca9f361451efec2c","libgit2/src/util/strlist.c":"5fd9b8d72068af3b7732e6e9a02bf84000748e6e50b24025a2a477c12d5824ef","libgit2/src/util/strlist.h":"1c3dfeb8e5e28e829a27dee01053500dbc32aea1f6c427953d7a53706f7a1eef","libgit2/src/util/strnlen.h":"27e73ccbe8be7c5bac1d022bf59f2458cc2998d0222e7658a687e02f89fcfd17","libgit2/src/util/thread.c":"6d741eabfe3739f6183e1d65935405f9fc457622d5b21978d4affdf6084e4fde","libgit2/src/util/thread.h":"a837c87234498a6d036fcbb84cddb4e9c28f73dac2f5572d0dbd2719330f2158","libgit2/src/util/tsort.c":"d1dee30457c185e86bc47efecc5417459aefeabe7cfaf8aebe5eb2239296ee20","libgit2/src/util/unix/map.c":"1606c6c48126c72040f985492b6e6bd7b5df27d384f6f5bea9c35118ef7bb2de","libgit2/src/util/unix/posix.h":"b6619fc5059c0759e2904cde66d879bfb0918567f5235026f0c2d7875a3d517e","libgit2/src/util/unix/process.c":"71641f263642687cf01b02b1b636282e89f2595038a47e63b9bdc872fec2d368","libgit2/src/util/unix/pthread.h":"c716daa88e2e7ef2a2df23591a6061965dc11d77ed5fe1863d367a2b738a9fb0","libgit2/src/util/unix/realpath.c":"41f2f5e03d021d2c981b325f7806e97669280fbcd944903eeab902095554c046","libgit2/src/util/utf8.c":"6cbf514d854524883a85a781e5e2a9003d71793fb2cd527795d26c972831a8ef","libgit2/src/util/utf8.h":"ca0239005ee004df1b2593aa727036a82f1ce3953000c3acf712b2639acd9206","libgit2/src/util/util.c":"35791b9a91e04008448360145c389374fa21296d8c90a99f9b41edf14794fc1c","libgit2/src/util/util.h":"07936486b65a55bcc6075eaf53984467a0edfe3d278af64a73f115f32d401a77","libgit2/src/util/varint.c":"f93c5ba3498ac04d98ba13d3a320263892452356f797c4091411e5833dfe82f2","libgit2/src/util/varint.h":"b429d5e8b69d579cc6dab5e12ab4acf904c66947b41ca5372dfc3e327e59f533","libgit2/src/util/vector.c":"438ce226d81796d0ac72fe5bd0427c6cefc98ed45dcbf1ecae5a56795ce4fec4","libgit2/src/util/vector.h":"345d074375a69c0111d44d493aa627fb297fdfad1f305b69f276eb9eab585e5b","libgit2/src/util/wildmatch.c":"0102685757a40046a5fdee4be5be784989a84d3b4be9160d48120c4485c37133","libgit2/src/util/wildmatch.h":"7d59454a8271f810622ccfebb26c8408bd85f64c99d4411028e8482ab2c333d0","libgit2/src/util/win32/dir.c":"a0490ab8fe3be9213093afca1923a0fc472d368f2907ab23a86bb392520359cc","libgit2/src/util/win32/dir.h":"7835a64f082860bee53530a381fe9d6917be39f0d756562e8306dfd78154d3f7","libgit2/src/util/win32/error.c":"78d4147c74ea5f040fc01add6d8f216be3593bf589814bb2a4748efea89d97be","libgit2/src/util/win32/error.h":"44175c12d291bdf80df65a41cf669708c252ec04c8e084c41777c7a0b53569d1","libgit2/src/util/win32/map.c":"6ce9c0a15df639d1d5b334685eeef03698b3ed006924e60b67461336fd7afc20","libgit2/src/util/win32/mingw-compat.h":"f7b0c7e9dd7147e8930782cdf54ce4be385172966dd1e27435446f38cd2f6d68","libgit2/src/util/win32/msvc-compat.h":"36e4c9515a8d53e70f78794826700c4a5472235aef703ac439308dd005e31454","libgit2/src/util/win32/path_w32.c":"9b1fbfe3907f3030dd065d2dea16d3ea534f5aed8c44d2709a8feeb0f93bcac8","libgit2/src/util/win32/path_w32.h":"de01a65de67423157b23202bcba550f4ed1d5de2ac0e06a4f2ae8177aa4319ba","libgit2/src/util/win32/posix.h":"4dd2fc1529fc7e625180b649b9212d83462c654e47ba799fa62353aed5f60e14","libgit2/src/util/win32/posix_w32.c":"4cb0cf6500dfb19ed11feb47e013e2ae06b0a04464a8e8ff27e3282e5a7b3fbd","libgit2/src/util/win32/precompiled.c":"4dff04101bd64b95c8f708d1accd1bedc39e95a263444290f796c63f4734d4cd","libgit2/src/util/win32/precompiled.h":"3f763f472fbf822314807b91ee9a2f88fe7f2fbcd165ee4e57f3f90e2715f14c","libgit2/src/util/win32/process.c":"b3e0cb393c876bee28a1ad794c4fd9a7c3d0bc022b12227d01ff9b5bab6afb5f","libgit2/src/util/win32/reparse.h":"ba254a97aba283930306f39f73e297d5eb30350276b3dd86deecbf10b4094c97","libgit2/src/util/win32/thread.c":"bce98dd6a635b66d811fab98b943ec448751a8b55c7261732277ca2804862fb9","libgit2/src/util/win32/thread.h":"8141cdc5c89901c2259a64c8f070950f69d4b742ed743ee1c520fd47b5b6948c","libgit2/src/util/win32/utf-conv.c":"7fc663799ef1f595c8438ddb5945aa71e9cf3b47ba2f626f017845af5eca7cdf","libgit2/src/util/win32/utf-conv.h":"61b3a452d6e79d03e5087885442a05c561a36ef5468575502c98bafb74a71cd7","libgit2/src/util/win32/version.h":"d9d9546728ee78923ba7ea1c16c000d44450ca80f8c0c3268bf58fa9966ba38f","libgit2/src/util/win32/w32_buffer.c":"1099be7c74b5bb265614787be285505e18dcaf71412607d8ec108639762a5b8d","libgit2/src/util/win32/w32_buffer.h":"a11005a0f3b2ecb3220ea9cdfd1df4b61d90f10e0e68ed2716b2f9502f2fa8f7","libgit2/src/util/win32/w32_common.h":"214cd67bcbdf89bb7effb78ee50c60b0504319117984c1a990f8301cffd78e0d","libgit2/src/util/win32/w32_leakcheck.c":"844ae1c7a959c8f181b45cc49a6f34262d03510f7748b6be7a159d7fd5c5e56d","libgit2/src/util/win32/w32_leakcheck.h":"cace8320ae9e8ef1d5b4836905a2d318e9e2a536ad3e232393e5f42b428554e7","libgit2/src/util/win32/w32_util.c":"27d9669fd679dc4d739c1a898028be6d154abe41f47ba7298c487956587e1c82","libgit2/src/util/win32/w32_util.h":"f052487c469e5ba2fd327e55db1721e8f3f8d8947872ac63c6a3591fe7768853","libgit2/src/util/win32/win32-compat.h":"5ea7b1837a159d66a89365ee2bf3724b32579a6a78d94b0b205d44c8697a8e6f","libgit2/src/util/zstream.c":"51e507d17742366b91e6f427a48b566b52013f06502c3f05fbd6f5120b3d6afb","libgit2/src/util/zstream.h":"daa41460cf0ed019ef62a1936fb18626e8abac2b9a05061b6a377d4ab74a4cc5"},"package":"e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec"}
\ No newline at end of file
diff --git a/libgit2-sys/CHANGELOG.md b/libgit2-sys/CHANGELOG.md
new file mode 100644 (file)
index 0000000..02f82b0
--- /dev/null
@@ -0,0 +1,194 @@
+# Changelog
+
+## 0.17.0+1.8.1 - 2024-06-13
+[0.16.2...0.17.0](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.16.2+1.7.2...libgit2-sys-0.17.0+1.8.1)
+
+### Changed
+
+- ❗ Updated to libgit2 [1.8.1](https://github.com/libgit2/libgit2/releases/tag/v1.8.1)
+  [#1032](https://github.com/rust-lang/git2-rs/pull/1032)
+
+## 0.16.2+1.7.2 - 2024-02-06
+[0.16.1...0.16.2](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.16.1+1.7.1...libgit2-sys-0.16.2+1.7.2)
+
+### Added
+
+- Added binding for `git_commit_lookup_prefix`.
+  [#1011](https://github.com/rust-lang/git2-rs/pull/1011)
+- Added binding for `git_object_lookup_prefix`.
+  [#1014](https://github.com/rust-lang/git2-rs/pull/1014)
+
+### Changed
+
+- ❗ Updated to libgit2 [1.7.2](https://github.com/libgit2/libgit2/releases/tag/v1.7.2).
+  This fixes [CVE-2024-24575](https://github.com/libgit2/libgit2/security/advisories/GHSA-54mf-x2rh-hq9v) and [CVE-2024-24577](https://github.com/libgit2/libgit2/security/advisories/GHSA-j2v7-4f6v-gpg8).
+  [#1017](https://github.com/rust-lang/git2-rs/pull/1017)
+
+## 0.16.1+1.7.1 - 2023-08-28
+[0.16.0...0.16.1](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.16.0+1.7.1...libgit2-sys-0.16.1+1.7.1)
+
+### Fixed
+
+- Fixed publish of 0.16.0 missing the libgit2 submodule.
+
+## 0.16.0+1.7.1 - 2023-08-28
+[0.15.2...0.16.0](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.15.2+1.6.4...libgit2-sys-0.16.0+1.7.1)
+
+### Added
+
+- Added LIBGIT2_NO_VENDOR environment variable to force using the system libgit2.
+  [#966](https://github.com/rust-lang/git2-rs/pull/966)
+- Added binding for `git_blame_buffer`.
+  [#981](https://github.com/rust-lang/git2-rs/pull/981)
+
+### Changed
+
+- Updated to libgit2 [1.7.0](https://github.com/libgit2/libgit2/releases/tag/v1.7.0).
+  [#968](https://github.com/rust-lang/git2-rs/pull/968)
+- Updated to libgit2 [1.7.1](https://github.com/libgit2/libgit2/releases/tag/v1.7.1).
+  [#982](https://github.com/rust-lang/git2-rs/pull/982)
+
+### Fixed
+
+- Fixed builds with cargo's `-Zminimal-versions`.
+  [#960](https://github.com/rust-lang/git2-rs/pull/960)
+
+
+## 0.15.2+1.6.4 - 2023-05-27
+[0.15.1...0.15.2](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.15.1+1.6.4...libgit2-sys-0.15.2+1.6.4)
+
+### Added
+
+- Added bindings for stash options.
+  [#930](https://github.com/rust-lang/git2-rs/pull/930)
+
+## 0.15.1+1.6.4 - 2023-04-13
+[0.15.0...0.15.1](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.15.0+1.6.3...libgit2-sys-0.15.1+1.6.4)
+
+### Changed
+
+- Updated to libgit2 [1.6.4](https://github.com/libgit2/libgit2/releases/tag/v1.6.4).
+  This brings in a minor fix on Windows when the ProgramData directory does not exist.
+  [#948](https://github.com/rust-lang/git2-rs/pull/948)
+
+## 0.15.0+1.6.3 - 2023-04-02
+[0.14.2...0.15.0](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.14.2+1.5.1...libgit2-sys-0.15.0+1.6.3)
+
+### Added
+
+- Added bindings for `git_remote_name_is_valid`, `git_reference_name_is_valid`, and `git_tag_name_is_valid`.
+  [#882](https://github.com/rust-lang/git2-rs/pull/882)
+- Added bindings for `git_indexer` support.
+  [#911](https://github.com/rust-lang/git2-rs/pull/911)
+- Added bindings for `git_index_find_prefix`.
+  [#903](https://github.com/rust-lang/git2-rs/pull/903)
+- Added support for the deprecated group-writeable blob file mode.
+  [#887](https://github.com/rust-lang/git2-rs/pull/887)
+
+### Changed
+
+- Updated libssh2-sys from 0.2 to 0.3.
+  This brings in numerous changes, including SHA2 algorithm support with RSA.
+  [#919](https://github.com/rust-lang/git2-rs/pull/919)
+- Updated to libgit2 [1.6.3](https://github.com/libgit2/libgit2/blob/main/docs/changelog.md#v163).
+  This brings in many changes, including better SSH host key support on Windows and better SSH host key algorithm negotiation.
+  1.6.3 is now the minimum supported version.
+  [#935](https://github.com/rust-lang/git2-rs/pull/935)
+- The `GIT_DIFF_` constants have been changed to be a `git_diff_option_t` type.
+  [#935](https://github.com/rust-lang/git2-rs/pull/935)
+
+### Fixed
+
+- Fixed the rerun-if-changed build script support on Windows. This is only relevant for those working within the git2-rs source tree.
+  [#916](https://github.com/rust-lang/git2-rs/pull/916)
+
+## 0.14.2+1.5.1 - 2023-01-20
+[0.14.1...0.14.2](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.14.1+1.5.0...libgit2-sys-0.14.2+1.5.1)
+
+### Changed
+- Updated the bundled libgit2 to [1.5.1](https://github.com/libgit2/libgit2/releases/tag/v1.5.1).
+  [a233483a3952d6112653be86fb5ce65267e3d5ac](https://github.com/rust-lang/git2-rs/commit/a233483a3952d6112653be86fb5ce65267e3d5ac)
+  - Changes: [fbea439d4b6fc91c6b619d01b85ab3b7746e4c19...42e5db98b963ae503229c63e44e06e439df50e56](https://github.com/libgit2/libgit2/compare/fbea439d4b6fc91c6b619d01b85ab3b7746e4c19...42e5db98b963ae503229c63e44e06e439df50e56):
+  - Fixes [GHSA-8643-3wh5-rmjq](https://github.com/libgit2/libgit2/security/advisories/GHSA-8643-3wh5-rmjq) to validate SSH host keys.
+  - The supported libgit2 system library range is 1.5.1 to less than 1.6.0 or 1.4.5 to less than 1.5.0, which should include this fix.
+
+## 0.13.5+1.4.5 - 2023-01-20
+[0.13.4...0.13.5](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.4+1.4.2...libgit2-sys-0.13.5+1.4.5)
+
+### Changed
+- Updated the bundled libgit2 to [1.4.5](https://github.com/libgit2/libgit2/releases/tag/v1.4.5).
+  - Changes: [2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7...cd6f679af401eda1f172402006ef8265f8bd58ea](https://github.com/libgit2/libgit2/compare/2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7...cd6f679af401eda1f172402006ef8265f8bd58ea):
+  - Fixes [GHSA-8643-3wh5-rmjq](https://github.com/libgit2/libgit2/security/advisories/GHSA-8643-3wh5-rmjq) to validate SSH host keys.
+  - The supported libgit2 system library range is 1.4.5 to less than 1.5.0.
+
+## 0.14.1+1.5.0 - 2023-01-10
+[0.14.0...0.14.1](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.14.0+1.5.0...libgit2-sys-0.14.1+1.5.0)
+
+### Added
+- Added variants to `git_cert_ssh_raw_type_t`.
+  [#909](https://github.com/rust-lang/git2-rs/pull/909)
+
+## 0.14.0+1.5.0 - 2022-07-28
+[0.13.4...0.14.0](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.4+1.4.2...libgit2-sys-0.14.0+1.5.0)
+
+### Added
+- Added bindings for ownership validation.
+  [#839](https://github.com/rust-lang/git2-rs/pull/839)
+
+### Changed
+
+- Updated the bundled libgit2 to [1.5.0](https://github.com/libgit2/libgit2/releases/tag/v1.5.0).
+  [#839](https://github.com/rust-lang/git2-rs/pull/839)
+  [#858](https://github.com/rust-lang/git2-rs/pull/858)
+  - Changes: [2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7...fbea439d4b6fc91c6b619d01b85ab3b7746e4c19](https://github.com/libgit2/libgit2/compare/2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7...fbea439d4b6fc91c6b619d01b85ab3b7746e4c19):
+  - The supported libgit2 system library range is 1.4.4 to less than 1.6.0.
+  - Fixes [CVE 2022-24765](https://github.com/libgit2/libgit2/releases/tag/v1.4.3).
+
+## 0.13.4+1.4.2 - 2022-05-10
+[0.13.3...0.13.4](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.3+1.4.2...libgit2-sys-0.13.4+1.4.2)
+
+### Added
+- Added bindings for `git_commit_body`
+  [#835](https://github.com/rust-lang/git2-rs/pull/835)
+
+## 0.13.3+1.4.2 - 2022-04-27
+[0.13.2...0.13.3](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.2+1.4.2...libgit2-sys-0.13.3+1.4.2)
+
+### Changed
+- Updated the bundled libgit2 to 1.5.0-alpha.
+  [#822](https://github.com/rust-lang/git2-rs/pull/822)
+  - Changes: [182d0d1ee933de46bf0b5a6ec269bafa77aba9a2...2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7](https://github.com/libgit2/libgit2/compare/182d0d1ee933de46bf0b5a6ec269bafa77aba9a2...2a0d0bd19b5d13e2ab7f3780e094404828cbb9a7)
+- Changed the pkg-config probe to restrict linking against a version of a system-installed libgit2 to a version less than 1.5.0.
+  Previously it would allow any version above 1.4.0 which could pick up an API-breaking version.
+  [#817](https://github.com/rust-lang/git2-rs/pull/817)
+- When using pkg-config to locate libgit2, the system lib dirs are no longer added to the search path.
+  [#831](https://github.com/rust-lang/git2-rs/pull/831)
+- When using the `zlib-ng-compat` Cargo feature, `libssh2-sys` is no longer automatically included unless you also enable the `ssh` feature.
+  [#833](https://github.com/rust-lang/git2-rs/pull/833)
+
+## 0.13.2+1.4.2 - 2022-03-10
+[0.13.1...0.13.2](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.1+1.4.2...libgit2-sys-0.13.2+1.4.2)
+
+### Added
+- Added bindings for `git_odb_exists_ext`.
+  [#818](https://github.com/rust-lang/git2-rs/pull/818)
+
+## 0.13.1+1.4.2 - 2022-02-28
+[0.13.0...0.13.1](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.13.0+1.4.1...libgit2-sys-0.13.1+1.4.2)
+
+### Changed
+- Updated the bundled libgit2 to [1.4.2](https://github.com/libgit2/libgit2/releases/tag/v1.4.2).
+  [#815](https://github.com/rust-lang/git2-rs/pull/815)
+  - Changes: [fdd15bcfca6b2ec4b7ecad1aa11a396cb15bd064...182d0d1ee933de46bf0b5a6ec269bafa77aba9a2](https://github.com/libgit2/libgit2/compare/fdd15bcfca6b2ec4b7ecad1aa11a396cb15bd064...182d0d1ee933de46bf0b5a6ec269bafa77aba9a2).
+
+## 0.13.0+1.4.1 - 2022-02-24
+[0.12.26...0.13.0](https://github.com/rust-lang/git2-rs/compare/libgit2-sys-0.12.26+1.3.0...libgit2-sys-0.13.0+1.4.1)
+
+### Changed
+- Changed libgit2-sys to use the presence of the `src` directory instead of `.git` to determine if it has a git submodule that needs updating.
+  [#801](https://github.com/rust-lang/git2-rs/pull/801)
+- Updated the bundled libgit2 to [1.4.1](https://github.com/libgit2/libgit2/releases/tag/v1.4.1) (see also [1.4.0](https://github.com/libgit2/libgit2/releases/tag/v1.4.0))
+  [#806](https://github.com/rust-lang/git2-rs/pull/806)
+  [#811](https://github.com/rust-lang/git2-rs/pull/811)
+  - Changes: [b7bad55e4bb0a285b073ba5e02b01d3f522fc95d...fdd15bcfca6b2ec4b7ecad1aa11a396cb15bd064](https://github.com/libgit2/libgit2/compare/b7bad55e4bb0a285b073ba5e02b01d3f522fc95d...fdd15bcfca6b2ec4b7ecad1aa11a396cb15bd064)
+  - The supported libgit2 system library range is 1.4.0 or greater.
diff --git a/libgit2-sys/Cargo.toml b/libgit2-sys/Cargo.toml
new file mode 100644 (file)
index 0000000..f6ca047
--- /dev/null
@@ -0,0 +1,74 @@
+# 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 = "2018"
+name = "libgit2-sys"
+version = "0.18.0+1.9.0"
+authors = [
+    "Josh Triplett <josh@joshtriplett.org>",
+    "Alex Crichton <alex@alexcrichton.com>",
+]
+build = "build.rs"
+links = "git2"
+exclude = [
+    "libgit2/ci/*",
+    "libgit2/docs/*",
+    "libgit2/examples/*",
+    "libgit2/fuzzers/*",
+    "libgit2/tests/*",
+]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "Native bindings to the libgit2 library"
+readme = false
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/git2-rs"
+
+[lib]
+name = "libgit2_sys"
+path = "lib.rs"
+
+[dependencies.libc]
+version = "0.2"
+
+[dependencies.libssh2-sys]
+version = "0.3.0"
+optional = true
+
+[dependencies.libz-sys]
+version = "1.1.0"
+features = ["libc"]
+default-features = false
+
+[build-dependencies.cc]
+version = "1.0.43"
+features = ["parallel"]
+
+[build-dependencies.pkg-config]
+version = "0.3.15"
+
+[features]
+https = ["openssl-sys"]
+ssh = ["libssh2-sys"]
+vendored = []
+vendored-openssl = ["openssl-sys/vendored"]
+zlib-ng-compat = [
+    "libz-sys/zlib-ng",
+    "libssh2-sys?/zlib-ng-compat",
+]
+
+[target."cfg(unix)".dependencies.openssl-sys]
+version = "0.9.45"
+optional = true
diff --git a/libgit2-sys/LICENSE-APACHE b/libgit2-sys/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..16fe87b
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/libgit2-sys/LICENSE-MIT b/libgit2-sys/LICENSE-MIT
new file mode 100644 (file)
index 0000000..39e0ed6
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2014 Alex Crichton
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/libgit2-sys/build.rs b/libgit2-sys/build.rs
new file mode 100644 (file)
index 0000000..77cd4ea
--- /dev/null
@@ -0,0 +1,301 @@
+use std::env;
+use std::fs;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+
+/// Tries to use system libgit2 and emits necessary build script instructions.
+fn try_system_libgit2() -> Result<pkg_config::Library, pkg_config::Error> {
+    let mut cfg = pkg_config::Config::new();
+    match cfg.range_version("1.9.0".."1.10.0").probe("libgit2") {
+        Ok(lib) => {
+            for include in &lib.include_paths {
+                println!("cargo:root={}", include.display());
+            }
+            Ok(lib)
+        }
+        Err(e) => {
+            println!("cargo:warning=failed to probe system libgit2: {e}");
+            Err(e)
+        }
+    }
+}
+
+fn main() {
+    println!(
+        "cargo:rustc-check-cfg=cfg(\
+            libgit2_vendored,\
+        )"
+    );
+
+    let https = env::var("CARGO_FEATURE_HTTPS").is_ok();
+    let ssh = env::var("CARGO_FEATURE_SSH").is_ok();
+    let vendored = env::var("CARGO_FEATURE_VENDORED").is_ok();
+    let zlib_ng_compat = env::var("CARGO_FEATURE_ZLIB_NG_COMPAT").is_ok();
+
+    // Specify `LIBGIT2_NO_VENDOR` to force to use system libgit2.
+    // Due to the additive nature of Cargo features, if some crate in the
+    // dependency graph activates `vendored` feature, there is no way to revert
+    // it back. This env var serves as a workaround for this purpose.
+    println!("cargo:rerun-if-env-changed=LIBGIT2_NO_VENDOR");
+    let forced_no_vendor = env::var_os("LIBGIT2_NO_VENDOR").map_or(false, |s| s != "0");
+
+    if forced_no_vendor {
+        if try_system_libgit2().is_err() {
+            panic!(
+                "\
+The environment variable `LIBGIT2_NO_VENDOR` has been set but no compatible system libgit2 could be found.
+The build is now aborting. To disable, unset the variable or use `LIBGIT2_NO_VENDOR=0`.
+",
+            );
+        }
+
+        // We've reached here, implying we're using system libgit2.
+        return;
+    }
+
+    // To use zlib-ng in zlib-compat mode, we have to build libgit2 ourselves.
+    let try_to_use_system_libgit2 = !vendored && !zlib_ng_compat;
+    if try_to_use_system_libgit2 && try_system_libgit2().is_ok() {
+        // using system libgit2 has worked
+        return;
+    }
+
+    println!("cargo:rustc-cfg=libgit2_vendored");
+
+    if !Path::new("libgit2/src").exists() {
+        let _ = Command::new("git")
+            .args(&["submodule", "update", "--init", "libgit2"])
+            .status();
+    }
+
+    let target = env::var("TARGET").unwrap();
+    let windows = target.contains("windows");
+    let dst = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+    let include = dst.join("include");
+    let mut cfg = cc::Build::new();
+    fs::create_dir_all(&include).unwrap();
+
+    // Copy over all header files
+    cp_r("libgit2/include", &include);
+
+    cfg.include(&include)
+        .include("libgit2/src/libgit2")
+        .include("libgit2/src/util")
+        .out_dir(dst.join("build"))
+        .warnings(false);
+
+    // Include all cross-platform C files
+    add_c_files(&mut cfg, "libgit2/src/libgit2");
+    add_c_files(&mut cfg, "libgit2/src/util");
+
+    // These are activated by features, but they're all unconditionally always
+    // compiled apparently and have internal #define's to make sure they're
+    // compiled correctly.
+    add_c_files(&mut cfg, "libgit2/src/libgit2/transports");
+    add_c_files(&mut cfg, "libgit2/src/libgit2/streams");
+
+    // Always use bundled HTTP parser (llhttp) for now
+    cfg.include("libgit2/deps/llhttp");
+    add_c_files(&mut cfg, "libgit2/deps/llhttp");
+
+    // external/system xdiff is not yet supported
+    cfg.include("libgit2/deps/xdiff");
+    add_c_files(&mut cfg, "libgit2/deps/xdiff");
+
+    // Use the included PCRE regex backend.
+    //
+    // Ideally these defines would be specific to the pcre files (or placed in
+    // a config.h), but since libgit2 already has a config.h used for other
+    // reasons, just define on the command-line for everything. Perhaps there
+    // is some way with cc to have different instructions per-file?
+    cfg.define("GIT_REGEX_BUILTIN", "1")
+        .include("libgit2/deps/pcre")
+        .define("HAVE_STDINT_H", Some("1"))
+        .define("HAVE_MEMMOVE", Some("1"))
+        .define("NO_RECURSE", Some("1"))
+        .define("NEWLINE", Some("10"))
+        .define("POSIX_MALLOC_THRESHOLD", Some("10"))
+        .define("LINK_SIZE", Some("2"))
+        .define("PARENS_NEST_LIMIT", Some("250"))
+        .define("MATCH_LIMIT", Some("10000000"))
+        .define("MATCH_LIMIT_RECURSION", Some("MATCH_LIMIT"))
+        .define("MAX_NAME_SIZE", Some("32"))
+        .define("MAX_NAME_COUNT", Some("10000"));
+    // "no symbols" warning on pcre_string_utils.c is because it is only used
+    // when when COMPILE_PCRE8 is not defined, which is the default.
+    add_c_files(&mut cfg, "libgit2/deps/pcre");
+
+    cfg.file("libgit2/src/util/allocators/failalloc.c");
+    cfg.file("libgit2/src/util/allocators/stdalloc.c");
+
+    if windows {
+        add_c_files(&mut cfg, "libgit2/src/util/win32");
+        cfg.define("STRSAFE_NO_DEPRECATE", None);
+        cfg.define("WIN32", None);
+        cfg.define("_WIN32_WINNT", Some("0x0600"));
+
+        // libgit2's build system claims that forks like mingw-w64 of MinGW
+        // still want this define to use C99 stdio functions automatically.
+        // Apparently libgit2 breaks at runtime if this isn't here? Who knows!
+        if target.contains("gnu") {
+            cfg.define("__USE_MINGW_ANSI_STDIO", "1");
+        }
+    } else {
+        add_c_files(&mut cfg, "libgit2/src/util/unix");
+        cfg.flag("-fvisibility=hidden");
+    }
+    if target.contains("solaris") || target.contains("illumos") {
+        cfg.define("_POSIX_C_SOURCE", "200112L");
+        cfg.define("__EXTENSIONS__", None);
+    }
+
+    let mut features = String::new();
+
+    features.push_str("#ifndef INCLUDE_features_h\n");
+    features.push_str("#define INCLUDE_features_h\n");
+    features.push_str("#define GIT_THREADS 1\n");
+    features.push_str("#define GIT_TRACE 1\n");
+    features.push_str("#define GIT_HTTPPARSER_BUILTIN 1\n");
+
+    if !target.contains("android") {
+        features.push_str("#define GIT_USE_NSEC 1\n");
+    }
+
+    if windows {
+        features.push_str("#define GIT_IO_WSAPOLL 1\n");
+    } else {
+        // Should we fallback to `select` as more systems have that?
+        features.push_str("#define GIT_IO_POLL 1\n");
+        features.push_str("#define GIT_IO_SELECT 1\n");
+    }
+
+    if target.contains("apple") {
+        features.push_str("#define GIT_USE_STAT_MTIMESPEC 1\n");
+    } else {
+        features.push_str("#define GIT_USE_STAT_MTIM 1\n");
+    }
+
+    if env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap() == "32" {
+        features.push_str("#define GIT_ARCH_32 1\n");
+    } else {
+        features.push_str("#define GIT_ARCH_64 1\n");
+    }
+
+    if ssh {
+        if let Some(path) = env::var_os("DEP_SSH2_INCLUDE") {
+            cfg.include(path);
+        }
+        features.push_str("#define GIT_SSH 1\n");
+        features.push_str("#define GIT_SSH_LIBSSH2 1\n");
+        features.push_str("#define GIT_SSH_LIBSSH2_MEMORY_CREDENTIALS 1\n");
+    }
+    if https {
+        features.push_str("#define GIT_HTTPS 1\n");
+
+        if windows {
+            features.push_str("#define GIT_WINHTTP 1\n");
+        } else if target.contains("apple") {
+            features.push_str("#define GIT_SECURE_TRANSPORT 1\n");
+        } else {
+            features.push_str("#define GIT_OPENSSL 1\n");
+            if let Some(path) = env::var_os("DEP_OPENSSL_INCLUDE") {
+                cfg.include(path);
+            }
+        }
+    }
+
+    // Use the CollisionDetection SHA1 implementation.
+    features.push_str("#define GIT_SHA1_COLLISIONDETECT 1\n");
+    cfg.define("SHA1DC_NO_STANDARD_INCLUDES", "1");
+    cfg.define("SHA1DC_CUSTOM_INCLUDE_SHA1_C", "\"common.h\"");
+    cfg.define("SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C", "\"common.h\"");
+    cfg.file("libgit2/src/util/hash/collisiondetect.c");
+    cfg.file("libgit2/src/util/hash/sha1dc/sha1.c");
+    cfg.file("libgit2/src/util/hash/sha1dc/ubc_check.c");
+
+    if https {
+        if windows {
+            features.push_str("#define GIT_SHA256_WIN32 1\n");
+            cfg.file("libgit2/src/util/hash/win32.c");
+        } else if target.contains("apple") {
+            features.push_str("#define GIT_SHA256_COMMON_CRYPTO 1\n");
+            cfg.file("libgit2/src/util/hash/common_crypto.c");
+        } else {
+            features.push_str("#define GIT_SHA256_OPENSSL 1\n");
+            cfg.file("libgit2/src/util/hash/openssl.c");
+        }
+    } else {
+        features.push_str("#define GIT_SHA256_BUILTIN 1\n");
+        cfg.file("libgit2/src/util/hash/builtin.c");
+        cfg.file("libgit2/src/util/hash/rfc6234/sha224-256.c");
+    }
+
+    if let Some(path) = env::var_os("DEP_Z_INCLUDE") {
+        cfg.include(path);
+    }
+
+    if target.contains("apple") {
+        features.push_str("#define GIT_USE_ICONV 1\n");
+    }
+
+    features.push_str("#endif\n");
+    fs::write(include.join("git2_features.h"), features).unwrap();
+
+    cfg.compile("git2");
+
+    println!("cargo:root={}", dst.display());
+
+    if target.contains("windows") {
+        println!("cargo:rustc-link-lib=winhttp");
+        println!("cargo:rustc-link-lib=rpcrt4");
+        println!("cargo:rustc-link-lib=ole32");
+        println!("cargo:rustc-link-lib=crypt32");
+        println!("cargo:rustc-link-lib=secur32");
+    }
+
+    if target.contains("apple") {
+        println!("cargo:rustc-link-lib=iconv");
+        println!("cargo:rustc-link-lib=framework=Security");
+        println!("cargo:rustc-link-lib=framework=CoreFoundation");
+    }
+
+    println!("cargo:rerun-if-changed=libgit2/include");
+    println!("cargo:rerun-if-changed=libgit2/src");
+    println!("cargo:rerun-if-changed=libgit2/deps");
+}
+
+fn cp_r(from: impl AsRef<Path>, to: impl AsRef<Path>) {
+    for e in from.as_ref().read_dir().unwrap() {
+        let e = e.unwrap();
+        let from = e.path();
+        let to = to.as_ref().join(e.file_name());
+        if e.file_type().unwrap().is_dir() {
+            fs::create_dir_all(&to).unwrap();
+            cp_r(&from, &to);
+        } else {
+            println!("{} => {}", from.display(), to.display());
+            fs::copy(&from, &to).unwrap();
+        }
+    }
+}
+
+fn add_c_files(build: &mut cc::Build, path: impl AsRef<Path>) {
+    let path = path.as_ref();
+    if !path.exists() {
+        panic!("Path {} does not exist", path.display());
+    }
+    // sort the C files to ensure a deterministic build for reproducible builds
+    let dir = path.read_dir().unwrap();
+    let mut paths = dir.collect::<io::Result<Vec<_>>>().unwrap();
+    paths.sort_by_key(|e| e.path());
+
+    for e in paths {
+        let path = e.path();
+        if e.file_type().unwrap().is_dir() {
+            // skip dirs for now
+        } else if path.extension().and_then(|s| s.to_str()) == Some("c") {
+            build.file(&path);
+        }
+    }
+}
diff --git a/libgit2-sys/lib.rs b/libgit2-sys/lib.rs
new file mode 100644 (file)
index 0000000..bf0f107
--- /dev/null
@@ -0,0 +1,4367 @@
+#![doc(html_root_url = "https://docs.rs/libgit2-sys/0.18")]
+#![allow(non_camel_case_types, unused_extern_crates)]
+
+// This is required to link libz when libssh2-sys is not included.
+extern crate libz_sys as libz;
+
+use libc::{c_char, c_int, c_uchar, c_uint, c_void, size_t};
+#[cfg(feature = "ssh")]
+use libssh2_sys as libssh2;
+use std::ffi::CStr;
+
+pub const GIT_OID_RAWSZ: usize = 20;
+pub const GIT_OID_HEXSZ: usize = GIT_OID_RAWSZ * 2;
+pub const GIT_CLONE_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_STASH_APPLY_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_CHECKOUT_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_MERGE_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_REMOTE_CALLBACKS_VERSION: c_uint = 1;
+pub const GIT_STATUS_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_BLAME_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_PROXY_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_SUBMODULE_UPDATE_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_ODB_BACKEND_VERSION: c_uint = 1;
+pub const GIT_REFDB_BACKEND_VERSION: c_uint = 1;
+pub const GIT_CHERRYPICK_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_APPLY_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_REVERT_OPTIONS_VERSION: c_uint = 1;
+pub const GIT_INDEXER_OPTIONS_VERSION: c_uint = 1;
+
+macro_rules! git_enum {
+    (pub enum $name:ident { $($variants:tt)* }) => {
+        #[cfg(target_env = "msvc")]
+        pub type $name = i32;
+        #[cfg(not(target_env = "msvc"))]
+        pub type $name = u32;
+        git_enum!(gen, $name, 0, $($variants)*);
+    };
+    (pub enum $name:ident: $t:ty { $($variants:tt)* }) => {
+        pub type $name = $t;
+        git_enum!(gen, $name, 0, $($variants)*);
+    };
+    (gen, $name:ident, $val:expr, $variant:ident, $($rest:tt)*) => {
+        pub const $variant: $name = $val;
+        git_enum!(gen, $name, $val+1, $($rest)*);
+    };
+    (gen, $name:ident, $val:expr, $variant:ident = $e:expr, $($rest:tt)*) => {
+        pub const $variant: $name = $e;
+        git_enum!(gen, $name, $e+1, $($rest)*);
+    };
+    (gen, $name:ident, $val:expr, ) => {}
+}
+
+pub enum git_blob {}
+pub enum git_branch_iterator {}
+pub enum git_blame {}
+pub enum git_commit {}
+pub enum git_config {}
+pub enum git_config_iterator {}
+pub enum git_index {}
+pub enum git_index_conflict_iterator {}
+pub enum git_object {}
+pub enum git_reference {}
+pub enum git_reference_iterator {}
+pub enum git_annotated_commit {}
+pub enum git_refdb {}
+pub enum git_refspec {}
+pub enum git_remote {}
+pub enum git_repository {}
+pub enum git_revwalk {}
+pub enum git_submodule {}
+pub enum git_tag {}
+pub enum git_tree {}
+pub enum git_tree_entry {}
+pub enum git_treebuilder {}
+pub enum git_push {}
+pub enum git_note {}
+pub enum git_note_iterator {}
+pub enum git_status_list {}
+pub enum git_pathspec {}
+pub enum git_pathspec_match_list {}
+pub enum git_diff {}
+pub enum git_diff_stats {}
+pub enum git_patch {}
+pub enum git_rebase {}
+pub enum git_reflog {}
+pub enum git_reflog_entry {}
+pub enum git_describe_result {}
+pub enum git_packbuilder {}
+pub enum git_odb {}
+pub enum git_odb_stream {}
+pub enum git_odb_object {}
+pub enum git_worktree {}
+pub enum git_transaction {}
+pub enum git_mailmap {}
+pub enum git_indexer {}
+
+#[repr(C)]
+pub struct git_revspec {
+    pub from: *mut git_object,
+    pub to: *mut git_object,
+    pub flags: c_uint,
+}
+
+#[repr(C)]
+pub struct git_error {
+    pub message: *mut c_char,
+    pub klass: c_int,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_oid {
+    pub id: [u8; GIT_OID_RAWSZ],
+}
+
+#[repr(C)]
+#[derive(Copy)]
+pub struct git_strarray {
+    pub strings: *mut *mut c_char,
+    pub count: size_t,
+}
+impl Clone for git_strarray {
+    fn clone(&self) -> git_strarray {
+        *self
+    }
+}
+
+#[repr(C)]
+#[derive(Copy)]
+pub struct git_oidarray {
+    pub ids: *mut git_oid,
+    pub count: size_t,
+}
+impl Clone for git_oidarray {
+    fn clone(&self) -> git_oidarray {
+        *self
+    }
+}
+
+#[repr(C)]
+pub struct git_signature {
+    pub name: *mut c_char,
+    pub email: *mut c_char,
+    pub when: git_time,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct git_time {
+    pub time: git_time_t,
+    pub offset: c_int,
+    pub sign: c_char,
+}
+
+pub type git_off_t = i64;
+pub type git_time_t = i64;
+pub type git_object_size_t = u64;
+
+git_enum! {
+    pub enum git_revparse_mode_t {
+        GIT_REVPARSE_SINGLE = 1 << 0,
+        GIT_REVPARSE_RANGE = 1 << 1,
+        GIT_REVPARSE_MERGE_BASE = 1 << 2,
+    }
+}
+
+git_enum! {
+    pub enum git_error_code: c_int {
+        GIT_OK = 0,
+
+        GIT_ERROR = -1,
+        GIT_ENOTFOUND = -3,
+        GIT_EEXISTS = -4,
+        GIT_EAMBIGUOUS = -5,
+        GIT_EBUFS = -6,
+        GIT_EUSER = -7,
+        GIT_EBAREREPO = -8,
+        GIT_EUNBORNBRANCH = -9,
+        GIT_EUNMERGED = -10,
+        GIT_ENONFASTFORWARD = -11,
+        GIT_EINVALIDSPEC = -12,
+        GIT_ECONFLICT = -13,
+        GIT_ELOCKED = -14,
+        GIT_EMODIFIED = -15,
+        GIT_EAUTH = -16,
+        GIT_ECERTIFICATE = -17,
+        GIT_EAPPLIED = -18,
+        GIT_EPEEL = -19,
+        GIT_EEOF = -20,
+        GIT_EINVALID = -21,
+        GIT_EUNCOMMITTED = -22,
+        GIT_EDIRECTORY = -23,
+        GIT_EMERGECONFLICT = -24,
+        GIT_PASSTHROUGH = -30,
+        GIT_ITEROVER = -31,
+        GIT_RETRY = -32,
+        GIT_EMISMATCH = -33,
+        GIT_EINDEXDIRTY = -34,
+        GIT_EAPPLYFAIL = -35,
+        GIT_EOWNER = -36,
+        GIT_TIMEOUT = -37,
+        GIT_EUNCHANGED = -38,
+        GIT_ENOTSUPPORTED = -39,
+        GIT_EREADONLY = -40,
+    }
+}
+
+git_enum! {
+    pub enum git_error_t {
+        GIT_ERROR_NONE = 0,
+        GIT_ERROR_NOMEMORY,
+        GIT_ERROR_OS,
+        GIT_ERROR_INVALID,
+        GIT_ERROR_REFERENCE,
+        GIT_ERROR_ZLIB,
+        GIT_ERROR_REPOSITORY,
+        GIT_ERROR_CONFIG,
+        GIT_ERROR_REGEX,
+        GIT_ERROR_ODB,
+        GIT_ERROR_INDEX,
+        GIT_ERROR_OBJECT,
+        GIT_ERROR_NET,
+        GIT_ERROR_TAG,
+        GIT_ERROR_TREE,
+        GIT_ERROR_INDEXER,
+        GIT_ERROR_SSL,
+        GIT_ERROR_SUBMODULE,
+        GIT_ERROR_THREAD,
+        GIT_ERROR_STASH,
+        GIT_ERROR_CHECKOUT,
+        GIT_ERROR_FETCHHEAD,
+        GIT_ERROR_MERGE,
+        GIT_ERROR_SSH,
+        GIT_ERROR_FILTER,
+        GIT_ERROR_REVERT,
+        GIT_ERROR_CALLBACK,
+        GIT_ERROR_CHERRYPICK,
+        GIT_ERROR_DESCRIBE,
+        GIT_ERROR_REBASE,
+        GIT_ERROR_FILESYSTEM,
+        GIT_ERROR_PATCH,
+        GIT_ERROR_WORKTREE,
+        GIT_ERROR_SHA1,
+        GIT_ERROR_HTTP,
+    }
+}
+
+git_enum! {
+    pub enum git_repository_state_t {
+        GIT_REPOSITORY_STATE_NONE,
+        GIT_REPOSITORY_STATE_MERGE,
+        GIT_REPOSITORY_STATE_REVERT,
+        GIT_REPOSITORY_STATE_REVERT_SEQUENCE,
+        GIT_REPOSITORY_STATE_CHERRYPICK,
+        GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE,
+        GIT_REPOSITORY_STATE_BISECT,
+        GIT_REPOSITORY_STATE_REBASE,
+        GIT_REPOSITORY_STATE_REBASE_INTERACTIVE,
+        GIT_REPOSITORY_STATE_REBASE_MERGE,
+        GIT_REPOSITORY_STATE_APPLY_MAILBOX,
+        GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE,
+    }
+}
+
+git_enum! {
+    pub enum git_direction {
+        GIT_DIRECTION_FETCH,
+        GIT_DIRECTION_PUSH,
+    }
+}
+
+#[repr(C)]
+pub struct git_clone_options {
+    pub version: c_uint,
+    pub checkout_opts: git_checkout_options,
+    pub fetch_opts: git_fetch_options,
+    pub bare: c_int,
+    pub local: git_clone_local_t,
+    pub checkout_branch: *const c_char,
+    pub repository_cb: git_repository_create_cb,
+    pub repository_cb_payload: *mut c_void,
+    pub remote_cb: git_remote_create_cb,
+    pub remote_cb_payload: *mut c_void,
+}
+
+git_enum! {
+    pub enum git_clone_local_t {
+        GIT_CLONE_LOCAL_AUTO,
+        GIT_CLONE_LOCAL,
+        GIT_CLONE_NO_LOCAL,
+        GIT_CLONE_LOCAL_NO_LINKS,
+    }
+}
+
+#[repr(C)]
+pub struct git_checkout_options {
+    pub version: c_uint,
+    pub checkout_strategy: c_uint,
+    pub disable_filters: c_int,
+    pub dir_mode: c_uint,
+    pub file_mode: c_uint,
+    pub file_open_flags: c_int,
+    pub notify_flags: c_uint,
+    pub notify_cb: git_checkout_notify_cb,
+    pub notify_payload: *mut c_void,
+    pub progress_cb: git_checkout_progress_cb,
+    pub progress_payload: *mut c_void,
+    pub paths: git_strarray,
+    pub baseline: *mut git_tree,
+    pub baseline_index: *mut git_index,
+    pub target_directory: *const c_char,
+    pub ancestor_label: *const c_char,
+    pub our_label: *const c_char,
+    pub their_label: *const c_char,
+    pub perfdata_cb: git_checkout_perfdata_cb,
+    pub perfdata_payload: *mut c_void,
+}
+
+pub type git_checkout_notify_cb = Option<
+    extern "C" fn(
+        git_checkout_notify_t,
+        *const c_char,
+        *const git_diff_file,
+        *const git_diff_file,
+        *const git_diff_file,
+        *mut c_void,
+    ) -> c_int,
+>;
+pub type git_checkout_progress_cb =
+    Option<extern "C" fn(*const c_char, size_t, size_t, *mut c_void)>;
+
+pub type git_checkout_perfdata_cb =
+    Option<extern "C" fn(*const git_checkout_perfdata, *mut c_void)>;
+
+#[repr(C)]
+pub struct git_checkout_perfdata {
+    pub mkdir_calls: size_t,
+    pub stat_calls: size_t,
+    pub chmod_calls: size_t,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct git_indexer_progress {
+    pub total_objects: c_uint,
+    pub indexed_objects: c_uint,
+    pub received_objects: c_uint,
+    pub local_objects: c_uint,
+    pub total_deltas: c_uint,
+    pub indexed_deltas: c_uint,
+    pub received_bytes: size_t,
+}
+
+pub type git_indexer_progress_cb =
+    Option<extern "C" fn(*const git_indexer_progress, *mut c_void) -> c_int>;
+
+#[deprecated(
+    since = "0.10.0",
+    note = "renamed to `git_indexer_progress` to match upstream"
+)]
+pub type git_transfer_progress = git_indexer_progress;
+
+#[repr(C)]
+pub struct git_indexer_options {
+    pub version: c_uint,
+    pub progress_cb: git_indexer_progress_cb,
+    pub progress_cb_payload: *mut c_void,
+    pub verify: c_uchar,
+}
+
+pub type git_remote_ready_cb = Option<extern "C" fn(*mut git_remote, c_int, *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_remote_update_flags {
+        GIT_REMOTE_UPDATE_FETCHHEAD = 1 << 0,
+        GIT_REMOTE_UPDATE_REPORT_UNCHANGED = 1 << 1,
+    }
+}
+
+#[repr(C)]
+pub struct git_remote_callbacks {
+    pub version: c_uint,
+    pub sideband_progress: git_transport_message_cb,
+    pub completion: Option<extern "C" fn(git_remote_completion_type, *mut c_void) -> c_int>,
+    pub credentials: git_cred_acquire_cb,
+    pub certificate_check: git_transport_certificate_check_cb,
+    pub transfer_progress: git_indexer_progress_cb,
+    pub update_tips:
+        Option<extern "C" fn(*const c_char, *const git_oid, *const git_oid, *mut c_void) -> c_int>,
+    pub pack_progress: git_packbuilder_progress,
+    pub push_transfer_progress: git_push_transfer_progress,
+    pub push_update_reference: git_push_update_reference_cb,
+    pub push_negotiation: git_push_negotiation,
+    pub transport: git_transport_cb,
+    pub remote_ready: git_remote_ready_cb,
+    pub payload: *mut c_void,
+    pub resolve_url: git_url_resolve_cb,
+    pub update_refs: Option<
+        extern "C" fn(
+            *const c_char,
+            *const git_oid,
+            *const git_oid,
+            *mut git_refspec,
+            *mut c_void,
+        ) -> c_int,
+    >,
+}
+
+#[repr(C)]
+pub struct git_fetch_options {
+    pub version: c_int,
+    pub callbacks: git_remote_callbacks,
+    pub prune: git_fetch_prune_t,
+    pub update_fetchhead: c_uint,
+    pub download_tags: git_remote_autotag_option_t,
+    pub proxy_opts: git_proxy_options,
+    pub depth: c_int,
+    pub follow_redirects: git_remote_redirect_t,
+    pub custom_headers: git_strarray,
+}
+
+#[repr(C)]
+pub struct git_fetch_negotiation {
+    refs: *const *const git_remote_head,
+    refs_len: size_t,
+    shallow_roots: *mut git_oid,
+    shallow_roots_len: size_t,
+    depth: c_int,
+}
+
+git_enum! {
+    pub enum git_remote_autotag_option_t {
+        GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED,
+        GIT_REMOTE_DOWNLOAD_TAGS_AUTO,
+        GIT_REMOTE_DOWNLOAD_TAGS_NONE,
+        GIT_REMOTE_DOWNLOAD_TAGS_ALL,
+    }
+}
+
+git_enum! {
+    pub enum git_fetch_prune_t {
+        GIT_FETCH_PRUNE_UNSPECIFIED,
+        GIT_FETCH_PRUNE,
+        GIT_FETCH_NO_PRUNE,
+    }
+}
+
+git_enum! {
+    pub enum git_remote_completion_type {
+        GIT_REMOTE_COMPLETION_DOWNLOAD,
+        GIT_REMOTE_COMPLETION_INDEXING,
+        GIT_REMOTE_COMPLETION_ERROR,
+    }
+}
+
+pub type git_transport_message_cb =
+    Option<extern "C" fn(*const c_char, c_int, *mut c_void) -> c_int>;
+pub type git_cred_acquire_cb = Option<
+    extern "C" fn(*mut *mut git_cred, *const c_char, *const c_char, c_uint, *mut c_void) -> c_int,
+>;
+pub type git_transfer_progress_cb =
+    Option<extern "C" fn(*const git_indexer_progress, *mut c_void) -> c_int>;
+pub type git_packbuilder_progress =
+    Option<extern "C" fn(git_packbuilder_stage_t, c_uint, c_uint, *mut c_void) -> c_int>;
+pub type git_push_transfer_progress =
+    Option<extern "C" fn(c_uint, c_uint, size_t, *mut c_void) -> c_int>;
+pub type git_transport_certificate_check_cb =
+    Option<extern "C" fn(*mut git_cert, c_int, *const c_char, *mut c_void) -> c_int>;
+pub type git_push_negotiation =
+    Option<extern "C" fn(*mut *const git_push_update, size_t, *mut c_void) -> c_int>;
+
+pub type git_push_update_reference_cb =
+    Option<extern "C" fn(*const c_char, *const c_char, *mut c_void) -> c_int>;
+pub type git_url_resolve_cb =
+    Option<extern "C" fn(*mut git_buf, *const c_char, c_int, *mut c_void) -> c_int>;
+
+#[repr(C)]
+pub struct git_push_update {
+    pub src_refname: *mut c_char,
+    pub dst_refname: *mut c_char,
+    pub src: git_oid,
+    pub dst: git_oid,
+}
+
+git_enum! {
+    pub enum git_cert_t {
+        GIT_CERT_NONE,
+        GIT_CERT_X509,
+        GIT_CERT_HOSTKEY_LIBSSH2,
+        GIT_CERT_STRARRAY,
+    }
+}
+
+#[repr(C)]
+pub struct git_cert {
+    pub cert_type: git_cert_t,
+}
+
+#[repr(C)]
+pub struct git_cert_hostkey {
+    pub parent: git_cert,
+    pub kind: git_cert_ssh_t,
+    pub hash_md5: [u8; 16],
+    pub hash_sha1: [u8; 20],
+    pub hash_sha256: [u8; 32],
+    pub raw_type: git_cert_ssh_raw_type_t,
+    pub hostkey: *const c_char,
+    pub hostkey_len: size_t,
+}
+
+#[repr(C)]
+pub struct git_cert_x509 {
+    pub parent: git_cert,
+    pub data: *mut c_void,
+    pub len: size_t,
+}
+
+git_enum! {
+    pub enum git_cert_ssh_t {
+        GIT_CERT_SSH_MD5 = 1 << 0,
+        GIT_CERT_SSH_SHA1 = 1 << 1,
+        GIT_CERT_SSH_SHA256 = 1 << 2,
+        GIT_CERT_SSH_RAW = 1 << 3,
+    }
+}
+
+git_enum! {
+    pub enum git_cert_ssh_raw_type_t {
+        GIT_CERT_SSH_RAW_TYPE_UNKNOWN = 0,
+        GIT_CERT_SSH_RAW_TYPE_RSA = 1,
+        GIT_CERT_SSH_RAW_TYPE_DSS = 2,
+        GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 = 3,
+        GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 = 4,
+        GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 = 5,
+        GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 = 6,
+    }
+}
+
+git_enum! {
+    pub enum git_diff_flag_t {
+        GIT_DIFF_FLAG_BINARY     = 1 << 0,
+        GIT_DIFF_FLAG_NOT_BINARY = 1 << 1,
+        GIT_DIFF_FLAG_VALID_ID   = 1 << 2,
+        GIT_DIFF_FLAG_EXISTS     = 1 << 3,
+    }
+}
+
+#[repr(C)]
+pub struct git_diff_file {
+    pub id: git_oid,
+    pub path: *const c_char,
+    pub size: git_object_size_t,
+    pub flags: u32,
+    pub mode: u16,
+    pub id_abbrev: u16,
+}
+
+pub type git_repository_create_cb =
+    Option<extern "C" fn(*mut *mut git_repository, *const c_char, c_int, *mut c_void) -> c_int>;
+pub type git_remote_create_cb = Option<
+    extern "C" fn(
+        *mut *mut git_remote,
+        *mut git_repository,
+        *const c_char,
+        *const c_char,
+        *mut c_void,
+    ) -> c_int,
+>;
+
+git_enum! {
+    pub enum git_checkout_notify_t {
+        GIT_CHECKOUT_NOTIFY_NONE = 0,
+        GIT_CHECKOUT_NOTIFY_CONFLICT = 1 << 0,
+        GIT_CHECKOUT_NOTIFY_DIRTY = 1 << 1,
+        GIT_CHECKOUT_NOTIFY_UPDATED = 1 << 2,
+        GIT_CHECKOUT_NOTIFY_UNTRACKED = 1 << 3,
+        GIT_CHECKOUT_NOTIFY_IGNORED = 1 << 4,
+
+        GIT_CHECKOUT_NOTIFY_ALL = 0x0FFFF,
+    }
+}
+
+git_enum! {
+    pub enum git_status_t {
+        GIT_STATUS_CURRENT = 0,
+
+        GIT_STATUS_INDEX_NEW = 1 << 0,
+        GIT_STATUS_INDEX_MODIFIED = 1 << 1,
+        GIT_STATUS_INDEX_DELETED = 1 << 2,
+        GIT_STATUS_INDEX_RENAMED = 1 << 3,
+        GIT_STATUS_INDEX_TYPECHANGE = 1 << 4,
+
+        GIT_STATUS_WT_NEW = 1 << 7,
+        GIT_STATUS_WT_MODIFIED = 1 << 8,
+        GIT_STATUS_WT_DELETED = 1 << 9,
+        GIT_STATUS_WT_TYPECHANGE = 1 << 10,
+        GIT_STATUS_WT_RENAMED = 1 << 11,
+        GIT_STATUS_WT_UNREADABLE = 1 << 12,
+
+        GIT_STATUS_IGNORED = 1 << 14,
+        GIT_STATUS_CONFLICTED = 1 << 15,
+    }
+}
+
+git_enum! {
+    pub enum git_status_opt_t {
+        GIT_STATUS_OPT_INCLUDE_UNTRACKED                = 1 << 0,
+        GIT_STATUS_OPT_INCLUDE_IGNORED                  = 1 << 1,
+        GIT_STATUS_OPT_INCLUDE_UNMODIFIED               = 1 << 2,
+        GIT_STATUS_OPT_EXCLUDE_SUBMODULES               = 1 << 3,
+        GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS           = 1 << 4,
+        GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH           = 1 << 5,
+        GIT_STATUS_OPT_RECURSE_IGNORED_DIRS             = 1 << 6,
+        GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX            = 1 << 7,
+        GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR         = 1 << 8,
+        GIT_STATUS_OPT_SORT_CASE_SENSITIVELY            = 1 << 9,
+        GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY          = 1 << 10,
+
+        GIT_STATUS_OPT_RENAMES_FROM_REWRITES            = 1 << 11,
+        GIT_STATUS_OPT_NO_REFRESH                       = 1 << 12,
+        GIT_STATUS_OPT_UPDATE_INDEX                     = 1 << 13,
+        GIT_STATUS_OPT_INCLUDE_UNREADABLE               = 1 << 14,
+        GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED  = 1 << 15,
+    }
+}
+
+git_enum! {
+    pub enum git_status_show_t {
+        GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0,
+        GIT_STATUS_SHOW_INDEX_ONLY = 1,
+        GIT_STATUS_SHOW_WORKDIR_ONLY = 2,
+    }
+}
+
+git_enum! {
+    pub enum git_delta_t {
+        GIT_DELTA_UNMODIFIED,
+        GIT_DELTA_ADDED,
+        GIT_DELTA_DELETED,
+        GIT_DELTA_MODIFIED,
+        GIT_DELTA_RENAMED,
+        GIT_DELTA_COPIED,
+        GIT_DELTA_IGNORED,
+        GIT_DELTA_UNTRACKED,
+        GIT_DELTA_TYPECHANGE,
+        GIT_DELTA_UNREADABLE,
+        GIT_DELTA_CONFLICTED,
+    }
+}
+
+#[repr(C)]
+pub struct git_status_options {
+    pub version: c_uint,
+    pub show: git_status_show_t,
+    pub flags: c_uint,
+    pub pathspec: git_strarray,
+    pub baseline: *mut git_tree,
+    pub rename_threshold: u16,
+}
+
+#[repr(C)]
+pub struct git_diff_delta {
+    pub status: git_delta_t,
+    pub flags: u32,
+    pub similarity: u16,
+    pub nfiles: u16,
+    pub old_file: git_diff_file,
+    pub new_file: git_diff_file,
+}
+
+#[repr(C)]
+pub struct git_status_entry {
+    pub status: git_status_t,
+    pub head_to_index: *mut git_diff_delta,
+    pub index_to_workdir: *mut git_diff_delta,
+}
+
+git_enum! {
+    pub enum git_checkout_strategy_t {
+        GIT_CHECKOUT_SAFE = 0,
+        GIT_CHECKOUT_FORCE = 1 << 1,
+        GIT_CHECKOUT_RECREATE_MISSING = 1 << 2,
+        GIT_CHECKOUT_ALLOW_CONFLICTS = 1 << 4,
+        GIT_CHECKOUT_REMOVE_UNTRACKED = 1 << 5,
+        GIT_CHECKOUT_REMOVE_IGNORED = 1 << 6,
+        GIT_CHECKOUT_UPDATE_ONLY = 1 << 7,
+        GIT_CHECKOUT_DONT_UPDATE_INDEX = 1 << 8,
+        GIT_CHECKOUT_NO_REFRESH = 1 << 9,
+        GIT_CHECKOUT_SKIP_UNMERGED = 1 << 10,
+        GIT_CHECKOUT_USE_OURS = 1 << 11,
+        GIT_CHECKOUT_USE_THEIRS = 1 << 12,
+        GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = 1 << 13,
+        GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = 1 << 18,
+        GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = 1 << 19,
+        GIT_CHECKOUT_CONFLICT_STYLE_MERGE = 1 << 20,
+        GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = 1 << 21,
+        GIT_CHECKOUT_NONE = 1 << 30,
+
+        GIT_CHECKOUT_UPDATE_SUBMODULES = 1 << 16,
+        GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = 1 << 17,
+    }
+}
+
+git_enum! {
+    pub enum git_reset_t {
+        GIT_RESET_SOFT = 1,
+        GIT_RESET_MIXED = 2,
+        GIT_RESET_HARD = 3,
+    }
+}
+
+git_enum! {
+    pub enum git_object_t: c_int {
+        GIT_OBJECT_ANY = -2,
+        GIT_OBJECT_INVALID = -1,
+        GIT_OBJECT_COMMIT = 1,
+        GIT_OBJECT_TREE = 2,
+        GIT_OBJECT_BLOB = 3,
+        GIT_OBJECT_TAG = 4,
+        GIT_OBJECT_OFS_DELTA = 6,
+        GIT_OBJECT_REF_DELTA = 7,
+    }
+}
+
+git_enum! {
+    pub enum git_reference_t {
+        GIT_REFERENCE_INVALID = 0,
+        GIT_REFERENCE_DIRECT = 1,
+        GIT_REFERENCE_SYMBOLIC = 2,
+        GIT_REFERENCE_ALL = GIT_REFERENCE_DIRECT | GIT_REFERENCE_SYMBOLIC,
+    }
+}
+
+git_enum! {
+    pub enum git_filemode_t {
+        GIT_FILEMODE_UNREADABLE = 0o000000,
+        GIT_FILEMODE_TREE = 0o040000,
+        GIT_FILEMODE_BLOB = 0o100644,
+        GIT_FILEMODE_BLOB_GROUP_WRITABLE = 0o100664,
+        GIT_FILEMODE_BLOB_EXECUTABLE = 0o100755,
+        GIT_FILEMODE_LINK = 0o120000,
+        GIT_FILEMODE_COMMIT = 0o160000,
+    }
+}
+
+git_enum! {
+    pub enum git_treewalk_mode {
+        GIT_TREEWALK_PRE = 0,
+        GIT_TREEWALK_POST = 1,
+    }
+}
+
+pub type git_treewalk_cb =
+    extern "C" fn(*const c_char, *const git_tree_entry, *mut c_void) -> c_int;
+pub type git_treebuilder_filter_cb =
+    Option<extern "C" fn(*const git_tree_entry, *mut c_void) -> c_int>;
+
+pub type git_revwalk_hide_cb = Option<extern "C" fn(*const git_oid, *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_tree_update_t {
+        GIT_TREE_UPDATE_UPSERT = 0,
+        GIT_TREE_UPDATE_REMOVE = 1,
+    }
+}
+
+#[repr(C)]
+pub struct git_tree_update {
+    pub action: git_tree_update_t,
+    pub id: git_oid,
+    pub filemode: git_filemode_t,
+    pub path: *const c_char,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_buf {
+    pub ptr: *mut c_char,
+    pub reserved: size_t,
+    pub size: size_t,
+}
+
+git_enum! {
+    pub enum git_branch_t {
+        GIT_BRANCH_LOCAL = 1,
+        GIT_BRANCH_REMOTE = 2,
+        GIT_BRANCH_ALL = GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE,
+    }
+}
+
+pub const GIT_BLAME_NORMAL: u32 = 0;
+pub const GIT_BLAME_TRACK_COPIES_SAME_FILE: u32 = 1 << 0;
+pub const GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES: u32 = 1 << 1;
+pub const GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES: u32 = 1 << 2;
+pub const GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES: u32 = 1 << 3;
+pub const GIT_BLAME_FIRST_PARENT: u32 = 1 << 4;
+pub const GIT_BLAME_USE_MAILMAP: u32 = 1 << 5;
+pub const GIT_BLAME_IGNORE_WHITESPACE: u32 = 1 << 6;
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_blame_options {
+    pub version: c_uint,
+
+    pub flags: u32,
+    pub min_match_characters: u16,
+    pub newest_commit: git_oid,
+    pub oldest_commit: git_oid,
+    pub min_line: usize,
+    pub max_line: usize,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_blame_hunk {
+    pub lines_in_hunk: usize,
+    pub final_commit_id: git_oid,
+    pub final_start_line_number: usize,
+    pub final_signature: *mut git_signature,
+    pub final_committer: *mut git_signature,
+    pub orig_commit_id: git_oid,
+    pub orig_path: *const c_char,
+    pub orig_start_line_number: usize,
+    pub orig_signature: *mut git_signature,
+    pub orig_committer: *mut git_signature,
+    pub summary: *const c_char,
+    pub boundary: c_char,
+}
+
+pub type git_index_matched_path_cb =
+    Option<extern "C" fn(*const c_char, *const c_char, *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_index_entry_extended_flag_t {
+        GIT_INDEX_ENTRY_INTENT_TO_ADD     = 1 << 13,
+        GIT_INDEX_ENTRY_SKIP_WORKTREE     = 1 << 14,
+
+        GIT_INDEX_ENTRY_UPTODATE          = 1 << 2,
+    }
+}
+
+git_enum! {
+    pub enum git_index_entry_flag_t {
+        GIT_INDEX_ENTRY_EXTENDED = 0x4000,
+        GIT_INDEX_ENTRY_VALID    = 0x8000,
+    }
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_index_entry {
+    pub ctime: git_index_time,
+    pub mtime: git_index_time,
+    pub dev: u32,
+    pub ino: u32,
+    pub mode: u32,
+    pub uid: u32,
+    pub gid: u32,
+    pub file_size: u32,
+    pub id: git_oid,
+    pub flags: u16,
+    pub flags_extended: u16,
+    pub path: *const c_char,
+}
+
+pub const GIT_INDEX_ENTRY_NAMEMASK: u16 = 0xfff;
+pub const GIT_INDEX_ENTRY_STAGEMASK: u16 = 0x3000;
+pub const GIT_INDEX_ENTRY_STAGESHIFT: u16 = 12;
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct git_index_time {
+    pub seconds: i32,
+    pub nanoseconds: u32,
+}
+
+#[repr(C)]
+pub struct git_config_entry {
+    pub name: *const c_char,
+    pub value: *const c_char,
+    pub backend_type: *const c_char,
+    pub origin_path: *const c_char,
+    pub include_depth: c_uint,
+    pub level: git_config_level_t,
+}
+
+git_enum! {
+    pub enum git_config_level_t: c_int {
+        GIT_CONFIG_LEVEL_PROGRAMDATA = 1,
+        GIT_CONFIG_LEVEL_SYSTEM = 2,
+        GIT_CONFIG_LEVEL_XDG = 3,
+        GIT_CONFIG_LEVEL_GLOBAL = 4,
+        GIT_CONFIG_LEVEL_LOCAL = 5,
+        GIT_CONFIG_LEVEL_WORKTREE = 6,
+        GIT_CONFIG_LEVEL_APP = 7,
+        GIT_CONFIG_HIGHEST_LEVEL = -1,
+    }
+}
+
+git_enum! {
+    pub enum git_submodule_update_t {
+        GIT_SUBMODULE_UPDATE_CHECKOUT = 1,
+        GIT_SUBMODULE_UPDATE_REBASE   = 2,
+        GIT_SUBMODULE_UPDATE_MERGE    = 3,
+        GIT_SUBMODULE_UPDATE_NONE     = 4,
+        GIT_SUBMODULE_UPDATE_DEFAULT  = 0,
+    }
+}
+
+git_enum! {
+    pub enum git_submodule_ignore_t: c_int {
+        GIT_SUBMODULE_IGNORE_UNSPECIFIED = -1,
+
+        GIT_SUBMODULE_IGNORE_NONE      = 1,
+        GIT_SUBMODULE_IGNORE_UNTRACKED = 2,
+        GIT_SUBMODULE_IGNORE_DIRTY     = 3,
+        GIT_SUBMODULE_IGNORE_ALL       = 4,
+    }
+}
+
+pub type git_submodule_cb =
+    Option<extern "C" fn(*mut git_submodule, *const c_char, *mut c_void) -> c_int>;
+
+#[repr(C)]
+pub struct git_submodule_update_options {
+    pub version: c_uint,
+    pub checkout_opts: git_checkout_options,
+    pub fetch_opts: git_fetch_options,
+    pub allow_fetch: c_int,
+}
+
+#[repr(C)]
+pub struct git_writestream {
+    pub write: Option<extern "C" fn(*mut git_writestream, *const c_char, size_t) -> c_int>,
+    pub close: Option<extern "C" fn(*mut git_writestream) -> c_int>,
+    pub free: Option<extern "C" fn(*mut git_writestream)>,
+}
+
+git_enum! {
+    pub enum git_attr_value_t {
+        GIT_ATTR_VALUE_UNSPECIFIED = 0,
+        GIT_ATTR_VALUE_TRUE,
+        GIT_ATTR_VALUE_FALSE,
+        GIT_ATTR_VALUE_STRING,
+    }
+}
+
+pub const GIT_ATTR_CHECK_FILE_THEN_INDEX: u32 = 0;
+pub const GIT_ATTR_CHECK_INDEX_THEN_FILE: u32 = 1;
+pub const GIT_ATTR_CHECK_INDEX_ONLY: u32 = 2;
+pub const GIT_ATTR_CHECK_NO_SYSTEM: u32 = 1 << 2;
+pub const GIT_ATTR_CHECK_INCLUDE_HEAD: u32 = 1 << 3;
+
+#[repr(C)]
+pub struct git_cred {
+    pub credtype: git_credtype_t,
+    pub free: Option<extern "C" fn(*mut git_cred)>,
+}
+
+git_enum! {
+    pub enum git_credtype_t {
+        GIT_CREDTYPE_USERPASS_PLAINTEXT = 1 << 0,
+        GIT_CREDTYPE_SSH_KEY = 1 << 1,
+        GIT_CREDTYPE_SSH_CUSTOM = 1 << 2,
+        GIT_CREDTYPE_DEFAULT = 1 << 3,
+        GIT_CREDTYPE_SSH_INTERACTIVE = 1 << 4,
+        GIT_CREDTYPE_USERNAME = 1 << 5,
+        GIT_CREDTYPE_SSH_MEMORY = 1 << 6,
+    }
+}
+
+pub type git_cred_ssh_interactive_callback = Option<
+    extern "C" fn(
+        name: *const c_char,
+        name_len: c_int,
+        instruction: *const c_char,
+        instruction_len: c_int,
+        num_prompts: c_int,
+        prompts: *const LIBSSH2_USERAUTH_KBDINT_PROMPT,
+        responses: *mut LIBSSH2_USERAUTH_KBDINT_RESPONSE,
+        abstrakt: *mut *mut c_void,
+    ),
+>;
+
+pub type git_cred_sign_callback = Option<
+    extern "C" fn(
+        session: *mut LIBSSH2_SESSION,
+        sig: *mut *mut c_uchar,
+        sig_len: *mut size_t,
+        data: *const c_uchar,
+        data_len: size_t,
+        abstrakt: *mut *mut c_void,
+    ),
+>;
+
+pub enum LIBSSH2_SESSION {}
+pub enum LIBSSH2_USERAUTH_KBDINT_PROMPT {}
+pub enum LIBSSH2_USERAUTH_KBDINT_RESPONSE {}
+
+#[repr(C)]
+pub struct git_push_options {
+    pub version: c_uint,
+    pub pb_parallelism: c_uint,
+    pub callbacks: git_remote_callbacks,
+    pub proxy_opts: git_proxy_options,
+    pub follow_redirects: git_remote_redirect_t,
+    pub custom_headers: git_strarray,
+    pub remote_push_options: git_strarray,
+}
+
+pub type git_tag_foreach_cb =
+    Option<extern "C" fn(name: *const c_char, oid: *mut git_oid, payload: *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_index_add_option_t {
+        GIT_INDEX_ADD_DEFAULT = 0,
+        GIT_INDEX_ADD_FORCE = 1 << 0,
+        GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH = 1 << 1,
+        GIT_INDEX_ADD_CHECK_PATHSPEC = 1 << 2,
+    }
+}
+
+git_enum! {
+    pub enum git_repository_open_flag_t {
+        GIT_REPOSITORY_OPEN_NO_SEARCH = 1 << 0,
+        GIT_REPOSITORY_OPEN_CROSS_FS = 1 << 1,
+        GIT_REPOSITORY_OPEN_BARE = 1 << 2,
+        GIT_REPOSITORY_OPEN_NO_DOTGIT = 1 << 3,
+        GIT_REPOSITORY_OPEN_FROM_ENV = 1 << 4,
+    }
+}
+
+#[repr(C)]
+pub struct git_repository_init_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub mode: u32,
+    pub workdir_path: *const c_char,
+    pub description: *const c_char,
+    pub template_path: *const c_char,
+    pub initial_head: *const c_char,
+    pub origin_url: *const c_char,
+}
+
+pub const GIT_REPOSITORY_INIT_OPTIONS_VERSION: c_uint = 1;
+
+git_enum! {
+    pub enum git_repository_init_flag_t {
+        GIT_REPOSITORY_INIT_BARE              = 1 << 0,
+        GIT_REPOSITORY_INIT_NO_REINIT         = 1 << 1,
+        GIT_REPOSITORY_INIT_NO_DOTGIT_DIR     = 1 << 2,
+        GIT_REPOSITORY_INIT_MKDIR             = 1 << 3,
+        GIT_REPOSITORY_INIT_MKPATH            = 1 << 4,
+        GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = 1 << 5,
+    }
+}
+
+git_enum! {
+    pub enum git_repository_init_mode_t {
+        GIT_REPOSITORY_INIT_SHARED_UMASK = 0,
+        GIT_REPOSITORY_INIT_SHARED_GROUP = 0o002775,
+        GIT_REPOSITORY_INIT_SHARED_ALL   = 0o002777,
+    }
+}
+
+git_enum! {
+    pub enum git_sort_t {
+        GIT_SORT_NONE        = 0,
+        GIT_SORT_TOPOLOGICAL = 1 << 0,
+        GIT_SORT_TIME        = 1 << 1,
+        GIT_SORT_REVERSE     = 1 << 2,
+    }
+}
+
+git_enum! {
+    pub enum git_submodule_status_t {
+        GIT_SUBMODULE_STATUS_IN_HEAD = 1 << 0,
+        GIT_SUBMODULE_STATUS_IN_INDEX = 1 << 1,
+        GIT_SUBMODULE_STATUS_IN_CONFIG = 1 << 2,
+        GIT_SUBMODULE_STATUS_IN_WD = 1 << 3,
+        GIT_SUBMODULE_STATUS_INDEX_ADDED = 1 << 4,
+        GIT_SUBMODULE_STATUS_INDEX_DELETED = 1 << 5,
+        GIT_SUBMODULE_STATUS_INDEX_MODIFIED = 1 << 6,
+        GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = 1 << 7,
+        GIT_SUBMODULE_STATUS_WD_ADDED = 1 << 8,
+        GIT_SUBMODULE_STATUS_WD_DELETED = 1 << 9,
+        GIT_SUBMODULE_STATUS_WD_MODIFIED = 1 << 10,
+        GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = 1 << 11,
+        GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = 1 << 12,
+        GIT_SUBMODULE_STATUS_WD_UNTRACKED = 1 << 13,
+    }
+}
+
+#[repr(C)]
+pub struct git_remote_head {
+    pub local: c_int,
+    pub oid: git_oid,
+    pub loid: git_oid,
+    pub name: *mut c_char,
+    pub symref_target: *mut c_char,
+}
+
+git_enum! {
+    pub enum git_pathspec_flag_t {
+        GIT_PATHSPEC_DEFAULT = 0,
+        GIT_PATHSPEC_IGNORE_CASE = 1 << 0,
+        GIT_PATHSPEC_USE_CASE = 1 << 1,
+        GIT_PATHSPEC_NO_GLOB = 1 << 2,
+        GIT_PATHSPEC_NO_MATCH_ERROR = 1 << 3,
+        GIT_PATHSPEC_FIND_FAILURES = 1 << 4,
+        GIT_PATHSPEC_FAILURES_ONLY = 1 << 5,
+    }
+}
+
+pub type git_diff_file_cb = Option<extern "C" fn(*const git_diff_delta, f32, *mut c_void) -> c_int>;
+pub type git_diff_hunk_cb =
+    Option<extern "C" fn(*const git_diff_delta, *const git_diff_hunk, *mut c_void) -> c_int>;
+pub type git_diff_line_cb = Option<
+    extern "C" fn(
+        *const git_diff_delta,
+        *const git_diff_hunk,
+        *const git_diff_line,
+        *mut c_void,
+    ) -> c_int,
+>;
+pub type git_diff_binary_cb =
+    Option<extern "C" fn(*const git_diff_delta, *const git_diff_binary, *mut c_void) -> c_int>;
+
+#[repr(C)]
+pub struct git_diff_hunk {
+    pub old_start: c_int,
+    pub old_lines: c_int,
+    pub new_start: c_int,
+    pub new_lines: c_int,
+    pub header_len: size_t,
+    pub header: [c_char; 128],
+}
+
+git_enum! {
+    pub enum git_diff_line_t {
+        GIT_DIFF_LINE_CONTEXT = b' ' as git_diff_line_t,
+        GIT_DIFF_LINE_ADDITION = b'+' as git_diff_line_t,
+        GIT_DIFF_LINE_DELETION = b'-' as git_diff_line_t,
+        GIT_DIFF_LINE_CONTEXT_EOFNL = b'=' as git_diff_line_t,
+        GIT_DIFF_LINE_ADD_EOFNL = b'>' as git_diff_line_t,
+        GIT_DIFF_LINE_DEL_EOFNL = b'<' as git_diff_line_t,
+        GIT_DIFF_LINE_FILE_HDR = b'F' as git_diff_line_t,
+        GIT_DIFF_LINE_HUNK_HDR = b'H' as git_diff_line_t,
+        GIT_DIFF_LINE_BINARY = b'B' as git_diff_line_t,
+    }
+}
+
+#[repr(C)]
+pub struct git_diff_line {
+    pub origin: c_char,
+    pub old_lineno: c_int,
+    pub new_lineno: c_int,
+    pub num_lines: c_int,
+    pub content_len: size_t,
+    pub content_offset: git_off_t,
+    pub content: *const c_char,
+}
+
+#[repr(C)]
+pub struct git_diff_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub ignore_submodules: git_submodule_ignore_t,
+    pub pathspec: git_strarray,
+    pub notify_cb: git_diff_notify_cb,
+    pub progress_cb: git_diff_progress_cb,
+    pub payload: *mut c_void,
+    pub context_lines: u32,
+    pub interhunk_lines: u32,
+    pub oid_type: git_oid_t,
+    pub id_abbrev: u16,
+    pub max_size: git_off_t,
+    pub old_prefix: *const c_char,
+    pub new_prefix: *const c_char,
+}
+
+git_enum! {
+    pub enum git_oid_t {
+        GIT_OID_SHA1 = 1,
+        // SHA256 is still experimental so we are not going to enable it.
+        /* GIT_OID_SHA256 = 2, */
+    }
+}
+
+git_enum! {
+    pub enum git_diff_format_t {
+        GIT_DIFF_FORMAT_PATCH = 1,
+        GIT_DIFF_FORMAT_PATCH_HEADER = 2,
+        GIT_DIFF_FORMAT_RAW = 3,
+        GIT_DIFF_FORMAT_NAME_ONLY = 4,
+        GIT_DIFF_FORMAT_NAME_STATUS = 5,
+        GIT_DIFF_FORMAT_PATCH_ID = 6,
+    }
+}
+
+git_enum! {
+    pub enum git_diff_stats_format_t {
+        GIT_DIFF_STATS_NONE = 0,
+        GIT_DIFF_STATS_FULL = 1 << 0,
+        GIT_DIFF_STATS_SHORT = 1 << 1,
+        GIT_DIFF_STATS_NUMBER = 1 << 2,
+        GIT_DIFF_STATS_INCLUDE_SUMMARY = 1 << 3,
+    }
+}
+
+pub type git_diff_notify_cb = Option<
+    extern "C" fn(*const git_diff, *const git_diff_delta, *const c_char, *mut c_void) -> c_int,
+>;
+
+pub type git_diff_progress_cb =
+    Option<extern "C" fn(*const git_diff, *const c_char, *const c_char, *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_diff_option_t {
+        GIT_DIFF_NORMAL = 0,
+        GIT_DIFF_REVERSE = 1 << 0,
+        GIT_DIFF_INCLUDE_IGNORED = 1 << 1,
+        GIT_DIFF_RECURSE_IGNORED_DIRS = 1 << 2,
+        GIT_DIFF_INCLUDE_UNTRACKED = 1 << 3,
+        GIT_DIFF_RECURSE_UNTRACKED_DIRS = 1 << 4,
+        GIT_DIFF_INCLUDE_UNMODIFIED = 1 << 5,
+        GIT_DIFF_INCLUDE_TYPECHANGE = 1 << 6,
+        GIT_DIFF_INCLUDE_TYPECHANGE_TREES = 1 << 7,
+        GIT_DIFF_IGNORE_FILEMODE = 1 << 8,
+        GIT_DIFF_IGNORE_SUBMODULES = 1 << 9,
+        GIT_DIFF_IGNORE_CASE = 1 << 10,
+        GIT_DIFF_DISABLE_PATHSPEC_MATCH = 1 << 12,
+        GIT_DIFF_SKIP_BINARY_CHECK = 1 << 13,
+        GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = 1 << 14,
+        GIT_DIFF_UPDATE_INDEX = 1 << 15,
+        GIT_DIFF_INCLUDE_UNREADABLE = 1 << 16,
+        GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = 1 << 17,
+        GIT_DIFF_INDENT_HEURISTIC = 1 << 18,
+        GIT_DIFF_IGNORE_BLANK_LINES = 1 << 19,
+        GIT_DIFF_FORCE_TEXT = 1 << 20,
+        GIT_DIFF_FORCE_BINARY = 1 << 21,
+        GIT_DIFF_IGNORE_WHITESPACE = 1 << 22,
+        GIT_DIFF_IGNORE_WHITESPACE_CHANGE = 1 << 23,
+        GIT_DIFF_IGNORE_WHITESPACE_EOL = 1 << 24,
+        GIT_DIFF_SHOW_UNTRACKED_CONTENT = 1 << 25,
+        GIT_DIFF_SHOW_UNMODIFIED = 1 << 26,
+        GIT_DIFF_PATIENCE = 1 << 28,
+        GIT_DIFF_MINIMAL = 1 << 29,
+        GIT_DIFF_SHOW_BINARY = 1 << 30,
+    }
+}
+
+#[repr(C)]
+pub struct git_diff_find_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub rename_threshold: u16,
+    pub rename_from_rewrite_threshold: u16,
+    pub copy_threshold: u16,
+    pub break_rewrite_threshold: u16,
+    pub rename_limit: size_t,
+    pub metric: *mut git_diff_similarity_metric,
+}
+
+#[repr(C)]
+pub struct git_diff_similarity_metric {
+    pub file_signature: Option<
+        extern "C" fn(*mut *mut c_void, *const git_diff_file, *const c_char, *mut c_void) -> c_int,
+    >,
+    pub buffer_signature: Option<
+        extern "C" fn(
+            *mut *mut c_void,
+            *const git_diff_file,
+            *const c_char,
+            size_t,
+            *mut c_void,
+        ) -> c_int,
+    >,
+    pub free_signature: Option<extern "C" fn(*mut c_void, *mut c_void)>,
+    pub similarity:
+        Option<extern "C" fn(*mut c_int, *mut c_void, *mut c_void, *mut c_void) -> c_int>,
+    pub payload: *mut c_void,
+}
+
+pub const GIT_DIFF_FIND_OPTIONS_VERSION: c_uint = 1;
+
+pub const GIT_DIFF_FIND_BY_CONFIG: u32 = 0;
+pub const GIT_DIFF_FIND_RENAMES: u32 = 1 << 0;
+pub const GIT_DIFF_FIND_RENAMES_FROM_REWRITES: u32 = 1 << 1;
+pub const GIT_DIFF_FIND_COPIES: u32 = 1 << 2;
+pub const GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED: u32 = 1 << 3;
+pub const GIT_DIFF_FIND_REWRITES: u32 = 1 << 4;
+pub const GIT_DIFF_BREAK_REWRITES: u32 = 1 << 5;
+pub const GIT_DIFF_FIND_AND_BREAK_REWRITES: u32 = GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES;
+pub const GIT_DIFF_FIND_FOR_UNTRACKED: u32 = 1 << 6;
+pub const GIT_DIFF_FIND_ALL: u32 = 0x0ff;
+pub const GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE: u32 = 0;
+pub const GIT_DIFF_FIND_IGNORE_WHITESPACE: u32 = 1 << 12;
+pub const GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE: u32 = 1 << 13;
+pub const GIT_DIFF_FIND_EXACT_MATCH_ONLY: u32 = 1 << 14;
+pub const GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY: u32 = 1 << 15;
+pub const GIT_DIFF_FIND_REMOVE_UNMODIFIED: u32 = 1 << 16;
+
+#[repr(C)]
+pub struct git_diff_format_email_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub patch_no: usize,
+    pub total_patches: usize,
+    pub id: *const git_oid,
+    pub summary: *const c_char,
+    pub body: *const c_char,
+    pub author: *const git_signature,
+}
+
+pub const GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION: c_uint = 1;
+
+pub const GIT_DIFF_FORMAT_EMAIL_NONE: u32 = 0;
+pub const GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER: u32 = 1 << 0;
+
+#[repr(C)]
+pub struct git_diff_patchid_options {
+    pub version: c_uint,
+}
+
+pub const GIT_DIFF_PATCHID_OPTIONS_VERSION: c_uint = 1;
+
+#[repr(C)]
+pub struct git_diff_binary {
+    pub contains_data: c_uint,
+    pub old_file: git_diff_binary_file,
+    pub new_file: git_diff_binary_file,
+}
+
+#[repr(C)]
+pub struct git_diff_binary_file {
+    pub kind: git_diff_binary_t,
+    pub data: *const c_char,
+    pub datalen: size_t,
+    pub inflatedlen: size_t,
+}
+
+git_enum! {
+    pub enum git_diff_binary_t {
+        GIT_DIFF_BINARY_NONE,
+        GIT_DIFF_BINARY_LITERAL,
+        GIT_DIFF_BINARY_DELTA,
+    }
+}
+
+#[repr(C)]
+pub struct git_merge_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub rename_threshold: c_uint,
+    pub target_limit: c_uint,
+    pub metric: *mut git_diff_similarity_metric,
+    pub recursion_limit: c_uint,
+    pub default_driver: *const c_char,
+    pub file_favor: git_merge_file_favor_t,
+    pub file_flags: u32,
+}
+
+git_enum! {
+    pub enum git_merge_flag_t {
+        GIT_MERGE_FIND_RENAMES = 1 << 0,
+        GIT_MERGE_FAIL_ON_CONFLICT = 1 << 1,
+        GIT_MERGE_SKIP_REUC = 1 << 2,
+        GIT_MERGE_NO_RECURSIVE = 1 << 3,
+    }
+}
+
+git_enum! {
+    pub enum git_merge_file_favor_t {
+        GIT_MERGE_FILE_FAVOR_NORMAL = 0,
+        GIT_MERGE_FILE_FAVOR_OURS = 1,
+        GIT_MERGE_FILE_FAVOR_THEIRS = 2,
+        GIT_MERGE_FILE_FAVOR_UNION = 3,
+    }
+}
+
+git_enum! {
+    pub enum git_merge_file_flag_t {
+        GIT_MERGE_FILE_DEFAULT = 0,
+        GIT_MERGE_FILE_STYLE_MERGE = 1 << 0,
+        GIT_MERGE_FILE_STYLE_DIFF3 = 1 << 1,
+        GIT_MERGE_FILE_SIMPLIFY_ALNUM = 1 << 2,
+        GIT_MERGE_FILE_IGNORE_WHITESPACE = 1 << 3,
+        GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE = 1 << 4,
+        GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = 1 << 5,
+        GIT_MERGE_FILE_DIFF_PATIENCE = 1 << 6,
+        GIT_MERGE_FILE_DIFF_MINIMAL = 1 << 7,
+    }
+}
+
+git_enum! {
+    pub enum git_merge_analysis_t {
+        GIT_MERGE_ANALYSIS_NONE = 0,
+        GIT_MERGE_ANALYSIS_NORMAL = 1 << 0,
+        GIT_MERGE_ANALYSIS_UP_TO_DATE = 1 << 1,
+        GIT_MERGE_ANALYSIS_FASTFORWARD = 1 << 2,
+        GIT_MERGE_ANALYSIS_UNBORN = 1 << 3,
+    }
+}
+
+git_enum! {
+    pub enum git_merge_preference_t {
+        GIT_MERGE_PREFERENCE_NONE = 0,
+        GIT_MERGE_PREFERENCE_NO_FASTFORWARD = 1 << 0,
+        GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = 1 << 1,
+    }
+}
+
+pub type git_transport_cb = Option<
+    extern "C" fn(
+        out: *mut *mut git_transport,
+        owner: *mut git_remote,
+        param: *mut c_void,
+    ) -> c_int,
+>;
+
+#[repr(C)]
+pub struct git_transport {
+    pub version: c_uint,
+    pub connect: Option<
+        extern "C" fn(
+            transport: *mut git_transport,
+            url: *const c_char,
+            direction: c_int,
+            connect_opts: *const git_remote_connect_options,
+        ) -> c_int,
+    >,
+    pub set_connect_opts: Option<
+        extern "C" fn(
+            transport: *mut git_transport,
+            connect_opts: *const git_remote_connect_options,
+        ) -> c_int,
+    >,
+    pub capabilities:
+        Option<extern "C" fn(capabilities: *mut c_uint, transport: *mut git_transport) -> c_int>,
+    pub ls: Option<
+        extern "C" fn(
+            out: *mut *mut *const git_remote_head,
+            size: *mut size_t,
+            transport: *mut git_transport,
+        ) -> c_int,
+    >,
+    pub push: Option<extern "C" fn(transport: *mut git_transport, push: *mut git_push) -> c_int>,
+    pub negotiate_fetch: Option<
+        extern "C" fn(
+            transport: *mut git_transport,
+            repo: *mut git_repository,
+            fetch_data: *const git_fetch_negotiation,
+        ) -> c_int,
+    >,
+    pub shallow_roots:
+        Option<extern "C" fn(out: *mut git_oidarray, transport: *mut git_transport) -> c_int>,
+    pub download_pack: Option<
+        extern "C" fn(
+            transport: *mut git_transport,
+            repo: *mut git_repository,
+            stats: *mut git_indexer_progress,
+        ) -> c_int,
+    >,
+    pub is_connected: Option<extern "C" fn(transport: *mut git_transport) -> c_int>,
+    pub cancel: Option<extern "C" fn(transport: *mut git_transport)>,
+    pub close: Option<extern "C" fn(transport: *mut git_transport) -> c_int>,
+    pub free: Option<extern "C" fn(transport: *mut git_transport)>,
+}
+
+#[repr(C)]
+pub struct git_remote_connect_options {
+    pub version: c_uint,
+    pub callbacks: git_remote_callbacks,
+    pub proxy_opts: git_proxy_options,
+    pub follow_redirects: git_remote_redirect_t,
+    pub custom_headers: git_strarray,
+}
+
+git_enum! {
+    pub enum git_remote_redirect_t {
+        GIT_REMOTE_REDIRECT_NONE = 1 << 0,
+        GIT_REMOTE_REDIRECT_INITIAL = 1 << 1,
+        GIT_REMOTE_REDIRECT_ALL = 1 << 2,
+    }
+}
+
+#[repr(C)]
+pub struct git_odb_backend {
+    pub version: c_uint,
+    pub odb: *mut git_odb,
+    pub read: Option<
+        extern "C" fn(
+            *mut *mut c_void,
+            *mut size_t,
+            *mut git_object_t,
+            *mut git_odb_backend,
+            *const git_oid,
+        ) -> c_int,
+    >,
+
+    pub read_prefix: Option<
+        extern "C" fn(
+            *mut git_oid,
+            *mut *mut c_void,
+            *mut size_t,
+            *mut git_object_t,
+            *mut git_odb_backend,
+            *const git_oid,
+            size_t,
+        ) -> c_int,
+    >,
+    pub read_header: Option<
+        extern "C" fn(
+            *mut size_t,
+            *mut git_object_t,
+            *mut git_odb_backend,
+            *const git_oid,
+        ) -> c_int,
+    >,
+
+    pub write: Option<
+        extern "C" fn(
+            *mut git_odb_backend,
+            *const git_oid,
+            *const c_void,
+            size_t,
+            git_object_t,
+        ) -> c_int,
+    >,
+
+    pub writestream: Option<
+        extern "C" fn(
+            *mut *mut git_odb_stream,
+            *mut git_odb_backend,
+            git_object_size_t,
+            git_object_t,
+        ) -> c_int,
+    >,
+
+    pub readstream: Option<
+        extern "C" fn(
+            *mut *mut git_odb_stream,
+            *mut size_t,
+            *mut git_object_t,
+            *mut git_odb_backend,
+            *const git_oid,
+        ) -> c_int,
+    >,
+
+    pub exists: Option<extern "C" fn(*mut git_odb_backend, *const git_oid) -> c_int>,
+
+    pub exists_prefix:
+        Option<extern "C" fn(*mut git_oid, *mut git_odb_backend, *const git_oid, size_t) -> c_int>,
+
+    pub refresh: Option<extern "C" fn(*mut git_odb_backend) -> c_int>,
+
+    pub foreach:
+        Option<extern "C" fn(*mut git_odb_backend, git_odb_foreach_cb, *mut c_void) -> c_int>,
+
+    pub writepack: Option<
+        extern "C" fn(
+            *mut *mut git_odb_writepack,
+            *mut git_odb_backend,
+            *mut git_odb,
+            git_indexer_progress_cb,
+            *mut c_void,
+        ) -> c_int,
+    >,
+
+    pub writemidx: Option<extern "C" fn(*mut git_odb_backend) -> c_int>,
+
+    pub freshen: Option<extern "C" fn(*mut git_odb_backend, *const git_oid) -> c_int>,
+
+    pub free: Option<extern "C" fn(*mut git_odb_backend)>,
+}
+
+git_enum! {
+    pub enum git_odb_lookup_flags_t {
+        GIT_ODB_LOOKUP_NO_REFRESH = 1 << 0,
+    }
+}
+
+#[repr(C)]
+pub struct git_odb_writepack {
+    pub backend: *mut git_odb_backend,
+
+    pub append: Option<
+        extern "C" fn(
+            *mut git_odb_writepack,
+            *const c_void,
+            size_t,
+            *mut git_indexer_progress,
+        ) -> c_int,
+    >,
+
+    pub commit:
+        Option<unsafe extern "C" fn(*mut git_odb_writepack, *mut git_indexer_progress) -> c_int>,
+
+    pub free: Option<unsafe extern "C" fn(*mut git_odb_writepack)>,
+}
+
+#[repr(C)]
+pub struct git_refdb_backend {
+    pub version: c_uint,
+    pub exists: Option<extern "C" fn(*mut c_int, *mut git_refdb_backend, *const c_char) -> c_int>,
+    pub lookup: Option<
+        extern "C" fn(*mut *mut git_reference, *mut git_refdb_backend, *const c_char) -> c_int,
+    >,
+    pub iterator: Option<
+        extern "C" fn(
+            *mut *mut git_reference_iterator,
+            *mut git_refdb_backend,
+            *const c_char,
+        ) -> c_int,
+    >,
+    pub write: Option<
+        extern "C" fn(
+            *mut git_refdb_backend,
+            *const git_reference,
+            c_int,
+            *const git_signature,
+            *const c_char,
+            *const git_oid,
+            *const c_char,
+        ) -> c_int,
+    >,
+    pub rename: Option<
+        extern "C" fn(
+            *mut *mut git_reference,
+            *mut git_refdb_backend,
+            *const c_char,
+            *const c_char,
+            c_int,
+            *const git_signature,
+            *const c_char,
+        ) -> c_int,
+    >,
+    pub del: Option<
+        extern "C" fn(
+            *mut git_refdb_backend,
+            *const c_char,
+            *const git_oid,
+            *const c_char,
+        ) -> c_int,
+    >,
+    pub compress: Option<extern "C" fn(*mut git_refdb_backend) -> c_int>,
+    pub has_log: Option<extern "C" fn(*mut git_refdb_backend, *const c_char) -> c_int>,
+    pub ensure_log: Option<extern "C" fn(*mut git_refdb_backend, *const c_char) -> c_int>,
+    pub free: Option<extern "C" fn(*mut git_refdb_backend)>,
+    pub reflog_read:
+        Option<extern "C" fn(*mut *mut git_reflog, *mut git_refdb_backend, *const c_char) -> c_int>,
+    pub reflog_write: Option<extern "C" fn(*mut git_refdb_backend, *mut git_reflog) -> c_int>,
+    pub reflog_rename:
+        Option<extern "C" fn(*mut git_refdb_backend, *const c_char, *const c_char) -> c_int>,
+    pub reflog_delete: Option<extern "C" fn(*mut git_refdb_backend, *const c_char) -> c_int>,
+    pub lock:
+        Option<extern "C" fn(*mut *mut c_void, *mut git_refdb_backend, *const c_char) -> c_int>,
+    pub unlock: Option<
+        extern "C" fn(
+            *mut git_refdb_backend,
+            *mut c_void,
+            c_int,
+            c_int,
+            *const git_reference,
+            *const git_signature,
+            *const c_char,
+        ) -> c_int,
+    >,
+}
+
+#[repr(C)]
+pub struct git_proxy_options {
+    pub version: c_uint,
+    pub kind: git_proxy_t,
+    pub url: *const c_char,
+    pub credentials: git_cred_acquire_cb,
+    pub certificate_check: git_transport_certificate_check_cb,
+    pub payload: *mut c_void,
+}
+
+git_enum! {
+    pub enum git_proxy_t {
+        GIT_PROXY_NONE = 0,
+        GIT_PROXY_AUTO = 1,
+        GIT_PROXY_SPECIFIED = 2,
+    }
+}
+
+git_enum! {
+    pub enum git_smart_service_t {
+        GIT_SERVICE_UPLOADPACK_LS = 1,
+        GIT_SERVICE_UPLOADPACK = 2,
+        GIT_SERVICE_RECEIVEPACK_LS = 3,
+        GIT_SERVICE_RECEIVEPACK = 4,
+    }
+}
+
+#[repr(C)]
+pub struct git_smart_subtransport_stream {
+    pub subtransport: *mut git_smart_subtransport,
+    pub read: Option<
+        extern "C" fn(
+            *mut git_smart_subtransport_stream,
+            *mut c_char,
+            size_t,
+            *mut size_t,
+        ) -> c_int,
+    >,
+    pub write:
+        Option<extern "C" fn(*mut git_smart_subtransport_stream, *const c_char, size_t) -> c_int>,
+    pub free: Option<extern "C" fn(*mut git_smart_subtransport_stream)>,
+}
+
+#[repr(C)]
+pub struct git_smart_subtransport {
+    pub action: Option<
+        extern "C" fn(
+            *mut *mut git_smart_subtransport_stream,
+            *mut git_smart_subtransport,
+            *const c_char,
+            git_smart_service_t,
+        ) -> c_int,
+    >,
+    pub close: Option<extern "C" fn(*mut git_smart_subtransport) -> c_int>,
+    pub free: Option<extern "C" fn(*mut git_smart_subtransport)>,
+}
+
+pub type git_smart_subtransport_cb = Option<
+    extern "C" fn(*mut *mut git_smart_subtransport, *mut git_transport, *mut c_void) -> c_int,
+>;
+
+#[repr(C)]
+pub struct git_smart_subtransport_definition {
+    pub callback: git_smart_subtransport_cb,
+    pub rpc: c_uint,
+    pub param: *mut c_void,
+}
+
+#[repr(C)]
+pub struct git_describe_options {
+    pub version: c_uint,
+    pub max_candidates_tags: c_uint,
+    pub describe_strategy: c_uint,
+    pub pattern: *const c_char,
+    pub only_follow_first_parent: c_int,
+    pub show_commit_oid_as_fallback: c_int,
+}
+
+git_enum! {
+    pub enum git_describe_strategy_t {
+        GIT_DESCRIBE_DEFAULT,
+        GIT_DESCRIBE_TAGS,
+        GIT_DESCRIBE_ALL,
+    }
+}
+
+#[repr(C)]
+pub struct git_describe_format_options {
+    pub version: c_uint,
+    pub abbreviated_size: c_uint,
+    pub always_use_long_format: c_int,
+    pub dirty_suffix: *const c_char,
+}
+
+git_enum! {
+    pub enum git_packbuilder_stage_t {
+        GIT_PACKBUILDER_ADDING_OBJECTS,
+        GIT_PACKBUILDER_DELTAFICATION,
+    }
+}
+
+git_enum! {
+    pub enum git_stash_flags {
+        GIT_STASH_DEFAULT = 0,
+        GIT_STASH_KEEP_INDEX = 1 << 0,
+        GIT_STASH_INCLUDE_UNTRACKED = 1 << 1,
+        GIT_STASH_INCLUDE_IGNORED = 1 << 2,
+        GIT_STASH_KEEP_ALL = 1 << 3,
+    }
+}
+
+git_enum! {
+    pub enum git_stash_apply_flags {
+        GIT_STASH_APPLY_DEFAULT = 0,
+        GIT_STASH_APPLY_REINSTATE_INDEX = 1 << 0,
+    }
+}
+
+git_enum! {
+    pub enum git_stash_apply_progress_t {
+        GIT_STASH_APPLY_PROGRESS_NONE = 0,
+        GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
+        GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
+        GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
+        GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
+        GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
+        GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
+        GIT_STASH_APPLY_PROGRESS_DONE,
+    }
+}
+
+#[repr(C)]
+pub struct git_stash_save_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub stasher: *const git_signature,
+    pub message: *const c_char,
+    pub paths: git_strarray,
+}
+
+pub const GIT_STASH_SAVE_OPTIONS_VERSION: c_uint = 1;
+
+#[repr(C)]
+pub struct git_stash_apply_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub checkout_options: git_checkout_options,
+    pub progress_cb: git_stash_apply_progress_cb,
+    pub progress_payload: *mut c_void,
+}
+
+pub type git_stash_apply_progress_cb =
+    Option<extern "C" fn(progress: git_stash_apply_progress_t, payload: *mut c_void) -> c_int>;
+
+pub type git_stash_cb = Option<
+    extern "C" fn(
+        index: size_t,
+        message: *const c_char,
+        stash_id: *const git_oid,
+        payload: *mut c_void,
+    ) -> c_int,
+>;
+
+pub type git_packbuilder_foreach_cb =
+    Option<extern "C" fn(*const c_void, size_t, *mut c_void) -> c_int>;
+
+pub type git_odb_foreach_cb =
+    Option<extern "C" fn(id: *const git_oid, payload: *mut c_void) -> c_int>;
+
+pub type git_commit_signing_cb = Option<
+    extern "C" fn(
+        signature: *mut git_buf,
+        signature_field: *mut git_buf,
+        commit_content: *const c_char,
+        payload: *mut c_void,
+    ) -> c_int,
+>;
+
+pub type git_commit_create_cb = Option<
+    extern "C" fn(
+        *mut git_oid,
+        *const git_signature,
+        *const git_signature,
+        *const c_char,
+        *const c_char,
+        *const git_tree,
+        usize,
+        *const git_commit,
+        *mut c_void,
+    ) -> c_int,
+>;
+
+pub const GIT_REBASE_NO_OPERATION: usize = usize::max_value();
+
+#[repr(C)]
+pub struct git_rebase_options {
+    pub version: c_uint,
+    pub quiet: c_int,
+    pub inmemory: c_int,
+    pub rewrite_notes_ref: *const c_char,
+    pub merge_options: git_merge_options,
+    pub checkout_options: git_checkout_options,
+    pub commit_create_cb: git_commit_create_cb,
+    pub signing_cb: git_commit_signing_cb,
+    pub payload: *mut c_void,
+}
+
+git_enum! {
+    pub enum git_rebase_operation_t {
+        GIT_REBASE_OPERATION_PICK = 0,
+        GIT_REBASE_OPERATION_REWORD,
+        GIT_REBASE_OPERATION_EDIT,
+        GIT_REBASE_OPERATION_SQUASH,
+        GIT_REBASE_OPERATION_FIXUP,
+        GIT_REBASE_OPERATION_EXEC,
+    }
+}
+
+#[repr(C)]
+pub struct git_rebase_operation {
+    pub kind: git_rebase_operation_t,
+    pub id: git_oid,
+    pub exec: *const c_char,
+}
+
+#[repr(C)]
+pub struct git_cherrypick_options {
+    pub version: c_uint,
+    pub mainline: c_uint,
+    pub merge_opts: git_merge_options,
+    pub checkout_opts: git_checkout_options,
+}
+
+pub type git_revert_options = git_cherrypick_options;
+
+pub type git_apply_delta_cb =
+    Option<extern "C" fn(delta: *const git_diff_delta, payload: *mut c_void) -> c_int>;
+
+pub type git_apply_hunk_cb =
+    Option<extern "C" fn(hunk: *const git_diff_hunk, payload: *mut c_void) -> c_int>;
+
+git_enum! {
+    pub enum git_apply_flags_t {
+        GIT_APPLY_CHECK = 1<<0,
+    }
+}
+
+#[repr(C)]
+pub struct git_apply_options {
+    pub version: c_uint,
+    pub delta_cb: git_apply_delta_cb,
+    pub hunk_cb: git_apply_hunk_cb,
+    pub payload: *mut c_void,
+    pub flags: u32,
+}
+
+git_enum! {
+    pub enum git_apply_location_t {
+        GIT_APPLY_LOCATION_WORKDIR = 0,
+        GIT_APPLY_LOCATION_INDEX = 1,
+        GIT_APPLY_LOCATION_BOTH = 2,
+    }
+}
+
+git_enum! {
+    pub enum git_libgit2_opt_t {
+        GIT_OPT_GET_MWINDOW_SIZE = 0,
+        GIT_OPT_SET_MWINDOW_SIZE,
+        GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,
+        GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
+        GIT_OPT_GET_SEARCH_PATH,
+        GIT_OPT_SET_SEARCH_PATH,
+        GIT_OPT_SET_CACHE_OBJECT_LIMIT,
+        GIT_OPT_SET_CACHE_MAX_SIZE,
+        GIT_OPT_ENABLE_CACHING,
+        GIT_OPT_GET_CACHED_MEMORY,
+        GIT_OPT_GET_TEMPLATE_PATH,
+        GIT_OPT_SET_TEMPLATE_PATH,
+        GIT_OPT_SET_SSL_CERT_LOCATIONS,
+        GIT_OPT_SET_USER_AGENT,
+        GIT_OPT_ENABLE_STRICT_OBJECT_CREATION,
+        GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION,
+        GIT_OPT_SET_SSL_CIPHERS,
+        GIT_OPT_GET_USER_AGENT,
+        GIT_OPT_ENABLE_OFS_DELTA,
+        GIT_OPT_ENABLE_FSYNC_GITDIR,
+        GIT_OPT_GET_WINDOWS_SHAREMODE,
+        GIT_OPT_SET_WINDOWS_SHAREMODE,
+        GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION,
+        GIT_OPT_SET_ALLOCATOR,
+        GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY,
+        GIT_OPT_GET_PACK_MAX_OBJECTS,
+        GIT_OPT_SET_PACK_MAX_OBJECTS,
+        GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS,
+        GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE,
+        GIT_OPT_GET_MWINDOW_FILE_LIMIT,
+        GIT_OPT_SET_MWINDOW_FILE_LIMIT,
+        GIT_OPT_SET_ODB_PACKED_PRIORITY,
+        GIT_OPT_SET_ODB_LOOSE_PRIORITY,
+        GIT_OPT_GET_EXTENSIONS,
+        GIT_OPT_SET_EXTENSIONS,
+        GIT_OPT_GET_OWNER_VALIDATION,
+        GIT_OPT_SET_OWNER_VALIDATION,
+        GIT_OPT_GET_HOMEDIR,
+        GIT_OPT_SET_HOMEDIR,
+        GIT_OPT_SET_SERVER_CONNECT_TIMEOUT,
+        GIT_OPT_GET_SERVER_CONNECT_TIMEOUT,
+        GIT_OPT_SET_SERVER_TIMEOUT,
+        GIT_OPT_GET_SERVER_TIMEOUT,
+        GIT_OPT_SET_USER_AGENT_PRODUCT,
+        GIT_OPT_GET_USER_AGENT_PRODUCT,
+    }
+}
+
+git_enum! {
+    pub enum git_reference_format_t {
+        GIT_REFERENCE_FORMAT_NORMAL = 0,
+        GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL = 1 << 0,
+        GIT_REFERENCE_FORMAT_REFSPEC_PATTERN = 1 << 1,
+        GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND = 1 << 2,
+    }
+}
+
+#[repr(C)]
+pub struct git_worktree_add_options {
+    pub version: c_uint,
+    pub lock: c_int,
+    pub checkout_existing: c_int,
+    pub reference: *mut git_reference,
+    pub checkout_options: git_checkout_options,
+}
+
+pub const GIT_WORKTREE_ADD_OPTIONS_VERSION: c_uint = 1;
+
+git_enum! {
+    pub enum git_worktree_prune_t {
+        /* Prune working tree even if working tree is valid */
+        GIT_WORKTREE_PRUNE_VALID = 1 << 0,
+        /* Prune working tree even if it is locked */
+        GIT_WORKTREE_PRUNE_LOCKED = 1 << 1,
+        /* Prune checked out working tree */
+        GIT_WORKTREE_PRUNE_WORKING_TREE = 1 << 2,
+    }
+}
+
+#[repr(C)]
+pub struct git_worktree_prune_options {
+    pub version: c_uint,
+    pub flags: u32,
+}
+
+pub const GIT_WORKTREE_PRUNE_OPTIONS_VERSION: c_uint = 1;
+
+pub type git_repository_mergehead_foreach_cb =
+    Option<extern "C" fn(oid: *const git_oid, payload: *mut c_void) -> c_int>;
+
+pub type git_repository_fetchhead_foreach_cb = Option<
+    extern "C" fn(*const c_char, *const c_char, *const git_oid, c_uint, *mut c_void) -> c_int,
+>;
+
+git_enum! {
+    pub enum git_trace_level_t {
+        /* No tracing will be performed. */
+        GIT_TRACE_NONE = 0,
+
+        /* Severe errors that may impact the program's execution */
+        GIT_TRACE_FATAL = 1,
+
+        /* Errors that do not impact the program's execution */
+        GIT_TRACE_ERROR = 2,
+
+        /* Warnings that suggest abnormal data */
+        GIT_TRACE_WARN = 3,
+
+        /* Informational messages about program execution */
+        GIT_TRACE_INFO = 4,
+
+        /* Detailed data that allows for debugging */
+        GIT_TRACE_DEBUG = 5,
+
+        /* Exceptionally detailed debugging data */
+        GIT_TRACE_TRACE = 6,
+    }
+}
+
+pub type git_trace_cb = Option<extern "C" fn(level: git_trace_level_t, msg: *const c_char)>;
+
+git_enum! {
+    pub enum git_feature_t {
+        GIT_FEATURE_THREADS = 1 << 0,
+        GIT_FEATURE_HTTPS   = 1 << 1,
+        GIT_FEATURE_SSH     = 1 << 2,
+        GIT_FEATURE_NSEC    = 1 << 3,
+    }
+}
+
+#[repr(C)]
+pub struct git_message_trailer {
+    pub key: *const c_char,
+    pub value: *const c_char,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct git_message_trailer_array {
+    pub trailers: *mut git_message_trailer,
+    pub count: size_t,
+    pub _trailer_block: *mut c_char,
+}
+
+#[repr(C)]
+pub struct git_email_create_options {
+    pub version: c_uint,
+    pub flags: u32,
+    pub diff_opts: git_diff_options,
+    pub diff_find_opts: git_diff_find_options,
+    pub subject_prefix: *const c_char,
+    pub start_number: usize,
+    pub reroll_number: usize,
+}
+
+pub const GIT_EMAIL_CREATE_OPTIONS_VERSION: c_uint = 1;
+
+git_enum! {
+    pub enum git_email_create_flags_t {
+        GIT_EMAIL_CREATE_DEFAULT = 0,
+        GIT_EMAIL_CREATE_OMIT_NUMBERS = 1 << 0,
+        GIT_EMAIL_CREATE_ALWAYS_NUMBER = 1 << 1,
+        GIT_EMAIL_CREATE_NO_RENAMES = 1 << 2,
+    }
+}
+
+extern "C" {
+    // threads
+    pub fn git_libgit2_init() -> c_int;
+    pub fn git_libgit2_shutdown() -> c_int;
+
+    // repository
+    pub fn git_repository_new(out: *mut *mut git_repository) -> c_int;
+    pub fn git_repository_free(repo: *mut git_repository);
+    pub fn git_repository_open(repo: *mut *mut git_repository, path: *const c_char) -> c_int;
+    pub fn git_repository_open_bare(repo: *mut *mut git_repository, path: *const c_char) -> c_int;
+    pub fn git_repository_open_ext(
+        repo: *mut *mut git_repository,
+        path: *const c_char,
+        flags: c_uint,
+        ceiling_dirs: *const c_char,
+    ) -> c_int;
+    pub fn git_repository_open_from_worktree(
+        repo: *mut *mut git_repository,
+        worktree: *mut git_worktree,
+    ) -> c_int;
+    pub fn git_repository_wrap_odb(repo: *mut *mut git_repository, odb: *mut git_odb) -> c_int;
+    pub fn git_repository_init(
+        repo: *mut *mut git_repository,
+        path: *const c_char,
+        is_bare: c_uint,
+    ) -> c_int;
+    pub fn git_repository_init_ext(
+        out: *mut *mut git_repository,
+        repo_path: *const c_char,
+        opts: *mut git_repository_init_options,
+    ) -> c_int;
+    pub fn git_repository_init_init_options(
+        opts: *mut git_repository_init_options,
+        version: c_uint,
+    ) -> c_int;
+    pub fn git_repository_get_namespace(repo: *mut git_repository) -> *const c_char;
+    pub fn git_repository_set_namespace(
+        repo: *mut git_repository,
+        namespace: *const c_char,
+    ) -> c_int;
+    pub fn git_repository_head(out: *mut *mut git_reference, repo: *mut git_repository) -> c_int;
+    pub fn git_repository_set_head(repo: *mut git_repository, refname: *const c_char) -> c_int;
+
+    pub fn git_repository_head_detached(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_set_head_detached(
+        repo: *mut git_repository,
+        commitish: *const git_oid,
+    ) -> c_int;
+    pub fn git_repository_set_head_detached_from_annotated(
+        repo: *mut git_repository,
+        commitish: *const git_annotated_commit,
+    ) -> c_int;
+    pub fn git_repository_set_bare(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_is_worktree(repo: *const git_repository) -> c_int;
+    pub fn git_repository_is_bare(repo: *const git_repository) -> c_int;
+    pub fn git_repository_is_empty(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_is_shallow(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_path(repo: *const git_repository) -> *const c_char;
+    pub fn git_repository_commondir(repo: *const git_repository) -> *const c_char;
+    pub fn git_repository_state(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_workdir(repo: *const git_repository) -> *const c_char;
+    pub fn git_repository_set_workdir(
+        repo: *mut git_repository,
+        workdir: *const c_char,
+        update_gitlink: c_int,
+    ) -> c_int;
+    pub fn git_repository_index(out: *mut *mut git_index, repo: *mut git_repository) -> c_int;
+    pub fn git_repository_set_index(repo: *mut git_repository, index: *mut git_index) -> c_int;
+
+    pub fn git_repository_message(buf: *mut git_buf, repo: *mut git_repository) -> c_int;
+
+    pub fn git_repository_message_remove(repo: *mut git_repository) -> c_int;
+    pub fn git_repository_config(out: *mut *mut git_config, repo: *mut git_repository) -> c_int;
+    pub fn git_repository_set_config(repo: *mut git_repository, config: *mut git_config) -> c_int;
+    pub fn git_repository_config_snapshot(
+        out: *mut *mut git_config,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_repository_discover(
+        out: *mut git_buf,
+        start_path: *const c_char,
+        across_fs: c_int,
+        ceiling_dirs: *const c_char,
+    ) -> c_int;
+    pub fn git_repository_set_odb(repo: *mut git_repository, odb: *mut git_odb) -> c_int;
+
+    pub fn git_repository_refdb(out: *mut *mut git_refdb, repo: *mut git_repository) -> c_int;
+    pub fn git_repository_set_refdb(repo: *mut git_repository, refdb: *mut git_refdb) -> c_int;
+
+    pub fn git_repository_reinit_filesystem(
+        repo: *mut git_repository,
+        recurse_submodules: c_int,
+    ) -> c_int;
+    pub fn git_repository_mergehead_foreach(
+        repo: *mut git_repository,
+        callback: git_repository_mergehead_foreach_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_repository_fetchhead_foreach(
+        repo: *mut git_repository,
+        callback: git_repository_fetchhead_foreach_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_ignore_add_rule(repo: *mut git_repository, rules: *const c_char) -> c_int;
+    pub fn git_ignore_clear_internal_rules(repo: *mut git_repository) -> c_int;
+    pub fn git_ignore_path_is_ignored(
+        ignored: *mut c_int,
+        repo: *mut git_repository,
+        path: *const c_char,
+    ) -> c_int;
+
+    // revparse
+    pub fn git_revparse(
+        revspec: *mut git_revspec,
+        repo: *mut git_repository,
+        spec: *const c_char,
+    ) -> c_int;
+    pub fn git_revparse_single(
+        out: *mut *mut git_object,
+        repo: *mut git_repository,
+        spec: *const c_char,
+    ) -> c_int;
+    pub fn git_revparse_ext(
+        object_out: *mut *mut git_object,
+        reference_out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        spec: *const c_char,
+    ) -> c_int;
+
+    // object
+    pub fn git_object_dup(dest: *mut *mut git_object, source: *mut git_object) -> c_int;
+    pub fn git_object_id(obj: *const git_object) -> *const git_oid;
+    pub fn git_object_free(object: *mut git_object);
+    pub fn git_object_lookup(
+        dest: *mut *mut git_object,
+        repo: *mut git_repository,
+        id: *const git_oid,
+        kind: git_object_t,
+    ) -> c_int;
+    pub fn git_object_lookup_prefix(
+        dest: *mut *mut git_object,
+        repo: *mut git_repository,
+        id: *const git_oid,
+        len: size_t,
+        kind: git_object_t,
+    ) -> c_int;
+    pub fn git_object_type(obj: *const git_object) -> git_object_t;
+    pub fn git_object_peel(
+        peeled: *mut *mut git_object,
+        object: *const git_object,
+        target_type: git_object_t,
+    ) -> c_int;
+    pub fn git_object_short_id(out: *mut git_buf, obj: *const git_object) -> c_int;
+    pub fn git_object_type2string(kind: git_object_t) -> *const c_char;
+    pub fn git_object_string2type(s: *const c_char) -> git_object_t;
+    pub fn git_object_typeisloose(kind: git_object_t) -> c_int;
+
+    // oid
+    pub fn git_oid_fromraw(out: *mut git_oid, raw: *const c_uchar) -> c_int;
+    pub fn git_oid_fromstrn(out: *mut git_oid, str: *const c_char, len: size_t) -> c_int;
+    pub fn git_oid_tostr(out: *mut c_char, n: size_t, id: *const git_oid) -> *mut c_char;
+    pub fn git_oid_cmp(a: *const git_oid, b: *const git_oid) -> c_int;
+    pub fn git_oid_equal(a: *const git_oid, b: *const git_oid) -> c_int;
+    pub fn git_oid_streq(id: *const git_oid, str: *const c_char) -> c_int;
+    pub fn git_oid_iszero(id: *const git_oid) -> c_int;
+
+    // error
+    pub fn git_error_last() -> *const git_error;
+    pub fn git_error_clear();
+    pub fn git_error_set_str(error_class: c_int, string: *const c_char) -> c_int;
+
+    // remote
+    pub fn git_remote_create(
+        out: *mut *mut git_remote,
+        repo: *mut git_repository,
+        name: *const c_char,
+        url: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_create_with_fetchspec(
+        out: *mut *mut git_remote,
+        repo: *mut git_repository,
+        name: *const c_char,
+        url: *const c_char,
+        fetch: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_lookup(
+        out: *mut *mut git_remote,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_create_anonymous(
+        out: *mut *mut git_remote,
+        repo: *mut git_repository,
+        url: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_create_detached(out: *mut *mut git_remote, url: *const c_char) -> c_int;
+    pub fn git_remote_delete(repo: *mut git_repository, name: *const c_char) -> c_int;
+    pub fn git_remote_free(remote: *mut git_remote);
+    pub fn git_remote_name(remote: *const git_remote) -> *const c_char;
+    pub fn git_remote_pushurl(remote: *const git_remote) -> *const c_char;
+    pub fn git_remote_refspec_count(remote: *const git_remote) -> size_t;
+    pub fn git_remote_url(remote: *const git_remote) -> *const c_char;
+    pub fn git_remote_connect(
+        remote: *mut git_remote,
+        dir: git_direction,
+        callbacks: *const git_remote_callbacks,
+        proxy_opts: *const git_proxy_options,
+        custom_headers: *const git_strarray,
+    ) -> c_int;
+    pub fn git_remote_connected(remote: *const git_remote) -> c_int;
+    pub fn git_remote_disconnect(remote: *mut git_remote) -> c_int;
+    pub fn git_remote_add_fetch(
+        repo: *mut git_repository,
+        remote: *const c_char,
+        refspec: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_add_push(
+        repo: *mut git_repository,
+        remote: *const c_char,
+        refspec: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_download(
+        remote: *mut git_remote,
+        refspecs: *const git_strarray,
+        opts: *const git_fetch_options,
+    ) -> c_int;
+    pub fn git_remote_stop(remote: *mut git_remote) -> c_int;
+    pub fn git_remote_dup(dest: *mut *mut git_remote, source: *mut git_remote) -> c_int;
+    pub fn git_remote_get_fetch_refspecs(
+        array: *mut git_strarray,
+        remote: *const git_remote,
+    ) -> c_int;
+    pub fn git_remote_get_push_refspecs(
+        array: *mut git_strarray,
+        remote: *const git_remote,
+    ) -> c_int;
+    pub fn git_remote_get_refspec(remote: *const git_remote, n: size_t) -> *const git_refspec;
+    pub fn git_remote_is_valid_name(remote_name: *const c_char) -> c_int;
+    pub fn git_remote_name_is_valid(valid: *mut c_int, remote_name: *const c_char) -> c_int;
+    pub fn git_remote_list(out: *mut git_strarray, repo: *mut git_repository) -> c_int;
+    pub fn git_remote_rename(
+        problems: *mut git_strarray,
+        repo: *mut git_repository,
+        name: *const c_char,
+        new_name: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_fetch(
+        remote: *mut git_remote,
+        refspecs: *const git_strarray,
+        opts: *const git_fetch_options,
+        reflog_message: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_push(
+        remote: *mut git_remote,
+        refspecs: *const git_strarray,
+        opts: *const git_push_options,
+    ) -> c_int;
+    pub fn git_remote_update_tips(
+        remote: *mut git_remote,
+        callbacks: *const git_remote_callbacks,
+        update_flags: c_uint,
+        download_tags: git_remote_autotag_option_t,
+        reflog_message: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_set_url(
+        repo: *mut git_repository,
+        remote: *const c_char,
+        url: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_set_pushurl(
+        repo: *mut git_repository,
+        remote: *const c_char,
+        pushurl: *const c_char,
+    ) -> c_int;
+    pub fn git_remote_init_callbacks(opts: *mut git_remote_callbacks, version: c_uint) -> c_int;
+    pub fn git_fetch_init_options(opts: *mut git_fetch_options, version: c_uint) -> c_int;
+    pub fn git_remote_stats(remote: *mut git_remote) -> *const git_indexer_progress;
+    pub fn git_remote_ls(
+        out: *mut *mut *const git_remote_head,
+        size: *mut size_t,
+        remote: *mut git_remote,
+    ) -> c_int;
+    pub fn git_remote_set_autotag(
+        repo: *mut git_repository,
+        remote: *const c_char,
+        value: git_remote_autotag_option_t,
+    ) -> c_int;
+    pub fn git_remote_prune(
+        remote: *mut git_remote,
+        callbacks: *const git_remote_callbacks,
+    ) -> c_int;
+    pub fn git_remote_default_branch(out: *mut git_buf, remote: *mut git_remote) -> c_int;
+
+    // refspec
+    pub fn git_refspec_direction(spec: *const git_refspec) -> git_direction;
+    pub fn git_refspec_dst(spec: *const git_refspec) -> *const c_char;
+    pub fn git_refspec_dst_matches(spec: *const git_refspec, refname: *const c_char) -> c_int;
+    pub fn git_refspec_src(spec: *const git_refspec) -> *const c_char;
+    pub fn git_refspec_src_matches(spec: *const git_refspec, refname: *const c_char) -> c_int;
+    pub fn git_refspec_force(spec: *const git_refspec) -> c_int;
+    pub fn git_refspec_string(spec: *const git_refspec) -> *const c_char;
+    pub fn git_refspec_transform(
+        out: *mut git_buf,
+        spec: *const git_refspec,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_refspec_rtransform(
+        out: *mut git_buf,
+        spec: *const git_refspec,
+        name: *const c_char,
+    ) -> c_int;
+
+    // strarray
+    pub fn git_strarray_free(array: *mut git_strarray);
+
+    // oidarray
+    pub fn git_oidarray_free(array: *mut git_oidarray);
+
+    // signature
+    pub fn git_signature_default(out: *mut *mut git_signature, repo: *mut git_repository) -> c_int;
+    pub fn git_signature_free(sig: *mut git_signature);
+    pub fn git_signature_new(
+        out: *mut *mut git_signature,
+        name: *const c_char,
+        email: *const c_char,
+        time: git_time_t,
+        offset: c_int,
+    ) -> c_int;
+    pub fn git_signature_now(
+        out: *mut *mut git_signature,
+        name: *const c_char,
+        email: *const c_char,
+    ) -> c_int;
+    pub fn git_signature_dup(dest: *mut *mut git_signature, sig: *const git_signature) -> c_int;
+
+    // status
+    pub fn git_status_list_new(
+        out: *mut *mut git_status_list,
+        repo: *mut git_repository,
+        options: *const git_status_options,
+    ) -> c_int;
+    pub fn git_status_list_entrycount(list: *mut git_status_list) -> size_t;
+    pub fn git_status_byindex(
+        statuslist: *mut git_status_list,
+        idx: size_t,
+    ) -> *const git_status_entry;
+    pub fn git_status_list_free(list: *mut git_status_list);
+    pub fn git_status_init_options(opts: *mut git_status_options, version: c_uint) -> c_int;
+    pub fn git_status_file(
+        status_flags: *mut c_uint,
+        repo: *mut git_repository,
+        path: *const c_char,
+    ) -> c_int;
+    pub fn git_status_should_ignore(
+        ignored: *mut c_int,
+        repo: *mut git_repository,
+        path: *const c_char,
+    ) -> c_int;
+
+    // clone
+    pub fn git_clone(
+        out: *mut *mut git_repository,
+        url: *const c_char,
+        local_path: *const c_char,
+        options: *const git_clone_options,
+    ) -> c_int;
+    pub fn git_clone_init_options(opts: *mut git_clone_options, version: c_uint) -> c_int;
+
+    // reset
+    pub fn git_reset(
+        repo: *mut git_repository,
+        target: *const git_object,
+        reset_type: git_reset_t,
+        checkout_opts: *const git_checkout_options,
+    ) -> c_int;
+    pub fn git_reset_default(
+        repo: *mut git_repository,
+        target: *const git_object,
+        pathspecs: *const git_strarray,
+    ) -> c_int;
+
+    // reference
+    pub fn git_reference_cmp(ref1: *const git_reference, ref2: *const git_reference) -> c_int;
+    pub fn git_reference_delete(r: *mut git_reference) -> c_int;
+    pub fn git_reference_free(r: *mut git_reference);
+    pub fn git_reference_is_branch(r: *const git_reference) -> c_int;
+    pub fn git_reference_is_note(r: *const git_reference) -> c_int;
+    pub fn git_reference_is_remote(r: *const git_reference) -> c_int;
+    pub fn git_reference_is_tag(r: *const git_reference) -> c_int;
+    pub fn git_reference_is_valid_name(name: *const c_char) -> c_int;
+    pub fn git_reference_name_is_valid(valid: *mut c_int, refname: *const c_char) -> c_int;
+    pub fn git_reference_lookup(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_dwim(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        refname: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_name(r: *const git_reference) -> *const c_char;
+    pub fn git_reference_name_to_id(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_peel(
+        out: *mut *mut git_object,
+        r: *const git_reference,
+        otype: git_object_t,
+    ) -> c_int;
+    pub fn git_reference_rename(
+        new_ref: *mut *mut git_reference,
+        r: *mut git_reference,
+        new_name: *const c_char,
+        force: c_int,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_resolve(out: *mut *mut git_reference, r: *const git_reference) -> c_int;
+    pub fn git_reference_shorthand(r: *const git_reference) -> *const c_char;
+    pub fn git_reference_symbolic_target(r: *const git_reference) -> *const c_char;
+    pub fn git_reference_target(r: *const git_reference) -> *const git_oid;
+    pub fn git_reference_target_peel(r: *const git_reference) -> *const git_oid;
+    pub fn git_reference_set_target(
+        out: *mut *mut git_reference,
+        r: *mut git_reference,
+        id: *const git_oid,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_symbolic_set_target(
+        out: *mut *mut git_reference,
+        r: *mut git_reference,
+        target: *const c_char,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_type(r: *const git_reference) -> git_reference_t;
+    pub fn git_reference_iterator_new(
+        out: *mut *mut git_reference_iterator,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_reference_iterator_glob_new(
+        out: *mut *mut git_reference_iterator,
+        repo: *mut git_repository,
+        glob: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_iterator_free(iter: *mut git_reference_iterator);
+    pub fn git_reference_next(
+        out: *mut *mut git_reference,
+        iter: *mut git_reference_iterator,
+    ) -> c_int;
+    pub fn git_reference_next_name(
+        out: *mut *const c_char,
+        iter: *mut git_reference_iterator,
+    ) -> c_int;
+    pub fn git_reference_create(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        name: *const c_char,
+        id: *const git_oid,
+        force: c_int,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_symbolic_create(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        name: *const c_char,
+        target: *const c_char,
+        force: c_int,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_create_matching(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        name: *const c_char,
+        id: *const git_oid,
+        force: c_int,
+        current_id: *const git_oid,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_symbolic_create_matching(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        name: *const c_char,
+        target: *const c_char,
+        force: c_int,
+        current_id: *const c_char,
+        log_message: *const c_char,
+    ) -> c_int;
+    pub fn git_reference_has_log(repo: *mut git_repository, name: *const c_char) -> c_int;
+    pub fn git_reference_ensure_log(repo: *mut git_repository, name: *const c_char) -> c_int;
+    pub fn git_reference_normalize_name(
+        buffer_out: *mut c_char,
+        buffer_size: size_t,
+        name: *const c_char,
+        flags: u32,
+    ) -> c_int;
+
+    // stash
+    pub fn git_stash_save(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        stasher: *const git_signature,
+        message: *const c_char,
+        flags: c_uint,
+    ) -> c_int;
+
+    pub fn git_stash_save_options_init(opts: *mut git_stash_save_options, version: c_uint)
+        -> c_int;
+
+    pub fn git_stash_save_with_opts(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        options: *const git_stash_save_options,
+    ) -> c_int;
+
+    pub fn git_stash_apply_init_options(
+        opts: *mut git_stash_apply_options,
+        version: c_uint,
+    ) -> c_int;
+
+    pub fn git_stash_apply(
+        repo: *mut git_repository,
+        index: size_t,
+        options: *const git_stash_apply_options,
+    ) -> c_int;
+
+    pub fn git_stash_foreach(
+        repo: *mut git_repository,
+        callback: git_stash_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+
+    pub fn git_stash_drop(repo: *mut git_repository, index: size_t) -> c_int;
+
+    pub fn git_stash_pop(
+        repo: *mut git_repository,
+        index: size_t,
+        options: *const git_stash_apply_options,
+    ) -> c_int;
+
+    // submodules
+    pub fn git_submodule_add_finalize(submodule: *mut git_submodule) -> c_int;
+    pub fn git_submodule_add_setup(
+        submodule: *mut *mut git_submodule,
+        repo: *mut git_repository,
+        url: *const c_char,
+        path: *const c_char,
+        use_gitlink: c_int,
+    ) -> c_int;
+    pub fn git_submodule_add_to_index(submodule: *mut git_submodule, write_index: c_int) -> c_int;
+    pub fn git_submodule_branch(submodule: *mut git_submodule) -> *const c_char;
+    pub fn git_submodule_clone(
+        repo: *mut *mut git_repository,
+        submodule: *mut git_submodule,
+        opts: *const git_submodule_update_options,
+    ) -> c_int;
+    pub fn git_submodule_foreach(
+        repo: *mut git_repository,
+        callback: git_submodule_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_submodule_free(submodule: *mut git_submodule);
+    pub fn git_submodule_head_id(submodule: *mut git_submodule) -> *const git_oid;
+    pub fn git_submodule_ignore(submodule: *mut git_submodule) -> git_submodule_ignore_t;
+    pub fn git_submodule_index_id(submodule: *mut git_submodule) -> *const git_oid;
+    pub fn git_submodule_init(submodule: *mut git_submodule, overwrite: c_int) -> c_int;
+    pub fn git_submodule_repo_init(
+        repo: *mut *mut git_repository,
+        submodule: *const git_submodule,
+        use_gitlink: c_int,
+    ) -> c_int;
+    pub fn git_submodule_location(status: *mut c_uint, submodule: *mut git_submodule) -> c_int;
+    pub fn git_submodule_lookup(
+        out: *mut *mut git_submodule,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_submodule_name(submodule: *mut git_submodule) -> *const c_char;
+    pub fn git_submodule_open(
+        repo: *mut *mut git_repository,
+        submodule: *mut git_submodule,
+    ) -> c_int;
+    pub fn git_submodule_path(submodule: *mut git_submodule) -> *const c_char;
+    pub fn git_submodule_reload(submodule: *mut git_submodule, force: c_int) -> c_int;
+    pub fn git_submodule_set_ignore(
+        repo: *mut git_repository,
+        name: *const c_char,
+        ignore: git_submodule_ignore_t,
+    ) -> c_int;
+    pub fn git_submodule_set_update(
+        repo: *mut git_repository,
+        name: *const c_char,
+        update: git_submodule_update_t,
+    ) -> c_int;
+    pub fn git_submodule_set_url(
+        repo: *mut git_repository,
+        name: *const c_char,
+        url: *const c_char,
+    ) -> c_int;
+    pub fn git_submodule_sync(submodule: *mut git_submodule) -> c_int;
+    pub fn git_submodule_update_strategy(submodule: *mut git_submodule) -> git_submodule_update_t;
+    pub fn git_submodule_update(
+        submodule: *mut git_submodule,
+        init: c_int,
+        options: *mut git_submodule_update_options,
+    ) -> c_int;
+    pub fn git_submodule_update_init_options(
+        options: *mut git_submodule_update_options,
+        version: c_uint,
+    ) -> c_int;
+    pub fn git_submodule_url(submodule: *mut git_submodule) -> *const c_char;
+    pub fn git_submodule_wd_id(submodule: *mut git_submodule) -> *const git_oid;
+    pub fn git_submodule_status(
+        status: *mut c_uint,
+        repo: *mut git_repository,
+        name: *const c_char,
+        ignore: git_submodule_ignore_t,
+    ) -> c_int;
+    pub fn git_submodule_set_branch(
+        repo: *mut git_repository,
+        name: *const c_char,
+        branch: *const c_char,
+    ) -> c_int;
+
+    // blob
+    pub fn git_blob_free(blob: *mut git_blob);
+    pub fn git_blob_id(blob: *const git_blob) -> *const git_oid;
+    pub fn git_blob_is_binary(blob: *const git_blob) -> c_int;
+    pub fn git_blob_lookup(
+        blob: *mut *mut git_blob,
+        repo: *mut git_repository,
+        id: *const git_oid,
+    ) -> c_int;
+    pub fn git_blob_lookup_prefix(
+        blob: *mut *mut git_blob,
+        repo: *mut git_repository,
+        id: *const git_oid,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_blob_rawcontent(blob: *const git_blob) -> *const c_void;
+    pub fn git_blob_rawsize(blob: *const git_blob) -> git_object_size_t;
+    pub fn git_blob_create_frombuffer(
+        id: *mut git_oid,
+        repo: *mut git_repository,
+        buffer: *const c_void,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_blob_create_fromdisk(
+        id: *mut git_oid,
+        repo: *mut git_repository,
+        path: *const c_char,
+    ) -> c_int;
+    pub fn git_blob_create_fromworkdir(
+        id: *mut git_oid,
+        repo: *mut git_repository,
+        relative_path: *const c_char,
+    ) -> c_int;
+    pub fn git_blob_create_fromstream(
+        out: *mut *mut git_writestream,
+        repo: *mut git_repository,
+        hintpath: *const c_char,
+    ) -> c_int;
+    pub fn git_blob_create_fromstream_commit(
+        id: *mut git_oid,
+        stream: *mut git_writestream,
+    ) -> c_int;
+
+    // tree
+    pub fn git_tree_entry_byid(tree: *const git_tree, id: *const git_oid) -> *const git_tree_entry;
+    pub fn git_tree_entry_byindex(tree: *const git_tree, idx: size_t) -> *const git_tree_entry;
+    pub fn git_tree_entry_byname(
+        tree: *const git_tree,
+        filename: *const c_char,
+    ) -> *const git_tree_entry;
+    pub fn git_tree_entry_bypath(
+        out: *mut *mut git_tree_entry,
+        tree: *const git_tree,
+        filename: *const c_char,
+    ) -> c_int;
+    pub fn git_tree_entry_cmp(e1: *const git_tree_entry, e2: *const git_tree_entry) -> c_int;
+    pub fn git_tree_entry_dup(dest: *mut *mut git_tree_entry, src: *const git_tree_entry) -> c_int;
+    pub fn git_tree_entry_filemode(entry: *const git_tree_entry) -> git_filemode_t;
+    pub fn git_tree_entry_filemode_raw(entry: *const git_tree_entry) -> git_filemode_t;
+    pub fn git_tree_entry_free(entry: *mut git_tree_entry);
+    pub fn git_tree_entry_id(entry: *const git_tree_entry) -> *const git_oid;
+    pub fn git_tree_entry_name(entry: *const git_tree_entry) -> *const c_char;
+    pub fn git_tree_entry_to_object(
+        out: *mut *mut git_object,
+        repo: *mut git_repository,
+        entry: *const git_tree_entry,
+    ) -> c_int;
+    pub fn git_tree_entry_type(entry: *const git_tree_entry) -> git_object_t;
+    pub fn git_tree_entrycount(tree: *const git_tree) -> size_t;
+    pub fn git_tree_free(tree: *mut git_tree);
+    pub fn git_tree_id(tree: *const git_tree) -> *const git_oid;
+    pub fn git_tree_lookup(
+        tree: *mut *mut git_tree,
+        repo: *mut git_repository,
+        id: *const git_oid,
+    ) -> c_int;
+    pub fn git_tree_walk(
+        tree: *const git_tree,
+        mode: git_treewalk_mode,
+        callback: git_treewalk_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_tree_create_updated(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        baseline: *mut git_tree,
+        nupdates: usize,
+        updates: *const git_tree_update,
+    ) -> c_int;
+
+    // treebuilder
+    pub fn git_treebuilder_new(
+        out: *mut *mut git_treebuilder,
+        repo: *mut git_repository,
+        source: *const git_tree,
+    ) -> c_int;
+    pub fn git_treebuilder_clear(bld: *mut git_treebuilder) -> c_int;
+    pub fn git_treebuilder_entrycount(bld: *mut git_treebuilder) -> size_t;
+    pub fn git_treebuilder_free(bld: *mut git_treebuilder);
+    pub fn git_treebuilder_get(
+        bld: *mut git_treebuilder,
+        filename: *const c_char,
+    ) -> *const git_tree_entry;
+    pub fn git_treebuilder_insert(
+        out: *mut *const git_tree_entry,
+        bld: *mut git_treebuilder,
+        filename: *const c_char,
+        id: *const git_oid,
+        filemode: git_filemode_t,
+    ) -> c_int;
+    pub fn git_treebuilder_remove(bld: *mut git_treebuilder, filename: *const c_char) -> c_int;
+    pub fn git_treebuilder_filter(
+        bld: *mut git_treebuilder,
+        filter: git_treebuilder_filter_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_treebuilder_write(id: *mut git_oid, bld: *mut git_treebuilder) -> c_int;
+
+    // buf
+    pub fn git_buf_dispose(buffer: *mut git_buf);
+    pub fn git_buf_grow(buffer: *mut git_buf, target_size: size_t) -> c_int;
+    pub fn git_buf_set(buffer: *mut git_buf, data: *const c_void, datalen: size_t) -> c_int;
+
+    // commit
+    pub fn git_commit_author(commit: *const git_commit) -> *const git_signature;
+    pub fn git_commit_author_with_mailmap(
+        out: *mut *mut git_signature,
+        commit: *const git_commit,
+        mailmap: *const git_mailmap,
+    ) -> c_int;
+    pub fn git_commit_committer(commit: *const git_commit) -> *const git_signature;
+    pub fn git_commit_committer_with_mailmap(
+        out: *mut *mut git_signature,
+        commit: *const git_commit,
+        mailmap: *const git_mailmap,
+    ) -> c_int;
+    pub fn git_commit_free(commit: *mut git_commit);
+    pub fn git_commit_id(commit: *const git_commit) -> *const git_oid;
+    pub fn git_commit_lookup(
+        commit: *mut *mut git_commit,
+        repo: *mut git_repository,
+        id: *const git_oid,
+    ) -> c_int;
+    pub fn git_commit_lookup_prefix(
+        commit: *mut *mut git_commit,
+        repo: *mut git_repository,
+        id: *const git_oid,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_commit_message(commit: *const git_commit) -> *const c_char;
+    pub fn git_commit_message_encoding(commit: *const git_commit) -> *const c_char;
+    pub fn git_commit_message_raw(commit: *const git_commit) -> *const c_char;
+    pub fn git_commit_nth_gen_ancestor(
+        commit: *mut *mut git_commit,
+        commit: *const git_commit,
+        n: c_uint,
+    ) -> c_int;
+    pub fn git_commit_parent(
+        out: *mut *mut git_commit,
+        commit: *const git_commit,
+        n: c_uint,
+    ) -> c_int;
+    pub fn git_commit_parent_id(commit: *const git_commit, n: c_uint) -> *const git_oid;
+    pub fn git_commit_parentcount(commit: *const git_commit) -> c_uint;
+    pub fn git_commit_raw_header(commit: *const git_commit) -> *const c_char;
+    pub fn git_commit_summary(commit: *mut git_commit) -> *const c_char;
+    pub fn git_commit_body(commit: *mut git_commit) -> *const c_char;
+    pub fn git_commit_time(commit: *const git_commit) -> git_time_t;
+    pub fn git_commit_time_offset(commit: *const git_commit) -> c_int;
+    pub fn git_commit_tree(tree_out: *mut *mut git_tree, commit: *const git_commit) -> c_int;
+    pub fn git_commit_tree_id(commit: *const git_commit) -> *const git_oid;
+    pub fn git_commit_amend(
+        id: *mut git_oid,
+        commit_to_amend: *const git_commit,
+        update_ref: *const c_char,
+        author: *const git_signature,
+        committer: *const git_signature,
+        message_encoding: *const c_char,
+        message: *const c_char,
+        tree: *const git_tree,
+    ) -> c_int;
+    pub fn git_commit_create(
+        id: *mut git_oid,
+        repo: *mut git_repository,
+        update_ref: *const c_char,
+        author: *const git_signature,
+        committer: *const git_signature,
+        message_encoding: *const c_char,
+        message: *const c_char,
+        tree: *const git_tree,
+        parent_count: size_t,
+        parents: *mut *const git_commit,
+    ) -> c_int;
+    pub fn git_commit_create_buffer(
+        out: *mut git_buf,
+        repo: *mut git_repository,
+        author: *const git_signature,
+        committer: *const git_signature,
+        message_encoding: *const c_char,
+        message: *const c_char,
+        tree: *const git_tree,
+        parent_count: size_t,
+        parents: *mut *const git_commit,
+    ) -> c_int;
+    pub fn git_commit_header_field(
+        out: *mut git_buf,
+        commit: *const git_commit,
+        field: *const c_char,
+    ) -> c_int;
+    pub fn git_annotated_commit_lookup(
+        out: *mut *mut git_annotated_commit,
+        repo: *mut git_repository,
+        id: *const git_oid,
+    ) -> c_int;
+    pub fn git_commit_create_with_signature(
+        id: *mut git_oid,
+        repo: *mut git_repository,
+        commit_content: *const c_char,
+        signature: *const c_char,
+        signature_field: *const c_char,
+    ) -> c_int;
+    pub fn git_commit_extract_signature(
+        signature: *mut git_buf,
+        signed_data: *mut git_buf,
+        repo: *mut git_repository,
+        commit_id: *mut git_oid,
+        field: *const c_char,
+    ) -> c_int;
+
+    // branch
+    pub fn git_branch_create(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        branch_name: *const c_char,
+        target: *const git_commit,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_branch_create_from_annotated(
+        ref_out: *mut *mut git_reference,
+        repository: *mut git_repository,
+        branch_name: *const c_char,
+        commit: *const git_annotated_commit,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_branch_delete(branch: *mut git_reference) -> c_int;
+    pub fn git_branch_is_head(branch: *const git_reference) -> c_int;
+    pub fn git_branch_iterator_free(iter: *mut git_branch_iterator);
+    pub fn git_branch_iterator_new(
+        iter: *mut *mut git_branch_iterator,
+        repo: *mut git_repository,
+        list_flags: git_branch_t,
+    ) -> c_int;
+    pub fn git_branch_lookup(
+        out: *mut *mut git_reference,
+        repo: *mut git_repository,
+        branch_name: *const c_char,
+        branch_type: git_branch_t,
+    ) -> c_int;
+    pub fn git_branch_move(
+        out: *mut *mut git_reference,
+        branch: *mut git_reference,
+        new_branch_name: *const c_char,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_branch_name(out: *mut *const c_char, branch: *const git_reference) -> c_int;
+    pub fn git_branch_name_is_valid(valid: *mut c_int, name: *const c_char) -> c_int;
+    pub fn git_branch_remote_name(
+        out: *mut git_buf,
+        repo: *mut git_repository,
+        refname: *const c_char,
+    ) -> c_int;
+    pub fn git_branch_next(
+        out: *mut *mut git_reference,
+        out_type: *mut git_branch_t,
+        iter: *mut git_branch_iterator,
+    ) -> c_int;
+    pub fn git_branch_set_upstream(
+        branch: *mut git_reference,
+        upstream_name: *const c_char,
+    ) -> c_int;
+    pub fn git_branch_upstream(out: *mut *mut git_reference, branch: *const git_reference)
+        -> c_int;
+    pub fn git_branch_upstream_name(
+        out: *mut git_buf,
+        repo: *mut git_repository,
+        refname: *const c_char,
+    ) -> c_int;
+    pub fn git_branch_upstream_remote(
+        out: *mut git_buf,
+        repo: *mut git_repository,
+        refname: *const c_char,
+    ) -> c_int;
+
+    // index
+    pub fn git_index_version(index: *mut git_index) -> c_uint;
+    pub fn git_index_set_version(index: *mut git_index, version: c_uint) -> c_int;
+    pub fn git_index_add(index: *mut git_index, entry: *const git_index_entry) -> c_int;
+    pub fn git_index_add_all(
+        index: *mut git_index,
+        pathspec: *const git_strarray,
+        flags: c_uint,
+        callback: git_index_matched_path_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_index_add_bypath(index: *mut git_index, path: *const c_char) -> c_int;
+    pub fn git_index_add_frombuffer(
+        index: *mut git_index,
+        entry: *const git_index_entry,
+        buffer: *const c_void,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_index_conflict_add(
+        index: *mut git_index,
+        ancestor_entry: *const git_index_entry,
+        our_entry: *const git_index_entry,
+        their_entry: *const git_index_entry,
+    ) -> c_int;
+    pub fn git_index_conflict_remove(index: *mut git_index, path: *const c_char) -> c_int;
+    pub fn git_index_conflict_get(
+        ancestor_out: *mut *const git_index_entry,
+        our_out: *mut *const git_index_entry,
+        their_out: *mut *const git_index_entry,
+        index: *mut git_index,
+        path: *const c_char,
+    ) -> c_int;
+    pub fn git_index_conflict_iterator_new(
+        iter: *mut *mut git_index_conflict_iterator,
+        index: *mut git_index,
+    ) -> c_int;
+    pub fn git_index_conflict_next(
+        ancestor_out: *mut *const git_index_entry,
+        our_out: *mut *const git_index_entry,
+        their_out: *mut *const git_index_entry,
+        iter: *mut git_index_conflict_iterator,
+    ) -> c_int;
+    pub fn git_index_conflict_iterator_free(iter: *mut git_index_conflict_iterator);
+    pub fn git_index_clear(index: *mut git_index) -> c_int;
+    pub fn git_index_entry_stage(entry: *const git_index_entry) -> c_int;
+    pub fn git_index_entrycount(entry: *const git_index) -> size_t;
+    pub fn git_index_find(at_pos: *mut size_t, index: *mut git_index, path: *const c_char)
+        -> c_int;
+    pub fn git_index_find_prefix(
+        at_pos: *mut size_t,
+        index: *mut git_index,
+        prefix: *const c_char,
+    ) -> c_int;
+    pub fn git_index_free(index: *mut git_index);
+    pub fn git_index_get_byindex(index: *mut git_index, n: size_t) -> *const git_index_entry;
+    pub fn git_index_get_bypath(
+        index: *mut git_index,
+        path: *const c_char,
+        stage: c_int,
+    ) -> *const git_index_entry;
+    pub fn git_index_has_conflicts(index: *const git_index) -> c_int;
+    pub fn git_index_new(index: *mut *mut git_index) -> c_int;
+    pub fn git_index_open(index: *mut *mut git_index, index_path: *const c_char) -> c_int;
+    pub fn git_index_path(index: *const git_index) -> *const c_char;
+    pub fn git_index_read(index: *mut git_index, force: c_int) -> c_int;
+    pub fn git_index_read_tree(index: *mut git_index, tree: *const git_tree) -> c_int;
+    pub fn git_index_remove(index: *mut git_index, path: *const c_char, stage: c_int) -> c_int;
+    pub fn git_index_remove_all(
+        index: *mut git_index,
+        pathspec: *const git_strarray,
+        callback: git_index_matched_path_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_index_remove_bypath(index: *mut git_index, path: *const c_char) -> c_int;
+    pub fn git_index_remove_directory(
+        index: *mut git_index,
+        dir: *const c_char,
+        stage: c_int,
+    ) -> c_int;
+    pub fn git_index_update_all(
+        index: *mut git_index,
+        pathspec: *const git_strarray,
+        callback: git_index_matched_path_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_index_write(index: *mut git_index) -> c_int;
+    pub fn git_index_write_tree(out: *mut git_oid, index: *mut git_index) -> c_int;
+    pub fn git_index_write_tree_to(
+        out: *mut git_oid,
+        index: *mut git_index,
+        repo: *mut git_repository,
+    ) -> c_int;
+
+    // config
+    pub fn git_config_add_file_ondisk(
+        cfg: *mut git_config,
+        path: *const c_char,
+        level: git_config_level_t,
+        repo: *const git_repository,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_config_delete_entry(cfg: *mut git_config, name: *const c_char) -> c_int;
+    pub fn git_config_delete_multivar(
+        cfg: *mut git_config,
+        name: *const c_char,
+        regexp: *const c_char,
+    ) -> c_int;
+    pub fn git_config_find_programdata(out: *mut git_buf) -> c_int;
+    pub fn git_config_find_global(out: *mut git_buf) -> c_int;
+    pub fn git_config_find_system(out: *mut git_buf) -> c_int;
+    pub fn git_config_find_xdg(out: *mut git_buf) -> c_int;
+    pub fn git_config_free(cfg: *mut git_config);
+    pub fn git_config_get_bool(
+        out: *mut c_int,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_entry(
+        out: *mut *mut git_config_entry,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_int32(
+        out: *mut i32,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_int64(
+        out: *mut i64,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_string(
+        out: *mut *const c_char,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_string_buf(
+        out: *mut git_buf,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_get_path(
+        out: *mut git_buf,
+        cfg: *const git_config,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_config_iterator_free(iter: *mut git_config_iterator);
+    pub fn git_config_iterator_glob_new(
+        out: *mut *mut git_config_iterator,
+        cfg: *const git_config,
+        regexp: *const c_char,
+    ) -> c_int;
+    pub fn git_config_iterator_new(
+        out: *mut *mut git_config_iterator,
+        cfg: *const git_config,
+    ) -> c_int;
+    pub fn git_config_new(out: *mut *mut git_config) -> c_int;
+    pub fn git_config_next(
+        entry: *mut *mut git_config_entry,
+        iter: *mut git_config_iterator,
+    ) -> c_int;
+    pub fn git_config_open_default(out: *mut *mut git_config) -> c_int;
+    pub fn git_config_open_global(out: *mut *mut git_config, config: *mut git_config) -> c_int;
+    pub fn git_config_open_level(
+        out: *mut *mut git_config,
+        parent: *const git_config,
+        level: git_config_level_t,
+    ) -> c_int;
+    pub fn git_config_open_ondisk(out: *mut *mut git_config, path: *const c_char) -> c_int;
+    pub fn git_config_parse_bool(out: *mut c_int, value: *const c_char) -> c_int;
+    pub fn git_config_parse_int32(out: *mut i32, value: *const c_char) -> c_int;
+    pub fn git_config_parse_int64(out: *mut i64, value: *const c_char) -> c_int;
+    pub fn git_config_set_bool(cfg: *mut git_config, name: *const c_char, value: c_int) -> c_int;
+    pub fn git_config_set_int32(cfg: *mut git_config, name: *const c_char, value: i32) -> c_int;
+    pub fn git_config_set_int64(cfg: *mut git_config, name: *const c_char, value: i64) -> c_int;
+    pub fn git_config_set_multivar(
+        cfg: *mut git_config,
+        name: *const c_char,
+        regexp: *const c_char,
+        value: *const c_char,
+    ) -> c_int;
+    pub fn git_config_set_string(
+        cfg: *mut git_config,
+        name: *const c_char,
+        value: *const c_char,
+    ) -> c_int;
+    pub fn git_config_snapshot(out: *mut *mut git_config, config: *mut git_config) -> c_int;
+    pub fn git_config_entry_free(entry: *mut git_config_entry);
+    pub fn git_config_multivar_iterator_new(
+        out: *mut *mut git_config_iterator,
+        cfg: *const git_config,
+        name: *const c_char,
+        regexp: *const c_char,
+    ) -> c_int;
+
+    // attr
+    pub fn git_attr_get(
+        value_out: *mut *const c_char,
+        repo: *mut git_repository,
+        flags: u32,
+        path: *const c_char,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_attr_value(value: *const c_char) -> git_attr_value_t;
+
+    // cred
+    pub fn git_cred_default_new(out: *mut *mut git_cred) -> c_int;
+    pub fn git_cred_has_username(cred: *mut git_cred) -> c_int;
+    pub fn git_cred_ssh_custom_new(
+        out: *mut *mut git_cred,
+        username: *const c_char,
+        publickey: *const c_char,
+        publickey_len: size_t,
+        sign_callback: git_cred_sign_callback,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_cred_ssh_interactive_new(
+        out: *mut *mut git_cred,
+        username: *const c_char,
+        prompt_callback: git_cred_ssh_interactive_callback,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_cred_ssh_key_from_agent(out: *mut *mut git_cred, username: *const c_char) -> c_int;
+    pub fn git_cred_ssh_key_new(
+        out: *mut *mut git_cred,
+        username: *const c_char,
+        publickey: *const c_char,
+        privatekey: *const c_char,
+        passphrase: *const c_char,
+    ) -> c_int;
+    pub fn git_cred_ssh_key_memory_new(
+        out: *mut *mut git_cred,
+        username: *const c_char,
+        publickey: *const c_char,
+        privatekey: *const c_char,
+        passphrase: *const c_char,
+    ) -> c_int;
+    pub fn git_cred_userpass(
+        cred: *mut *mut git_cred,
+        url: *const c_char,
+        user_from_url: *const c_char,
+        allowed_types: c_uint,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_cred_userpass_plaintext_new(
+        out: *mut *mut git_cred,
+        username: *const c_char,
+        password: *const c_char,
+    ) -> c_int;
+    pub fn git_cred_username_new(cred: *mut *mut git_cred, username: *const c_char) -> c_int;
+
+    // tags
+    pub fn git_tag_annotation_create(
+        oid: *mut git_oid,
+        repo: *mut git_repository,
+        tag_name: *const c_char,
+        target: *const git_object,
+        tagger: *const git_signature,
+        message: *const c_char,
+    ) -> c_int;
+    pub fn git_tag_create(
+        oid: *mut git_oid,
+        repo: *mut git_repository,
+        tag_name: *const c_char,
+        target: *const git_object,
+        tagger: *const git_signature,
+        message: *const c_char,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_tag_create_frombuffer(
+        oid: *mut git_oid,
+        repo: *mut git_repository,
+        buffer: *const c_char,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_tag_create_lightweight(
+        oid: *mut git_oid,
+        repo: *mut git_repository,
+        tag_name: *const c_char,
+        target: *const git_object,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_tag_delete(repo: *mut git_repository, tag_name: *const c_char) -> c_int;
+    pub fn git_tag_foreach(
+        repo: *mut git_repository,
+        callback: git_tag_foreach_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_tag_free(tag: *mut git_tag);
+    pub fn git_tag_id(tag: *const git_tag) -> *const git_oid;
+    pub fn git_tag_list(tag_names: *mut git_strarray, repo: *mut git_repository) -> c_int;
+    pub fn git_tag_list_match(
+        tag_names: *mut git_strarray,
+        pattern: *const c_char,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_tag_lookup(
+        out: *mut *mut git_tag,
+        repo: *mut git_repository,
+        id: *const git_oid,
+    ) -> c_int;
+    pub fn git_tag_lookup_prefix(
+        out: *mut *mut git_tag,
+        repo: *mut git_repository,
+        id: *const git_oid,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_tag_message(tag: *const git_tag) -> *const c_char;
+    pub fn git_tag_name(tag: *const git_tag) -> *const c_char;
+    pub fn git_tag_peel(tag_target_out: *mut *mut git_object, tag: *const git_tag) -> c_int;
+    pub fn git_tag_tagger(tag: *const git_tag) -> *const git_signature;
+    pub fn git_tag_target(target_out: *mut *mut git_object, tag: *const git_tag) -> c_int;
+    pub fn git_tag_target_id(tag: *const git_tag) -> *const git_oid;
+    pub fn git_tag_target_type(tag: *const git_tag) -> git_object_t;
+    pub fn git_tag_name_is_valid(valid: *mut c_int, tag_name: *const c_char) -> c_int;
+
+    // checkout
+    pub fn git_checkout_head(repo: *mut git_repository, opts: *const git_checkout_options)
+        -> c_int;
+    pub fn git_checkout_index(
+        repo: *mut git_repository,
+        index: *mut git_index,
+        opts: *const git_checkout_options,
+    ) -> c_int;
+    pub fn git_checkout_tree(
+        repo: *mut git_repository,
+        treeish: *const git_object,
+        opts: *const git_checkout_options,
+    ) -> c_int;
+    pub fn git_checkout_init_options(opts: *mut git_checkout_options, version: c_uint) -> c_int;
+
+    // merge
+    pub fn git_annotated_commit_id(commit: *const git_annotated_commit) -> *const git_oid;
+    pub fn git_annotated_commit_ref(commit: *const git_annotated_commit) -> *const c_char;
+    pub fn git_annotated_commit_from_ref(
+        out: *mut *mut git_annotated_commit,
+        repo: *mut git_repository,
+        reference: *const git_reference,
+    ) -> c_int;
+    pub fn git_annotated_commit_from_fetchhead(
+        out: *mut *mut git_annotated_commit,
+        repo: *mut git_repository,
+        branch_name: *const c_char,
+        remote_url: *const c_char,
+        oid: *const git_oid,
+    ) -> c_int;
+    pub fn git_annotated_commit_free(commit: *mut git_annotated_commit);
+    pub fn git_merge_init_options(opts: *mut git_merge_options, version: c_uint) -> c_int;
+    pub fn git_merge(
+        repo: *mut git_repository,
+        their_heads: *mut *const git_annotated_commit,
+        len: size_t,
+        merge_opts: *const git_merge_options,
+        checkout_opts: *const git_checkout_options,
+    ) -> c_int;
+    pub fn git_merge_commits(
+        out: *mut *mut git_index,
+        repo: *mut git_repository,
+        our_commit: *const git_commit,
+        their_commit: *const git_commit,
+        opts: *const git_merge_options,
+    ) -> c_int;
+    pub fn git_merge_trees(
+        out: *mut *mut git_index,
+        repo: *mut git_repository,
+        ancestor_tree: *const git_tree,
+        our_tree: *const git_tree,
+        their_tree: *const git_tree,
+        opts: *const git_merge_options,
+    ) -> c_int;
+    pub fn git_repository_state_cleanup(repo: *mut git_repository) -> c_int;
+
+    // merge analysis
+
+    pub fn git_merge_analysis(
+        analysis_out: *mut git_merge_analysis_t,
+        pref_out: *mut git_merge_preference_t,
+        repo: *mut git_repository,
+        their_heads: *mut *const git_annotated_commit,
+        their_heads_len: usize,
+    ) -> c_int;
+
+    pub fn git_merge_analysis_for_ref(
+        analysis_out: *mut git_merge_analysis_t,
+        pref_out: *mut git_merge_preference_t,
+        repo: *mut git_repository,
+        git_reference: *mut git_reference,
+        their_heads: *mut *const git_annotated_commit,
+        their_heads_len: usize,
+    ) -> c_int;
+
+    // notes
+    pub fn git_note_author(note: *const git_note) -> *const git_signature;
+    pub fn git_note_committer(note: *const git_note) -> *const git_signature;
+    pub fn git_note_create(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        notes_ref: *const c_char,
+        author: *const git_signature,
+        committer: *const git_signature,
+        oid: *const git_oid,
+        note: *const c_char,
+        force: c_int,
+    ) -> c_int;
+    pub fn git_note_default_ref(out: *mut git_buf, repo: *mut git_repository) -> c_int;
+    pub fn git_note_free(note: *mut git_note);
+    pub fn git_note_id(note: *const git_note) -> *const git_oid;
+    pub fn git_note_iterator_free(it: *mut git_note_iterator);
+    pub fn git_note_iterator_new(
+        out: *mut *mut git_note_iterator,
+        repo: *mut git_repository,
+        notes_ref: *const c_char,
+    ) -> c_int;
+    pub fn git_note_message(note: *const git_note) -> *const c_char;
+    pub fn git_note_next(
+        note_id: *mut git_oid,
+        annotated_id: *mut git_oid,
+        it: *mut git_note_iterator,
+    ) -> c_int;
+    pub fn git_note_read(
+        out: *mut *mut git_note,
+        repo: *mut git_repository,
+        notes_ref: *const c_char,
+        oid: *const git_oid,
+    ) -> c_int;
+    pub fn git_note_remove(
+        repo: *mut git_repository,
+        notes_ref: *const c_char,
+        author: *const git_signature,
+        committer: *const git_signature,
+        oid: *const git_oid,
+    ) -> c_int;
+
+    // blame
+    pub fn git_blame_buffer(
+        out: *mut *mut git_blame,
+        reference: *mut git_blame,
+        buffer: *const c_char,
+        buffer_len: size_t,
+    ) -> c_int;
+    pub fn git_blame_file(
+        out: *mut *mut git_blame,
+        repo: *mut git_repository,
+        path: *const c_char,
+        options: *mut git_blame_options,
+    ) -> c_int;
+    pub fn git_blame_free(blame: *mut git_blame);
+
+    pub fn git_blame_init_options(opts: *mut git_blame_options, version: c_uint) -> c_int;
+    pub fn git_blame_get_hunk_count(blame: *mut git_blame) -> u32;
+
+    pub fn git_blame_get_hunk_byline(blame: *mut git_blame, lineno: usize)
+        -> *const git_blame_hunk;
+    pub fn git_blame_get_hunk_byindex(blame: *mut git_blame, index: u32) -> *const git_blame_hunk;
+
+    // revwalk
+    pub fn git_revwalk_new(out: *mut *mut git_revwalk, repo: *mut git_repository) -> c_int;
+    pub fn git_revwalk_free(walk: *mut git_revwalk);
+
+    pub fn git_revwalk_reset(walk: *mut git_revwalk) -> c_int;
+
+    pub fn git_revwalk_sorting(walk: *mut git_revwalk, sort_mode: c_uint) -> c_int;
+
+    pub fn git_revwalk_push_head(walk: *mut git_revwalk) -> c_int;
+    pub fn git_revwalk_push(walk: *mut git_revwalk, oid: *const git_oid) -> c_int;
+    pub fn git_revwalk_push_ref(walk: *mut git_revwalk, refname: *const c_char) -> c_int;
+    pub fn git_revwalk_push_glob(walk: *mut git_revwalk, glob: *const c_char) -> c_int;
+    pub fn git_revwalk_push_range(walk: *mut git_revwalk, range: *const c_char) -> c_int;
+    pub fn git_revwalk_simplify_first_parent(walk: *mut git_revwalk) -> c_int;
+
+    pub fn git_revwalk_hide_head(walk: *mut git_revwalk) -> c_int;
+    pub fn git_revwalk_hide(walk: *mut git_revwalk, oid: *const git_oid) -> c_int;
+    pub fn git_revwalk_hide_ref(walk: *mut git_revwalk, refname: *const c_char) -> c_int;
+    pub fn git_revwalk_hide_glob(walk: *mut git_revwalk, refname: *const c_char) -> c_int;
+    pub fn git_revwalk_add_hide_cb(
+        walk: *mut git_revwalk,
+        hide_cb: git_revwalk_hide_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+
+    pub fn git_revwalk_next(out: *mut git_oid, walk: *mut git_revwalk) -> c_int;
+
+    // merge
+    pub fn git_merge_base(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        one: *const git_oid,
+        two: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_merge_base_many(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        length: size_t,
+        input_array: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_merge_base_octopus(
+        out: *mut git_oid,
+        repo: *mut git_repository,
+        length: size_t,
+        input_array: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_merge_bases(
+        out: *mut git_oidarray,
+        repo: *mut git_repository,
+        one: *const git_oid,
+        two: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_merge_bases_many(
+        out: *mut git_oidarray,
+        repo: *mut git_repository,
+        length: size_t,
+        input_array: *const git_oid,
+    ) -> c_int;
+
+    // pathspec
+    pub fn git_pathspec_free(ps: *mut git_pathspec);
+    pub fn git_pathspec_match_diff(
+        out: *mut *mut git_pathspec_match_list,
+        diff: *mut git_diff,
+        flags: u32,
+        ps: *mut git_pathspec,
+    ) -> c_int;
+    pub fn git_pathspec_match_index(
+        out: *mut *mut git_pathspec_match_list,
+        index: *mut git_index,
+        flags: u32,
+        ps: *mut git_pathspec,
+    ) -> c_int;
+    pub fn git_pathspec_match_list_diff_entry(
+        m: *const git_pathspec_match_list,
+        pos: size_t,
+    ) -> *const git_diff_delta;
+    pub fn git_pathspec_match_list_entry(
+        m: *const git_pathspec_match_list,
+        pos: size_t,
+    ) -> *const c_char;
+    pub fn git_pathspec_match_list_entrycount(m: *const git_pathspec_match_list) -> size_t;
+    pub fn git_pathspec_match_list_failed_entry(
+        m: *const git_pathspec_match_list,
+        pos: size_t,
+    ) -> *const c_char;
+    pub fn git_pathspec_match_list_failed_entrycount(m: *const git_pathspec_match_list) -> size_t;
+    pub fn git_pathspec_match_list_free(m: *mut git_pathspec_match_list);
+    pub fn git_pathspec_match_tree(
+        out: *mut *mut git_pathspec_match_list,
+        tree: *mut git_tree,
+        flags: u32,
+        ps: *mut git_pathspec,
+    ) -> c_int;
+    pub fn git_pathspec_match_workdir(
+        out: *mut *mut git_pathspec_match_list,
+        repo: *mut git_repository,
+        flags: u32,
+        ps: *mut git_pathspec,
+    ) -> c_int;
+    pub fn git_pathspec_matches_path(
+        ps: *const git_pathspec,
+        flags: u32,
+        path: *const c_char,
+    ) -> c_int;
+    pub fn git_pathspec_new(out: *mut *mut git_pathspec, pathspec: *const git_strarray) -> c_int;
+
+    // diff
+    pub fn git_diff_blob_to_buffer(
+        old_blob: *const git_blob,
+        old_as_path: *const c_char,
+        buffer: *const c_char,
+        buffer_len: size_t,
+        buffer_as_path: *const c_char,
+        options: *const git_diff_options,
+        file_cb: git_diff_file_cb,
+        binary_cb: git_diff_binary_cb,
+        hunk_cb: git_diff_hunk_cb,
+        line_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_diff_blobs(
+        old_blob: *const git_blob,
+        old_as_path: *const c_char,
+        new_blob: *const git_blob,
+        new_as_path: *const c_char,
+        options: *const git_diff_options,
+        file_cb: git_diff_file_cb,
+        binary_cb: git_diff_binary_cb,
+        hunk_cb: git_diff_hunk_cb,
+        line_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_diff_buffers(
+        old_buffer: *const c_void,
+        old_len: size_t,
+        old_as_path: *const c_char,
+        new_buffer: *const c_void,
+        new_len: size_t,
+        new_as_path: *const c_char,
+        options: *const git_diff_options,
+        file_cb: git_diff_file_cb,
+        binary_cb: git_diff_binary_cb,
+        hunk_cb: git_diff_hunk_cb,
+        line_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_diff_from_buffer(
+        diff: *mut *mut git_diff,
+        content: *const c_char,
+        content_len: size_t,
+    ) -> c_int;
+    pub fn git_diff_find_similar(
+        diff: *mut git_diff,
+        options: *const git_diff_find_options,
+    ) -> c_int;
+    pub fn git_diff_find_init_options(opts: *mut git_diff_find_options, version: c_uint) -> c_int;
+    pub fn git_diff_foreach(
+        diff: *mut git_diff,
+        file_cb: git_diff_file_cb,
+        binary_cb: git_diff_binary_cb,
+        hunk_cb: git_diff_hunk_cb,
+        line_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_diff_free(diff: *mut git_diff);
+    pub fn git_diff_get_delta(diff: *const git_diff, idx: size_t) -> *const git_diff_delta;
+    pub fn git_diff_get_stats(out: *mut *mut git_diff_stats, diff: *mut git_diff) -> c_int;
+    pub fn git_diff_index_to_index(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        old_index: *mut git_index,
+        new_index: *mut git_index,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_diff_index_to_workdir(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        index: *mut git_index,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_diff_init_options(opts: *mut git_diff_options, version: c_uint) -> c_int;
+    pub fn git_diff_is_sorted_icase(diff: *const git_diff) -> c_int;
+    pub fn git_diff_merge(onto: *mut git_diff, from: *const git_diff) -> c_int;
+    pub fn git_diff_num_deltas(diff: *const git_diff) -> size_t;
+    pub fn git_diff_num_deltas_of_type(diff: *const git_diff, delta: git_delta_t) -> size_t;
+    pub fn git_diff_print(
+        diff: *mut git_diff,
+        format: git_diff_format_t,
+        print_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_diff_stats_deletions(stats: *const git_diff_stats) -> size_t;
+    pub fn git_diff_stats_files_changed(stats: *const git_diff_stats) -> size_t;
+    pub fn git_diff_stats_free(stats: *mut git_diff_stats);
+    pub fn git_diff_stats_insertions(stats: *const git_diff_stats) -> size_t;
+    pub fn git_diff_stats_to_buf(
+        out: *mut git_buf,
+        stats: *const git_diff_stats,
+        format: git_diff_stats_format_t,
+        width: size_t,
+    ) -> c_int;
+    pub fn git_diff_status_char(status: git_delta_t) -> c_char;
+    pub fn git_diff_tree_to_index(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        old_tree: *mut git_tree,
+        index: *mut git_index,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_diff_tree_to_tree(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        old_tree: *mut git_tree,
+        new_tree: *mut git_tree,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_diff_tree_to_workdir(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        old_tree: *mut git_tree,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_diff_tree_to_workdir_with_index(
+        diff: *mut *mut git_diff,
+        repo: *mut git_repository,
+        old_tree: *mut git_tree,
+        opts: *const git_diff_options,
+    ) -> c_int;
+
+    pub fn git_graph_ahead_behind(
+        ahead: *mut size_t,
+        behind: *mut size_t,
+        repo: *mut git_repository,
+        local: *const git_oid,
+        upstream: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_graph_descendant_of(
+        repo: *mut git_repository,
+        commit: *const git_oid,
+        ancestor: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_diff_format_email(
+        out: *mut git_buf,
+        diff: *mut git_diff,
+        opts: *const git_diff_format_email_options,
+    ) -> c_int;
+    pub fn git_diff_format_email_options_init(
+        opts: *mut git_diff_format_email_options,
+        version: c_uint,
+    ) -> c_int;
+
+    pub fn git_diff_patchid(
+        out: *mut git_oid,
+        diff: *mut git_diff,
+        opts: *mut git_diff_patchid_options,
+    ) -> c_int;
+    pub fn git_diff_patchid_options_init(
+        opts: *mut git_diff_patchid_options,
+        version: c_uint,
+    ) -> c_int;
+
+    // patch
+    pub fn git_patch_from_diff(out: *mut *mut git_patch, diff: *mut git_diff, idx: size_t)
+        -> c_int;
+    pub fn git_patch_from_blobs(
+        out: *mut *mut git_patch,
+        old_blob: *const git_blob,
+        old_as_path: *const c_char,
+        new_blob: *const git_blob,
+        new_as_path: *const c_char,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_patch_from_blob_and_buffer(
+        out: *mut *mut git_patch,
+        old_blob: *const git_blob,
+        old_as_path: *const c_char,
+        buffer: *const c_void,
+        buffer_len: size_t,
+        buffer_as_path: *const c_char,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_patch_from_buffers(
+        out: *mut *mut git_patch,
+        old_buffer: *const c_void,
+        old_len: size_t,
+        old_as_path: *const c_char,
+        new_buffer: *const c_void,
+        new_len: size_t,
+        new_as_path: *const c_char,
+        opts: *const git_diff_options,
+    ) -> c_int;
+    pub fn git_patch_free(patch: *mut git_patch);
+    pub fn git_patch_get_delta(patch: *const git_patch) -> *const git_diff_delta;
+    pub fn git_patch_num_hunks(patch: *const git_patch) -> size_t;
+    pub fn git_patch_line_stats(
+        total_context: *mut size_t,
+        total_additions: *mut size_t,
+        total_deletions: *mut size_t,
+        patch: *const git_patch,
+    ) -> c_int;
+    pub fn git_patch_get_hunk(
+        out: *mut *const git_diff_hunk,
+        lines_in_hunk: *mut size_t,
+        patch: *mut git_patch,
+        hunk_idx: size_t,
+    ) -> c_int;
+    pub fn git_patch_num_lines_in_hunk(patch: *const git_patch, hunk_idx: size_t) -> c_int;
+    pub fn git_patch_get_line_in_hunk(
+        out: *mut *const git_diff_line,
+        patch: *mut git_patch,
+        hunk_idx: size_t,
+        line_of_hunk: size_t,
+    ) -> c_int;
+    pub fn git_patch_size(
+        patch: *mut git_patch,
+        include_context: c_int,
+        include_hunk_headers: c_int,
+        include_file_headers: c_int,
+    ) -> size_t;
+    pub fn git_patch_print(
+        patch: *mut git_patch,
+        print_cb: git_diff_line_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_patch_to_buf(buf: *mut git_buf, patch: *mut git_patch) -> c_int;
+
+    // reflog
+    pub fn git_reflog_append(
+        reflog: *mut git_reflog,
+        id: *const git_oid,
+        committer: *const git_signature,
+        msg: *const c_char,
+    ) -> c_int;
+    pub fn git_reflog_delete(repo: *mut git_repository, name: *const c_char) -> c_int;
+    pub fn git_reflog_drop(
+        reflog: *mut git_reflog,
+        idx: size_t,
+        rewrite_previous_entry: c_int,
+    ) -> c_int;
+    pub fn git_reflog_entry_byindex(
+        reflog: *const git_reflog,
+        idx: size_t,
+    ) -> *const git_reflog_entry;
+    pub fn git_reflog_entry_committer(entry: *const git_reflog_entry) -> *const git_signature;
+    pub fn git_reflog_entry_id_new(entry: *const git_reflog_entry) -> *const git_oid;
+    pub fn git_reflog_entry_id_old(entry: *const git_reflog_entry) -> *const git_oid;
+    pub fn git_reflog_entry_message(entry: *const git_reflog_entry) -> *const c_char;
+    pub fn git_reflog_entrycount(reflog: *mut git_reflog) -> size_t;
+    pub fn git_reflog_free(reflog: *mut git_reflog);
+    pub fn git_reflog_read(
+        out: *mut *mut git_reflog,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_reflog_rename(
+        repo: *mut git_repository,
+        old_name: *const c_char,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_reflog_write(reflog: *mut git_reflog) -> c_int;
+
+    // transport
+    pub fn git_transport_register(
+        prefix: *const c_char,
+        cb: git_transport_cb,
+        param: *mut c_void,
+    ) -> c_int;
+    pub fn git_transport_unregister(prefix: *const c_char) -> c_int;
+    pub fn git_transport_smart(
+        out: *mut *mut git_transport,
+        owner: *mut git_remote,
+        payload: *mut c_void,
+    ) -> c_int;
+
+    // describe
+    pub fn git_describe_commit(
+        result: *mut *mut git_describe_result,
+        object: *mut git_object,
+        opts: *mut git_describe_options,
+    ) -> c_int;
+    pub fn git_describe_format(
+        buf: *mut git_buf,
+        result: *const git_describe_result,
+        opts: *const git_describe_format_options,
+    ) -> c_int;
+    pub fn git_describe_result_free(result: *mut git_describe_result);
+    pub fn git_describe_workdir(
+        out: *mut *mut git_describe_result,
+        repo: *mut git_repository,
+        opts: *mut git_describe_options,
+    ) -> c_int;
+
+    // message
+    pub fn git_message_prettify(
+        out: *mut git_buf,
+        message: *const c_char,
+        strip_comments: c_int,
+        comment_char: c_char,
+    ) -> c_int;
+
+    pub fn git_message_trailers(
+        out: *mut git_message_trailer_array,
+        message: *const c_char,
+    ) -> c_int;
+
+    pub fn git_message_trailer_array_free(trailer: *mut git_message_trailer_array);
+
+    // packbuilder
+    pub fn git_packbuilder_new(out: *mut *mut git_packbuilder, repo: *mut git_repository) -> c_int;
+    pub fn git_packbuilder_set_threads(pb: *mut git_packbuilder, n: c_uint) -> c_uint;
+    pub fn git_packbuilder_insert(
+        pb: *mut git_packbuilder,
+        id: *const git_oid,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_packbuilder_insert_tree(pb: *mut git_packbuilder, id: *const git_oid) -> c_int;
+    pub fn git_packbuilder_insert_commit(pb: *mut git_packbuilder, id: *const git_oid) -> c_int;
+    pub fn git_packbuilder_insert_walk(pb: *mut git_packbuilder, walk: *mut git_revwalk) -> c_int;
+    pub fn git_packbuilder_insert_recur(
+        pb: *mut git_packbuilder,
+        id: *const git_oid,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_packbuilder_write_buf(buf: *mut git_buf, pb: *mut git_packbuilder) -> c_int;
+    pub fn git_packbuilder_write(
+        pb: *mut git_packbuilder,
+        path: *const c_char,
+        mode: c_uint,
+        progress_cb: git_indexer_progress_cb,
+        progress_cb_payload: *mut c_void,
+    ) -> c_int;
+    #[deprecated = "use `git_packbuilder_name` to retrieve the filename"]
+    pub fn git_packbuilder_hash(pb: *mut git_packbuilder) -> *const git_oid;
+    pub fn git_packbuilder_name(pb: *mut git_packbuilder) -> *const c_char;
+    pub fn git_packbuilder_foreach(
+        pb: *mut git_packbuilder,
+        cb: git_packbuilder_foreach_cb,
+        payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_packbuilder_object_count(pb: *mut git_packbuilder) -> size_t;
+    pub fn git_packbuilder_written(pb: *mut git_packbuilder) -> size_t;
+    pub fn git_packbuilder_set_callbacks(
+        pb: *mut git_packbuilder,
+        progress_cb: git_packbuilder_progress,
+        progress_cb_payload: *mut c_void,
+    ) -> c_int;
+    pub fn git_packbuilder_free(pb: *mut git_packbuilder);
+
+    // indexer
+    pub fn git_indexer_new(
+        out: *mut *mut git_indexer,
+        path: *const c_char,
+        mode: c_uint,
+        odb: *mut git_odb,
+        opts: *mut git_indexer_options,
+    ) -> c_int;
+    pub fn git_indexer_append(
+        idx: *mut git_indexer,
+        data: *const c_void,
+        size: size_t,
+        stats: *mut git_indexer_progress,
+    ) -> c_int;
+    pub fn git_indexer_commit(idx: *mut git_indexer, stats: *mut git_indexer_progress) -> c_int;
+    #[deprecated = "use `git_indexer_name` to retrieve the filename"]
+    pub fn git_indexer_hash(idx: *const git_indexer) -> *const git_oid;
+    pub fn git_indexer_name(idx: *const git_indexer) -> *const c_char;
+    pub fn git_indexer_free(idx: *mut git_indexer);
+
+    pub fn git_indexer_options_init(opts: *mut git_indexer_options, version: c_uint) -> c_int;
+
+    // odb
+    pub fn git_repository_odb(out: *mut *mut git_odb, repo: *mut git_repository) -> c_int;
+    pub fn git_odb_new(db: *mut *mut git_odb) -> c_int;
+    pub fn git_odb_free(db: *mut git_odb);
+    pub fn git_odb_open_rstream(
+        out: *mut *mut git_odb_stream,
+        len: *mut size_t,
+        otype: *mut git_object_t,
+        db: *mut git_odb,
+        oid: *const git_oid,
+    ) -> c_int;
+    pub fn git_odb_stream_read(
+        stream: *mut git_odb_stream,
+        buffer: *mut c_char,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_odb_open_wstream(
+        out: *mut *mut git_odb_stream,
+        db: *mut git_odb,
+        size: git_object_size_t,
+        obj_type: git_object_t,
+    ) -> c_int;
+    pub fn git_odb_stream_write(
+        stream: *mut git_odb_stream,
+        buffer: *const c_char,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_odb_stream_finalize_write(id: *mut git_oid, stream: *mut git_odb_stream) -> c_int;
+    pub fn git_odb_stream_free(stream: *mut git_odb_stream);
+    pub fn git_odb_foreach(db: *mut git_odb, cb: git_odb_foreach_cb, payload: *mut c_void)
+        -> c_int;
+
+    pub fn git_odb_read(
+        out: *mut *mut git_odb_object,
+        odb: *mut git_odb,
+        oid: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_odb_read_header(
+        len_out: *mut size_t,
+        type_out: *mut git_object_t,
+        odb: *mut git_odb,
+        oid: *const git_oid,
+    ) -> c_int;
+
+    pub fn git_odb_write(
+        out: *mut git_oid,
+        odb: *mut git_odb,
+        data: *const c_void,
+        len: size_t,
+        otype: git_object_t,
+    ) -> c_int;
+
+    pub fn git_odb_write_pack(
+        out: *mut *mut git_odb_writepack,
+        odb: *mut git_odb,
+        progress_cb: git_indexer_progress_cb,
+        progress_payload: *mut c_void,
+    ) -> c_int;
+
+    pub fn git_odb_hash(
+        out: *mut git_oid,
+        data: *const c_void,
+        len: size_t,
+        otype: git_object_t,
+    ) -> c_int;
+
+    pub fn git_odb_hashfile(out: *mut git_oid, path: *const c_char, otype: git_object_t) -> c_int;
+
+    pub fn git_odb_exists_prefix(
+        out: *mut git_oid,
+        odb: *mut git_odb,
+        short_oid: *const git_oid,
+        len: size_t,
+    ) -> c_int;
+
+    pub fn git_odb_exists(odb: *mut git_odb, oid: *const git_oid) -> c_int;
+    pub fn git_odb_exists_ext(odb: *mut git_odb, oid: *const git_oid, flags: c_uint) -> c_int;
+
+    pub fn git_odb_refresh(odb: *mut git_odb) -> c_int;
+
+    pub fn git_odb_object_id(obj: *mut git_odb_object) -> *const git_oid;
+    pub fn git_odb_object_size(obj: *mut git_odb_object) -> size_t;
+    pub fn git_odb_object_type(obj: *mut git_odb_object) -> git_object_t;
+    pub fn git_odb_object_data(obj: *mut git_odb_object) -> *const c_void;
+    pub fn git_odb_object_dup(out: *mut *mut git_odb_object, obj: *mut git_odb_object) -> c_int;
+    pub fn git_odb_object_free(obj: *mut git_odb_object);
+
+    pub fn git_odb_init_backend(odb: *mut git_odb_backend, version: c_uint) -> c_int;
+
+    pub fn git_odb_add_backend(
+        odb: *mut git_odb,
+        backend: *mut git_odb_backend,
+        priority: c_int,
+    ) -> c_int;
+
+    pub fn git_odb_backend_pack(
+        out: *mut *mut git_odb_backend,
+        objects_dir: *const c_char,
+    ) -> c_int;
+
+    pub fn git_odb_backend_one_pack(
+        out: *mut *mut git_odb_backend,
+        objects_dir: *const c_char,
+    ) -> c_int;
+
+    pub fn git_odb_add_disk_alternate(odb: *mut git_odb, path: *const c_char) -> c_int;
+
+    pub fn git_odb_backend_loose(
+        out: *mut *mut git_odb_backend,
+        objects_dir: *const c_char,
+        compression_level: c_int,
+        do_fsync: c_int,
+        dir_mode: c_uint,
+        file_mode: c_uint,
+    ) -> c_int;
+
+    pub fn git_odb_add_alternate(
+        odb: *mut git_odb,
+        backend: *mut git_odb_backend,
+        priority: c_int,
+    ) -> c_int;
+
+    pub fn git_odb_backend_malloc(backend: *mut git_odb_backend, len: size_t) -> *mut c_void;
+
+    pub fn git_odb_num_backends(odb: *mut git_odb) -> size_t;
+    pub fn git_odb_get_backend(
+        backend: *mut *mut git_odb_backend,
+        odb: *mut git_odb,
+        position: size_t,
+    ) -> c_int;
+
+    // mempack
+    pub fn git_mempack_new(out: *mut *mut git_odb_backend) -> c_int;
+    pub fn git_mempack_reset(backend: *mut git_odb_backend) -> c_int;
+    pub fn git_mempack_dump(
+        pack: *mut git_buf,
+        repo: *mut git_repository,
+        backend: *mut git_odb_backend,
+    ) -> c_int;
+
+    // refdb
+    pub fn git_refdb_new(out: *mut *mut git_refdb, repo: *mut git_repository) -> c_int;
+    pub fn git_refdb_open(out: *mut *mut git_refdb, repo: *mut git_repository) -> c_int;
+    pub fn git_refdb_backend_fs(
+        out: *mut *mut git_refdb_backend,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_refdb_init_backend(backend: *mut git_refdb_backend, version: c_uint) -> c_int;
+    pub fn git_refdb_set_backend(refdb: *mut git_refdb, backend: *mut git_refdb_backend) -> c_int;
+    pub fn git_refdb_compress(refdb: *mut git_refdb) -> c_int;
+    pub fn git_refdb_free(refdb: *mut git_refdb);
+
+    // rebase
+    pub fn git_rebase_init_options(opts: *mut git_rebase_options, version: c_uint) -> c_int;
+    pub fn git_rebase_init(
+        out: *mut *mut git_rebase,
+        repo: *mut git_repository,
+        branch: *const git_annotated_commit,
+        upstream: *const git_annotated_commit,
+        onto: *const git_annotated_commit,
+        opts: *const git_rebase_options,
+    ) -> c_int;
+    pub fn git_rebase_open(
+        out: *mut *mut git_rebase,
+        repo: *mut git_repository,
+        opts: *const git_rebase_options,
+    ) -> c_int;
+    pub fn git_rebase_operation_entrycount(rebase: *mut git_rebase) -> size_t;
+    pub fn git_rebase_operation_current(rebase: *mut git_rebase) -> size_t;
+    pub fn git_rebase_operation_byindex(
+        rebase: *mut git_rebase,
+        idx: size_t,
+    ) -> *mut git_rebase_operation;
+    pub fn git_rebase_orig_head_id(rebase: *mut git_rebase) -> *const git_oid;
+    pub fn git_rebase_orig_head_name(rebase: *mut git_rebase) -> *const c_char;
+    pub fn git_rebase_next(
+        operation: *mut *mut git_rebase_operation,
+        rebase: *mut git_rebase,
+    ) -> c_int;
+    pub fn git_rebase_inmemory_index(index: *mut *mut git_index, rebase: *mut git_rebase) -> c_int;
+    pub fn git_rebase_commit(
+        id: *mut git_oid,
+        rebase: *mut git_rebase,
+        author: *const git_signature,
+        committer: *const git_signature,
+        message_encoding: *const c_char,
+        message: *const c_char,
+    ) -> c_int;
+    pub fn git_rebase_abort(rebase: *mut git_rebase) -> c_int;
+    pub fn git_rebase_finish(rebase: *mut git_rebase, signature: *const git_signature) -> c_int;
+    pub fn git_rebase_free(rebase: *mut git_rebase);
+
+    // cherrypick
+    pub fn git_cherrypick_init_options(opts: *mut git_cherrypick_options, version: c_uint)
+        -> c_int;
+    pub fn git_cherrypick(
+        repo: *mut git_repository,
+        commit: *mut git_commit,
+        options: *const git_cherrypick_options,
+    ) -> c_int;
+    pub fn git_cherrypick_commit(
+        out: *mut *mut git_index,
+        repo: *mut git_repository,
+        cherrypick_commit: *mut git_commit,
+        our_commit: *mut git_commit,
+        mainline: c_uint,
+        merge_options: *const git_merge_options,
+    ) -> c_int;
+
+    // apply
+    pub fn git_apply_options_init(opts: *mut git_apply_options, version: c_uint) -> c_int;
+    pub fn git_apply_to_tree(
+        out: *mut *mut git_index,
+        repo: *mut git_repository,
+        preimage: *mut git_tree,
+        diff: *mut git_diff,
+        options: *const git_apply_options,
+    ) -> c_int;
+    pub fn git_apply(
+        repo: *mut git_repository,
+        diff: *mut git_diff,
+        location: git_apply_location_t,
+        options: *const git_apply_options,
+    ) -> c_int;
+
+    // revert
+    pub fn git_revert_options_init(opts: *mut git_revert_options, version: c_uint) -> c_int;
+    pub fn git_revert_commit(
+        out: *mut *mut git_index,
+        repo: *mut git_repository,
+        revert_commit: *mut git_commit,
+        our_commit: *mut git_commit,
+        mainline: c_uint,
+        merge_options: *const git_merge_options,
+    ) -> c_int;
+    pub fn git_revert(
+        repo: *mut git_repository,
+        commit: *mut git_commit,
+        given_opts: *const git_revert_options,
+    ) -> c_int;
+
+    // Common
+    pub fn git_libgit2_version(major: *mut c_int, minor: *mut c_int, rev: *mut c_int) -> c_int;
+    pub fn git_libgit2_features() -> c_int;
+    pub fn git_libgit2_opts(option: c_int, ...) -> c_int;
+
+    // Worktrees
+    pub fn git_worktree_list(out: *mut git_strarray, repo: *mut git_repository) -> c_int;
+    pub fn git_worktree_lookup(
+        out: *mut *mut git_worktree,
+        repo: *mut git_repository,
+        name: *const c_char,
+    ) -> c_int;
+    pub fn git_worktree_open_from_repository(
+        out: *mut *mut git_worktree,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_worktree_free(wt: *mut git_worktree);
+    pub fn git_worktree_validate(wt: *const git_worktree) -> c_int;
+    pub fn git_worktree_add_options_init(
+        opts: *mut git_worktree_add_options,
+        version: c_uint,
+    ) -> c_int;
+    pub fn git_worktree_add(
+        out: *mut *mut git_worktree,
+        repo: *mut git_repository,
+        name: *const c_char,
+        path: *const c_char,
+        opts: *const git_worktree_add_options,
+    ) -> c_int;
+    pub fn git_worktree_lock(wt: *mut git_worktree, reason: *const c_char) -> c_int;
+    pub fn git_worktree_unlock(wt: *mut git_worktree) -> c_int;
+    pub fn git_worktree_is_locked(reason: *mut git_buf, wt: *const git_worktree) -> c_int;
+    pub fn git_worktree_name(wt: *const git_worktree) -> *const c_char;
+    pub fn git_worktree_path(wt: *const git_worktree) -> *const c_char;
+    pub fn git_worktree_prune_options_init(
+        opts: *mut git_worktree_prune_options,
+        version: c_uint,
+    ) -> c_int;
+    pub fn git_worktree_is_prunable(
+        wt: *mut git_worktree,
+        opts: *mut git_worktree_prune_options,
+    ) -> c_int;
+    pub fn git_worktree_prune(
+        wt: *mut git_worktree,
+        opts: *mut git_worktree_prune_options,
+    ) -> c_int;
+
+    // Ref transactions
+    pub fn git_transaction_new(out: *mut *mut git_transaction, repo: *mut git_repository) -> c_int;
+    pub fn git_transaction_lock_ref(tx: *mut git_transaction, refname: *const c_char) -> c_int;
+    pub fn git_transaction_set_target(
+        tx: *mut git_transaction,
+        refname: *const c_char,
+        target: *const git_oid,
+        sig: *const git_signature,
+        msg: *const c_char,
+    ) -> c_int;
+    pub fn git_transaction_set_symbolic_target(
+        tx: *mut git_transaction,
+        refname: *const c_char,
+        target: *const c_char,
+        sig: *const git_signature,
+        msg: *const c_char,
+    ) -> c_int;
+    pub fn git_transaction_set_reflog(
+        tx: *mut git_transaction,
+        refname: *const c_char,
+        reflog: *const git_reflog,
+    ) -> c_int;
+    pub fn git_transaction_remove(tx: *mut git_transaction, refname: *const c_char) -> c_int;
+    pub fn git_transaction_commit(tx: *mut git_transaction) -> c_int;
+    pub fn git_transaction_free(tx: *mut git_transaction);
+
+    // Mailmap
+    pub fn git_mailmap_new(out: *mut *mut git_mailmap) -> c_int;
+    pub fn git_mailmap_from_buffer(
+        out: *mut *mut git_mailmap,
+        buf: *const c_char,
+        len: size_t,
+    ) -> c_int;
+    pub fn git_mailmap_from_repository(
+        out: *mut *mut git_mailmap,
+        repo: *mut git_repository,
+    ) -> c_int;
+    pub fn git_mailmap_free(mm: *mut git_mailmap);
+    pub fn git_mailmap_resolve_signature(
+        out: *mut *mut git_signature,
+        mm: *const git_mailmap,
+        sig: *const git_signature,
+    ) -> c_int;
+    pub fn git_mailmap_add_entry(
+        mm: *mut git_mailmap,
+        real_name: *const c_char,
+        real_email: *const c_char,
+        replace_name: *const c_char,
+        replace_email: *const c_char,
+    ) -> c_int;
+
+    // email
+    pub fn git_email_create_from_diff(
+        out: *mut git_buf,
+        diff: *mut git_diff,
+        patch_idx: usize,
+        patch_count: usize,
+        commit_id: *const git_oid,
+        summary: *const c_char,
+        body: *const c_char,
+        author: *const git_signature,
+        given_opts: *const git_email_create_options,
+    ) -> c_int;
+
+    pub fn git_email_create_from_commit(
+        out: *mut git_buf,
+        commit: *mut git_commit,
+        given_opts: *const git_email_create_options,
+    ) -> c_int;
+
+    pub fn git_trace_set(level: git_trace_level_t, cb: git_trace_cb) -> c_int;
+}
+
+pub fn init() {
+    use std::sync::Once;
+
+    static INIT: Once = Once::new();
+    INIT.call_once(|| unsafe {
+        openssl_init();
+        ssh_init();
+        let rc = git_libgit2_init();
+        if rc >= 0 {
+            // Note that we intentionally never schedule `git_libgit2_shutdown`
+            // to get called. There's not really a great time to call that and
+            // #276 has some more info about how automatically doing it can
+            // cause problems.
+            return;
+        }
+
+        let git_error = git_error_last();
+        let error = if !git_error.is_null() {
+            CStr::from_ptr((*git_error).message).to_string_lossy()
+        } else {
+            "unknown error".into()
+        };
+        panic!(
+            "couldn't initialize the libgit2 library: {}, error: {}",
+            rc, error
+        );
+    });
+}
+
+#[cfg(all(unix, feature = "https"))]
+#[doc(hidden)]
+pub fn openssl_init() {
+    openssl_sys::init();
+}
+
+#[cfg(any(windows, not(feature = "https")))]
+#[doc(hidden)]
+pub fn openssl_init() {}
+
+#[cfg(feature = "ssh")]
+fn ssh_init() {
+    libssh2::init();
+}
+
+#[cfg(not(feature = "ssh"))]
+fn ssh_init() {}
+
+#[doc(hidden)]
+pub fn vendored() -> bool {
+    cfg!(libgit2_vendored)
+}
diff --git a/sha1-checked-0.10.0/.cargo-checksum.json b/sha1-checked-0.10.0/.cargo-checksum.json
new file mode 100644 (file)
index 0000000..e6e44ae
--- /dev/null
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"66380ce326b6b03522fa2b3ae8a5d23453378d9421822160959b0fe98e75aebb","Cargo.toml":"6c9b65bf842b71222af16c58f2f6616e82c7390e765199e23a465481e3c17c21","LICENSE-APACHE":"a9040321c3712d8fd0b09cf52b17445de04a23a10165049ae187cd39e5c86be5","LICENSE-MIT":"6d6da4c40195ecbd22595195acd91e1b50bf61bfe0275fa57f8c4d0290f72674","README.md":"fc17b4c8065ccecc63e45cf1e091b8029311655db02bbab5000270346f9c642c","benches/mod.rs":"019335b3f8c28d0626b7cda461659ddf8753c9d21922cef78a26fb10373768f9","src/compress.rs":"b7eadea87d7495fafd8232a5a667f039e61503e41d57f5f21c5d73f51ad29d0b","src/lib.rs":"917cd14ae4af8965f99d97a4a5b5bf9466fca9c14dc2af186f893922119c2c5e","src/ubc_check.rs":"24bce5b8aecfb4394ad961aa2c4112335f0b374e2961971f5843e3175468a904","tests/data/sha-mbles-1.bin":"3ead211681cec93d265c8ac123dd062e105408cebf82fa6e2b126f4f40bcb88c","tests/data/sha-mbles-2.bin":"208feafe1c6a95c73f662514ac48761f25e1f3b74922521a98d9ce287f4a2197","tests/data/sha1.blb":"b9c03b9e56e0a7b08a6d6867599a33cab1a55aec3f41fef910c133fc35dc2851","tests/data/sha1_reducedsha_coll.bin":"f238052907fa3ce28e550806a61f30a73b3394185e63c290439053bef45b3216","tests/data/shattered-1.pdf":"2bb787a73e37352f92383abe7e2902936d1059ad9f1ba6daaa9c1e58ee6970d0","tests/data/shattered-2.pdf":"d4488775d29bdef7993367d541064dbdda50d383f89f0aa13a6ff2e0894ba5ff","tests/mod.rs":"2b5d76415769e882174ee00114bb4cb1955b05261a341b039e0f96ae44408d1f"},"package":"89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"}
\ No newline at end of file
diff --git a/sha1-checked-0.10.0/CHANGELOG.md b/sha1-checked-0.10.0/CHANGELOG.md
new file mode 100644 (file)
index 0000000..dadf785
--- /dev/null
@@ -0,0 +1,8 @@
+# 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).
+
+## UNRELEASED
diff --git a/sha1-checked-0.10.0/Cargo.toml b/sha1-checked-0.10.0/Cargo.toml
new file mode 100644 (file)
index 0000000..45cdc43
--- /dev/null
@@ -0,0 +1,78 @@
+# 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.72"
+name = "sha1-checked"
+version = "0.10.0"
+authors = ["RustCrypto Developers"]
+exclude = [
+    "sha1-checked/tests/data/shattered-1.pdf",
+    "sha1-checked/tests/data/shattered-2.pdf",
+]
+description = "SHA-1 hash function with collision detection"
+documentation = "https://docs.rs/sha1-checked"
+readme = "README.md"
+keywords = [
+    "crypto",
+    "sha1",
+    "hash",
+    "digest",
+]
+categories = [
+    "cryptography",
+    "no-std",
+]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/RustCrypto/hashes"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+    "--cfg",
+    "docsrs",
+]
+
+[dependencies.digest]
+version = "0.10.7"
+
+[dependencies.sha1]
+version = "0.10.6"
+features = ["compress"]
+default-features = false
+
+[dependencies.zeroize]
+version = "1.7"
+optional = true
+default-features = false
+
+[dev-dependencies.digest]
+version = "0.10.7"
+features = ["dev"]
+
+[dev-dependencies.hex-literal]
+version = "0.4"
+
+[features]
+default = [
+    "oid",
+    "std",
+]
+oid = [
+    "digest/oid",
+    "sha1/oid",
+]
+std = [
+    "digest/std",
+    "sha1/std",
+]
+zeroize = ["dep:zeroize"]
diff --git a/sha1-checked-0.10.0/LICENSE-APACHE b/sha1-checked-0.10.0/LICENSE-APACHE
new file mode 100644 (file)
index 0000000..78173fa
--- /dev/null
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/sha1-checked-0.10.0/LICENSE-MIT b/sha1-checked-0.10.0/LICENSE-MIT
new file mode 100644 (file)
index 0000000..455de7c
--- /dev/null
@@ -0,0 +1,25 @@
+Copyright (c) 2024 The RustCrypto Project Developers
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/sha1-checked-0.10.0/README.md b/sha1-checked-0.10.0/README.md
new file mode 100644 (file)
index 0000000..32c4bc9
--- /dev/null
@@ -0,0 +1,98 @@
+# RustCrypto: SHA-1 Checked
+
+[![crate][crate-image]][crate-link]
+[![Docs][docs-image]][docs-link]
+![Apache2/MIT licensed][license-image]
+![Rust Version][rustc-image]
+[![Project Chat][chat-image]][chat-link]
+[![Build Status][build-image]][build-link]
+
+Pure Rust implementation of the [SHA-1] cryptographic hash algorithm with collision detection.
+
+## 🚨 Warning: Cryptographically Broken! 🚨
+
+The SHA-1 hash function should be considered cryptographically broken and
+unsuitable for further use in any security critical capacity, as it is
+[practically vulnerable to chosen-prefix collisions][1].
+
+But, this crate provides the detection [algorithm] pioneered by git, to detect hash collisions when they
+occur and prevent them. The [paper] has more details on how this works.
+
+This implementation will be slower to use than the pure SHA-1 implementation, as more work as to be done.
+
+## Examples
+
+### One-shot API
+
+```rust
+use hex_literal::hex;
+use sha1_checked::Sha1;
+
+let result = Sha1::try_digest(b"hello world");
+assert_eq!(result.hash().as_ref(), hex!("2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"));
+assert!(!result.has_collision());
+```
+
+### Incremental API
+
+```rust
+use hex_literal::hex;
+use sha1_checked::{Sha1, Digest};
+
+let mut hasher = Sha1::new();
+hasher.update(b"hello world");
+let result = hasher.try_finalize();
+
+assert_eq!(result.hash().as_ref(), hex!("2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"));
+assert!(!result.has_collision());
+```
+
+Also, see the [examples section] in the RustCrypto/hashes readme.
+
+## Minimum Supported Rust Version
+
+Rust **1.72** or higher.
+
+Minimum supported Rust version can be changed in the future, but it will be
+done with a minor version bump.
+
+## SemVer Policy
+
+- All on-by-default features of this library are covered by SemVer
+- MSRV is considered exempt from SemVer as noted above
+
+## License
+
+The crate is licensed under either of:
+
+* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)
+* [MIT license](http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
+
+[//]: # (badges)
+
+[crate-image]: https://img.shields.io/crates/v/sha1-checked.svg
+[crate-link]: https://crates.io/crates/sha1-checked
+[docs-image]: https://docs.rs/sha1-checked/badge.svg
+[docs-link]: https://docs.rs/sha1-checked/
+[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.72+-blue.svg
+[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
+[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes
+[build-image]: https://github.com/RustCrypto/hashes/workflows/sha1-checked/badge.svg?branch=master
+[build-link]: https://github.com/RustCrypto/hashes/actions?query=workflow%3Asha1-checked
+
+[//]: # (general links)
+
+[SHA-1]: https://en.wikipedia.org/wiki/SHA-1
+[1]: https://sha-mbles.github.io/
+[examples section]: https://github.com/RustCrypto/hashes#Examples
+[algorithm]: https://github.com/cr-marcstevens/sha1collisiondetection
+[paper]: https://marc-stevens.nl/research/papers/C13-S.pdf
diff --git a/sha1-checked-0.10.0/src/compress.rs b/sha1-checked-0.10.0/src/compress.rs
new file mode 100644 (file)
index 0000000..60e4bf8
--- /dev/null
@@ -0,0 +1,787 @@
+//! Direct translation of the C code found at
+//! [sha1.c](https://github.com/cr-marcstevens/sha1collisiondetection/blob/master/lib/sha1.c).
+//!
+//! For the original license and source details see the comments in `src/checked.rs`.
+
+#![allow(clippy::many_single_char_names, clippy::too_many_arguments)]
+
+use crate::{
+    BLOCK_SIZE,
+    {ubc_check::Testt, DetectionState},
+};
+
+const K: [u32; 4] = [0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6];
+
+#[inline(always)]
+fn mix(w: &mut [u32; 80], t: usize) -> u32 {
+    (w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]).rotate_left(1)
+}
+
+#[inline(always)]
+fn f1(b: u32, c: u32, d: u32) -> u32 {
+    d ^ b & (c ^ d)
+}
+
+#[inline(always)]
+fn f2(b: u32, c: u32, d: u32) -> u32 {
+    b ^ c ^ d
+}
+
+#[inline(always)]
+fn f3(b: u32, c: u32, d: u32) -> u32 {
+    (b & c).wrapping_add(d & (b ^ c))
+}
+
+#[inline(always)]
+fn f4(b: u32, c: u32, d: u32) -> u32 {
+    b ^ c ^ d
+}
+
+#[inline(always)]
+fn round3_step(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *e = e.wrapping_add(
+        a.rotate_left(5)
+            .wrapping_add(f3(*b, c, d))
+            .wrapping_add(K[2])
+            .wrapping_add(mt),
+    );
+    *b = b.rotate_left(30);
+}
+
+#[inline(always)]
+fn round4_step(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *e = e.wrapping_add(
+        a.rotate_left(5)
+            .wrapping_add(f4(*b, c, d))
+            .wrapping_add(K[3])
+            .wrapping_add(mt),
+    );
+    *b = b.rotate_left(30);
+}
+
+#[inline(always)]
+fn round1_step_bw(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *b = b.rotate_right(30);
+    *e = e.wrapping_sub(
+        a.rotate_left(5)
+            .wrapping_add(f1(*b, c, d))
+            .wrapping_add(K[0])
+            .wrapping_add(mt),
+    );
+}
+
+#[inline(always)]
+fn round2_step_bw(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *b = b.rotate_right(30);
+    *e = e.wrapping_sub(
+        a.rotate_left(5)
+            .wrapping_add(f2(*b, c, d))
+            .wrapping_add(K[1])
+            .wrapping_add(mt),
+    );
+}
+
+#[inline(always)]
+fn round3_step_bw(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *b = b.rotate_right(30);
+    *e = e.wrapping_sub(
+        a.rotate_left(5)
+            .wrapping_add(f3(*b, c, d))
+            .wrapping_add(K[2])
+            .wrapping_add(mt),
+    );
+}
+
+#[inline(always)]
+fn round4_step_bw(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, mt: u32) {
+    *b = b.rotate_right(30);
+    *e = e.wrapping_sub(
+        a.rotate_left(5)
+            .wrapping_add(f4(*b, c, d))
+            .wrapping_add(K[3])
+            .wrapping_add(mt),
+    );
+}
+
+#[inline(always)]
+fn full_round3_step(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, w: &mut [u32; 80], t: usize) {
+    w[t] = mix(w, t);
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f3(*b, c, d))
+            .wrapping_add(K[2]),
+    );
+    *b = b.rotate_left(30);
+}
+
+#[inline(always)]
+fn full_round4_step(a: u32, b: &mut u32, c: u32, d: u32, e: &mut u32, w: &mut [u32; 80], t: usize) {
+    w[t] = mix(w, t);
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f4(*b, c, d))
+            .wrapping_add(K[3]),
+    );
+    *b = b.rotate_left(30);
+}
+
+#[inline]
+fn round2_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &[u32; 80],
+    t: usize,
+) {
+    // 1
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f2(*b, *c, *d))
+            .wrapping_add(K[1]),
+    );
+    *b = b.rotate_left(30);
+
+    // 2
+    *d = d.wrapping_add(
+        w[t + 1]
+            .wrapping_add(e.rotate_left(5))
+            .wrapping_add(f2(*a, *b, *c))
+            .wrapping_add(K[1]),
+    );
+    *a = a.rotate_left(30);
+
+    // 3
+    *c = c.wrapping_add(
+        w[t + 2]
+            .wrapping_add(d.rotate_left(5))
+            .wrapping_add(f2(*e, *a, *b))
+            .wrapping_add(K[1]),
+    );
+    *e = e.rotate_left(30);
+
+    // 4
+    *b = b.wrapping_add(
+        w[t + 3]
+            .wrapping_add(c.rotate_left(5))
+            .wrapping_add(f2(*d, *e, *a))
+            .wrapping_add(K[1]),
+    );
+    *d = d.rotate_left(30);
+}
+
+#[inline]
+fn round3_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &[u32; 80],
+    t: usize,
+) {
+    // 1
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f3(*b, *c, *d))
+            .wrapping_add(K[2]),
+    );
+    *b = b.rotate_left(30);
+
+    // 2
+    *d = d.wrapping_add(
+        w[t + 1]
+            .wrapping_add(e.rotate_left(5))
+            .wrapping_add(f3(*a, *b, *c))
+            .wrapping_add(K[2]),
+    );
+    *a = a.rotate_left(30);
+
+    // 3
+    *c = c.wrapping_add(
+        w[t + 2]
+            .wrapping_add(d.rotate_left(5))
+            .wrapping_add(f3(*e, *a, *b))
+            .wrapping_add(K[2]),
+    );
+    *e = e.rotate_left(30);
+
+    // 4
+    *b = b.wrapping_add(
+        w[t + 3]
+            .wrapping_add(c.rotate_left(5))
+            .wrapping_add(f3(*d, *e, *a))
+            .wrapping_add(K[2]),
+    );
+    *d = d.rotate_left(30);
+}
+
+#[inline]
+fn round4_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &[u32; 80],
+    t: usize,
+) {
+    // 1
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f4(*b, *c, *d))
+            .wrapping_add(K[3]),
+    );
+    *b = b.rotate_left(30);
+
+    // 2
+    *d = d.wrapping_add(
+        w[t + 1]
+            .wrapping_add(e.rotate_left(5))
+            .wrapping_add(f4(*a, *b, *c))
+            .wrapping_add(K[3]),
+    );
+    *a = a.rotate_left(30);
+
+    // 3
+    *c = c.wrapping_add(
+        w[t + 2]
+            .wrapping_add(d.rotate_left(5))
+            .wrapping_add(f4(*e, *a, *b))
+            .wrapping_add(K[3]),
+    );
+    *e = e.rotate_left(30);
+
+    // 4
+    *b = b.wrapping_add(
+        w[t + 3]
+            .wrapping_add(c.rotate_left(5))
+            .wrapping_add(f4(*d, *e, *a))
+            .wrapping_add(K[3]),
+    );
+    *d = d.rotate_left(30);
+}
+
+#[inline]
+fn full_round1_step_load4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    m: &[u32; 16],
+    w: &mut [u32; 80],
+    t: usize,
+) {
+    // load
+    w[t..t + 4].copy_from_slice(&m[t..t + 4]);
+    round1_step4(a, b, c, d, e, w, t);
+}
+
+#[inline(always)]
+fn round1_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &[u32; 80],
+    t: usize,
+) {
+    // 1
+    *e = e.wrapping_add(
+        w[t].wrapping_add(a.rotate_left(5))
+            .wrapping_add(f1(*b, *c, *d))
+            .wrapping_add(K[0]),
+    );
+    *b = b.rotate_left(30);
+
+    // 2
+    *d = d.wrapping_add(
+        w[t + 1]
+            .wrapping_add(e.rotate_left(5))
+            .wrapping_add(f1(*a, *b, *c))
+            .wrapping_add(K[0]),
+    );
+    *a = a.rotate_left(30);
+
+    // 3
+    *c = c.wrapping_add(
+        w[t + 2]
+            .wrapping_add(d.rotate_left(5))
+            .wrapping_add(f1(*e, *a, *b))
+            .wrapping_add(K[0]),
+    );
+    *e = e.rotate_left(30);
+
+    // 4
+    *b = b.wrapping_add(
+        w[t + 3]
+            .wrapping_add(c.rotate_left(5))
+            .wrapping_add(f1(*d, *e, *a))
+            .wrapping_add(K[0]),
+    );
+    *d = d.rotate_left(30);
+}
+
+#[inline]
+fn full_round1_step_expand4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &mut [u32; 80],
+    t: usize,
+) {
+    w[t] = mix(w, t);
+    w[t + 1] = mix(w, t + 1);
+    w[t + 2] = mix(w, t + 2);
+    w[t + 3] = mix(w, t + 3);
+    round1_step4(a, b, c, d, e, w, t);
+}
+
+#[inline]
+fn full_round2_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &mut [u32; 80],
+    t: usize,
+) {
+    w[t] = mix(w, t);
+    w[t + 1] = mix(w, t + 1);
+    w[t + 2] = mix(w, t + 2);
+    w[t + 3] = mix(w, t + 3);
+    round2_step4(a, b, c, d, e, w, t);
+}
+
+#[inline]
+fn full_round3_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &mut [u32; 80],
+    t: usize,
+) {
+    w[t] = mix(w, t);
+    w[t + 1] = mix(w, t + 1);
+    w[t + 2] = mix(w, t + 2);
+    w[t + 3] = mix(w, t + 3);
+    round3_step4(a, b, c, d, e, w, t);
+}
+
+#[inline]
+fn full_round4_step4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    w: &mut [u32; 80],
+    t: usize,
+) {
+    w[t] = mix(w, t);
+    w[t + 1] = mix(w, t + 1);
+    w[t + 2] = mix(w, t + 2);
+    w[t + 3] = mix(w, t + 3);
+    round4_step4(a, b, c, d, e, w, t);
+}
+
+#[inline]
+fn round1_step_bw4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    m: &[u32; 80],
+    t: usize,
+) {
+    round1_step_bw(*a, b, *c, *d, e, m[t]);
+    round1_step_bw(*b, c, *d, *e, a, m[t - 1]);
+    round1_step_bw(*c, d, *e, *a, b, m[t - 2]);
+    round1_step_bw(*d, e, *a, *b, c, m[t - 3]);
+}
+
+#[inline]
+fn round2_step_bw4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    m: &[u32; 80],
+    t: usize,
+) {
+    round2_step_bw(*a, b, *c, *d, e, m[t]);
+    round2_step_bw(*b, c, *d, *e, a, m[t - 1]);
+    round2_step_bw(*c, d, *e, *a, b, m[t - 2]);
+    round2_step_bw(*d, e, *a, *b, c, m[t - 3]);
+}
+
+#[inline]
+fn round3_step_bw4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    m: &[u32; 80],
+    t: usize,
+) {
+    round3_step_bw(*a, b, *c, *d, e, m[t]);
+    round3_step_bw(*b, c, *d, *e, a, m[t - 1]);
+    round3_step_bw(*c, d, *e, *a, b, m[t - 2]);
+    round3_step_bw(*d, e, *a, *b, c, m[t - 3]);
+}
+
+#[inline]
+fn round4_step_bw4(
+    a: &mut u32,
+    b: &mut u32,
+    c: &mut u32,
+    d: &mut u32,
+    e: &mut u32,
+    m: &[u32; 80],
+    t: usize,
+) {
+    round4_step_bw(*a, b, *c, *d, e, m[t]);
+    round4_step_bw(*b, c, *d, *e, a, m[t - 1]);
+    round4_step_bw(*c, d, *e, *a, b, m[t - 2]);
+    round4_step_bw(*d, e, *a, *b, c, m[t - 3]);
+}
+
+fn add_assign(left: &mut [u32; 5], right: [u32; 5]) {
+    left[0] = left[0].wrapping_add(right[0]);
+    left[1] = left[1].wrapping_add(right[1]);
+    left[2] = left[2].wrapping_add(right[2]);
+    left[3] = left[3].wrapping_add(right[3]);
+    left[4] = left[4].wrapping_add(right[4]);
+}
+
+fn compression_w(ihv: &mut [u32; 5], w: &[u32; 80]) {
+    let [mut a, mut b, mut c, mut d, mut e] = ihv;
+
+    round1_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 0);
+    round1_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 4);
+    round1_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 8);
+    round1_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 12);
+    round1_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 16);
+
+    round2_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 20);
+    round2_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 24);
+    round2_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 28);
+    round2_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 32);
+    round2_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 36);
+
+    round3_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 40);
+    round3_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 44);
+    round3_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 48);
+    round3_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 52);
+    round3_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 56);
+
+    round4_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 60);
+    round4_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 64);
+    round4_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 68);
+    round4_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 72);
+    round4_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 76);
+
+    add_assign(ihv, [a, b, c, d, e]);
+}
+
+fn compression_states(
+    ihv: &mut [u32; 5],
+    m: &[u32; 16],
+    w: &mut [u32; 80],
+    state_58: &mut [u32; 5],
+    state_65: &mut [u32; 5],
+) {
+    let [mut a, mut b, mut c, mut d, mut e] = ihv;
+
+    full_round1_step_load4(&mut a, &mut b, &mut c, &mut d, &mut e, m, w, 0);
+    full_round1_step_load4(&mut b, &mut c, &mut d, &mut e, &mut a, m, w, 4);
+    full_round1_step_load4(&mut c, &mut d, &mut e, &mut a, &mut b, m, w, 8);
+    full_round1_step_load4(&mut d, &mut e, &mut a, &mut b, &mut c, m, w, 12);
+
+    full_round1_step_expand4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 16);
+
+    full_round2_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 20);
+    full_round2_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 24);
+    full_round2_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 28);
+    full_round2_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 32);
+    full_round2_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 36);
+
+    full_round3_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 40);
+    full_round3_step4(&mut b, &mut c, &mut d, &mut e, &mut a, w, 44);
+    full_round3_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 48);
+    full_round3_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 52);
+
+    full_round3_step(e, &mut a, b, c, &mut d, w, 56);
+    full_round3_step(d, &mut e, a, b, &mut c, w, 57);
+
+    // Store state58
+    *state_58 = [a, b, c, d, e];
+
+    full_round3_step(c, &mut d, e, a, &mut b, w, 58);
+    full_round3_step(b, &mut c, d, e, &mut a, w, 59);
+
+    full_round4_step4(&mut a, &mut b, &mut c, &mut d, &mut e, w, 60);
+    full_round4_step(b, &mut c, d, e, &mut a, w, 64);
+
+    // Store state65
+    *state_65 = [a, b, c, d, e];
+
+    full_round4_step(a, &mut b, c, d, &mut e, w, 65);
+    full_round4_step(e, &mut a, b, c, &mut d, w, 66);
+    full_round4_step(d, &mut e, a, b, &mut c, w, 67);
+
+    full_round4_step4(&mut c, &mut d, &mut e, &mut a, &mut b, w, 68);
+    full_round4_step4(&mut d, &mut e, &mut a, &mut b, &mut c, w, 72);
+    full_round4_step4(&mut e, &mut a, &mut b, &mut c, &mut d, w, 76);
+
+    add_assign(ihv, [a, b, c, d, e]);
+}
+
+fn recompress_fast_58(
+    ihvin: &mut [u32; 5],
+    ihvout: &mut [u32; 5],
+    me2: &[u32; 80],
+    state: &[u32; 5],
+) {
+    let [mut a, mut b, mut c, mut d, mut e] = state;
+
+    round3_step_bw(d, &mut e, a, b, &mut c, me2[57]);
+    round3_step_bw(e, &mut a, b, c, &mut d, me2[56]);
+
+    round3_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 55);
+    round3_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 51);
+    round3_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 47);
+    round3_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 43);
+
+    round2_step_bw4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 39);
+    round2_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 35);
+    round2_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 31);
+    round2_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 27);
+    round2_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 23);
+
+    round1_step_bw4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 19);
+    round1_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 15);
+    round1_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 11);
+    round1_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 7);
+    round1_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 3);
+
+    *ihvin = [a, b, c, d, e];
+    [a, b, c, d, e] = *state;
+
+    round3_step(c, &mut d, e, a, &mut b, me2[58]);
+    round3_step(b, &mut c, d, e, &mut a, me2[59]);
+
+    round4_step4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 60);
+    round4_step4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 64);
+    round4_step4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 68);
+    round4_step4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 72);
+    round4_step4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 76);
+
+    ihvout[0] = ihvin[0].wrapping_add(a);
+    ihvout[1] = ihvin[1].wrapping_add(b);
+    ihvout[2] = ihvin[2].wrapping_add(c);
+    ihvout[3] = ihvin[3].wrapping_add(d);
+    ihvout[4] = ihvin[4].wrapping_add(e);
+}
+
+fn recompress_fast_65(
+    ihvin: &mut [u32; 5],
+    ihvout: &mut [u32; 5],
+    me2: &[u32; 80],
+    state: &[u32; 5],
+) {
+    let [mut a, mut b, mut c, mut d, mut e] = state;
+
+    round4_step_bw(b, &mut c, d, e, &mut a, me2[64]);
+    round4_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 63);
+
+    round3_step_bw4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 59);
+    round3_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 55);
+    round3_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 51);
+    round3_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 47);
+    round3_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 43);
+
+    round2_step_bw4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 39);
+    round2_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 35);
+    round2_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 31);
+    round2_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 27);
+    round2_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 23);
+
+    round1_step_bw4(&mut b, &mut c, &mut d, &mut e, &mut a, me2, 19);
+    round1_step_bw4(&mut a, &mut b, &mut c, &mut d, &mut e, me2, 15);
+    round1_step_bw4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 11);
+    round1_step_bw4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 7);
+    round1_step_bw4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 3);
+
+    *ihvin = [a, b, c, d, e];
+    [a, b, c, d, e] = *state;
+
+    round4_step(a, &mut b, c, d, &mut e, me2[65]);
+    round4_step(e, &mut a, b, c, &mut d, me2[66]);
+    round4_step(d, &mut e, a, b, &mut c, me2[67]);
+
+    round4_step4(&mut c, &mut d, &mut e, &mut a, &mut b, me2, 68);
+    round4_step4(&mut d, &mut e, &mut a, &mut b, &mut c, me2, 72);
+    round4_step4(&mut e, &mut a, &mut b, &mut c, &mut d, me2, 76);
+
+    ihvout[0] = ihvin[0].wrapping_add(a);
+    ihvout[1] = ihvin[1].wrapping_add(b);
+    ihvout[2] = ihvin[2].wrapping_add(c);
+    ihvout[3] = ihvin[3].wrapping_add(d);
+    ihvout[4] = ihvin[4].wrapping_add(e);
+}
+
+fn recompression_step(
+    step: Testt,
+    ihvin: &mut [u32; 5],
+    ihvout: &mut [u32; 5],
+    me2: &[u32; 80],
+    state: &[u32; 5],
+) {
+    match step {
+        Testt::T58 => {
+            recompress_fast_58(ihvin, ihvout, me2, state);
+        }
+        Testt::T65 => {
+            recompress_fast_65(ihvin, ihvout, me2, state);
+        }
+    }
+}
+
+#[inline(always)]
+fn xor(a: &[u32; 5], b: &[u32; 5]) -> u32 {
+    a[0] ^ b[0] | a[1] ^ b[1] | a[2] ^ b[2] | a[3] ^ b[3] | a[4] ^ b[4]
+}
+
+#[inline]
+pub(super) fn compress(
+    state: &mut [u32; 5],
+    ctx: &mut DetectionState,
+    blocks: &[[u8; BLOCK_SIZE]],
+) {
+    let mut block_u32 = [0u32; BLOCK_SIZE / 4];
+
+    for block in blocks.iter() {
+        ctx.ihv1.copy_from_slice(&*state);
+
+        for (o, chunk) in block_u32.iter_mut().zip(block.chunks_exact(4)) {
+            *o = u32::from_be_bytes(chunk.try_into().unwrap());
+        }
+
+        let DetectionState {
+            m1,
+            state_58,
+            state_65,
+            ..
+        } = ctx;
+
+        compression_states(state, &block_u32, m1, state_58, state_65);
+
+        let ubc_mask = if ctx.ubc_check {
+            crate::ubc_check::ubc_check(&ctx.m1)
+        } else {
+            0xFFFFFFFF
+        };
+
+        if ubc_mask != 0 {
+            let mut ihvtmp = [0u32; 5];
+            for dv_type in &crate::ubc_check::SHA1_DVS {
+                if ubc_mask & (1 << dv_type.maskb) != 0 {
+                    for ((m2, m1), dm) in
+                        ctx.m2.iter_mut().zip(ctx.m1.iter()).zip(dv_type.dm.iter())
+                    {
+                        *m2 = m1 ^ dm;
+                    }
+                    let DetectionState {
+                        ihv2,
+                        m2,
+                        state_58,
+                        state_65,
+                        ..
+                    } = ctx;
+
+                    recompression_step(
+                        dv_type.testt,
+                        ihv2,
+                        &mut ihvtmp,
+                        m2,
+                        match dv_type.testt {
+                            Testt::T58 => state_58,
+                            Testt::T65 => state_65,
+                        },
+                    );
+
+                    // to verify SHA-1 collision detection code with collisions for reduced-step SHA-1
+                    if (0 == xor(&ihvtmp, &*state))
+                        || (ctx.reduced_round_collision && 0 == xor(&ctx.ihv1, &ctx.ihv2))
+                    {
+                        ctx.found_collision = true;
+
+                        if ctx.safe_hash {
+                            compression_w(state, &ctx.m1);
+                            compression_w(state, &ctx.m1);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+const SHA1_PADDING: [u8; 64] = [
+    0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0,
+];
+
+#[inline]
+pub(super) fn finalize(
+    state: &mut [u32; 5],
+    total: u64,
+    last_block: &[u8],
+    ctx: &mut DetectionState,
+) {
+    let mut total = total + last_block.len() as u64;
+    let last = last_block.len();
+    let needs_two_blocks = last >= 56;
+
+    let mut buffer = [0u8; BLOCK_SIZE];
+    buffer[..last].copy_from_slice(last_block);
+    let left = BLOCK_SIZE - last;
+
+    if needs_two_blocks {
+        let padn = 120 - last;
+        let (pad0, pad1) = SHA1_PADDING[..padn].split_at(left);
+        buffer[last..].copy_from_slice(pad0);
+        compress(state, ctx, &[buffer]);
+        buffer[..pad1.len()].copy_from_slice(pad1);
+    } else {
+        let padn = 56 - last;
+        buffer[last..56].copy_from_slice(&SHA1_PADDING[..padn]);
+    }
+
+    total <<= 3;
+
+    buffer[56] = (total >> 56) as u8;
+    buffer[57] = (total >> 48) as u8;
+    buffer[58] = (total >> 40) as u8;
+    buffer[59] = (total >> 32) as u8;
+    buffer[60] = (total >> 24) as u8;
+    buffer[61] = (total >> 16) as u8;
+    buffer[62] = (total >> 8) as u8;
+    buffer[63] = total as u8;
+
+    compress(state, ctx, &[buffer]);
+}
diff --git a/sha1-checked-0.10.0/src/lib.rs b/sha1-checked-0.10.0/src/lib.rs
new file mode 100644 (file)
index 0000000..1dcf28d
--- /dev/null
@@ -0,0 +1,369 @@
+#![no_std]
+#![doc = include_str!("../README.md")]
+#![doc(
+    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
+    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
+)]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+#![warn(missing_docs, rust_2018_idioms)]
+
+//! Collision checked Sha1.
+//!
+//! General techniques and implementation are based on the research and implementation done in [1], [2] by
+//! Marc Stevens.
+//!
+//!
+//! Original license can be found in [3].
+//!
+//! [1]: https://github.com/cr-marcstevens/sha1collisiondetection
+//! [2]: https://marc-stevens.nl/research/papers/C13-S.pdf
+//! [3]: https://github.com/cr-marcstevens/sha1collisiondetection/blob/master/LICENSE.txt
+
+pub use digest::{self, Digest};
+
+use core::slice::from_ref;
+
+#[cfg(feature = "std")]
+extern crate std;
+
+use digest::{
+    block_buffer::{BlockBuffer, Eager},
+    core_api::BlockSizeUser,
+    typenum::{Unsigned, U20, U64},
+    FixedOutput, FixedOutputReset, HashMarker, Output, OutputSizeUser, Reset, Update,
+};
+#[cfg(feature = "zeroize")]
+use zeroize::{Zeroize, ZeroizeOnDrop};
+
+const BLOCK_SIZE: usize = <sha1::Sha1Core as BlockSizeUser>::BlockSize::USIZE;
+const STATE_LEN: usize = 5;
+const INITIAL_H: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
+
+mod compress;
+mod ubc_check;
+
+/// SHA-1 collision detection hasher state.
+#[derive(Clone)]
+pub struct Sha1 {
+    h: [u32; STATE_LEN],
+    block_len: u64,
+    detection: Option<DetectionState>,
+    buffer: BlockBuffer<U64, Eager>,
+}
+
+impl HashMarker for Sha1 {}
+
+impl Default for Sha1 {
+    fn default() -> Self {
+        Builder::default().build()
+    }
+}
+
+impl Sha1 {
+    /// Create a new Sha1 instance, with collision detection enabled.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Create a new Sha1 builder to configure detection.
+    pub fn builder() -> Builder {
+        Builder::default()
+    }
+
+    /// Oneshot hashing, reporting the collision state.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use hex_literal::hex;
+    /// use sha1_checked::Sha1;
+    ///
+    /// let result = Sha1::try_digest(b"hello world");
+    /// assert_eq!(result.hash().as_ref(), hex!("2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"));
+    /// assert!(!result.has_collision());
+    /// ```
+    pub fn try_digest(data: impl AsRef<[u8]>) -> CollisionResult {
+        let mut hasher = Self::default();
+        Digest::update(&mut hasher, data);
+        hasher.try_finalize()
+    }
+
+    /// Try finalization, reporting the collision state.
+    pub fn try_finalize(mut self) -> CollisionResult {
+        let mut out = Output::<Self>::default();
+        self.finalize_inner(&mut out);
+
+        if let Some(ref ctx) = self.detection {
+            if ctx.found_collision {
+                if ctx.safe_hash {
+                    return CollisionResult::Mitigated(out);
+                }
+                return CollisionResult::Collision(out);
+            }
+        }
+        CollisionResult::Ok(out)
+    }
+
+    fn finalize_inner(&mut self, out: &mut Output<Self>) {
+        let bs = 64;
+        let buffer = &mut self.buffer;
+        let h = &mut self.h;
+
+        if let Some(ref mut ctx) = self.detection {
+            let last_block = buffer.get_data();
+            compress::finalize(h, bs * self.block_len, last_block, ctx);
+        } else {
+            let bit_len = 8 * (buffer.get_pos() as u64 + bs * self.block_len);
+            buffer.len64_padding_be(bit_len, |b| sha1::compress(h, from_ref(b)));
+        }
+
+        for (chunk, v) in out.chunks_exact_mut(4).zip(h.iter()) {
+            chunk.copy_from_slice(&v.to_be_bytes());
+        }
+    }
+}
+
+/// Result when trying to finalize a hash.
+#[derive(Debug)]
+pub enum CollisionResult {
+    /// No collision.
+    Ok(Output<Sha1>),
+    /// Collision occured, but was mititgated.
+    Mitigated(Output<Sha1>),
+    /// Collision occured, the hash is the one that collided.
+    Collision(Output<Sha1>),
+}
+
+impl CollisionResult {
+    /// Returns the output hash.
+    pub fn hash(&self) -> &Output<Sha1> {
+        match self {
+            CollisionResult::Ok(ref s) => s,
+            CollisionResult::Mitigated(ref s) => s,
+            CollisionResult::Collision(ref s) => s,
+        }
+    }
+
+    /// Returns if there was a collision
+    pub fn has_collision(&self) -> bool {
+        !matches!(self, CollisionResult::Ok(_))
+    }
+}
+
+impl core::fmt::Debug for Sha1 {
+    #[inline]
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
+        f.write_str("Sha1CollisionDetection { .. }")
+    }
+}
+
+impl Reset for Sha1 {
+    #[inline]
+    fn reset(&mut self) {
+        self.h = INITIAL_H;
+        self.block_len = 0;
+        self.buffer.reset();
+        if let Some(ref mut ctx) = self.detection {
+            ctx.reset();
+        }
+    }
+}
+
+impl Update for Sha1 {
+    #[inline]
+    fn update(&mut self, input: &[u8]) {
+        let Self {
+            h,
+            detection,
+            buffer,
+            ..
+        } = self;
+        buffer.digest_blocks(input, |blocks| {
+            self.block_len += blocks.len() as u64;
+            if let Some(ref mut ctx) = detection {
+                // SAFETY: GenericArray<u8, U64> and [u8; 64] have
+                // exactly the same memory layout
+                let blocks: &[[u8; BLOCK_SIZE]] =
+                    unsafe { &*(blocks as *const _ as *const [[u8; BLOCK_SIZE]]) };
+                compress::compress(h, ctx, blocks);
+            } else {
+                sha1::compress(h, blocks);
+            }
+        });
+    }
+}
+
+impl OutputSizeUser for Sha1 {
+    type OutputSize = U20;
+}
+
+impl FixedOutput for Sha1 {
+    #[inline]
+    fn finalize_into(mut self, out: &mut Output<Self>) {
+        self.finalize_inner(out);
+    }
+}
+
+impl FixedOutputReset for Sha1 {
+    #[inline]
+    fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
+        self.finalize_inner(out);
+        Reset::reset(self);
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl ZeroizeOnDrop for Sha1 {}
+
+impl Drop for DetectionState {
+    #[inline]
+    fn drop(&mut self) {
+        #[cfg(feature = "zeroize")]
+        {
+            self.ihv1.zeroize();
+            self.ihv2.zeroize();
+            self.m1.zeroize();
+            self.m2.zeroize();
+            self.state_58.zeroize();
+            self.state_65.zeroize();
+        }
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl ZeroizeOnDrop for DetectionState {}
+
+#[cfg(feature = "oid")]
+#[cfg_attr(docsrs, doc(cfg(feature = "oid")))]
+impl digest::const_oid::AssociatedOid for Sha1 {
+    const OID: digest::const_oid::ObjectIdentifier = sha1::Sha1Core::OID;
+}
+
+#[cfg(feature = "std")]
+impl std::io::Write for Sha1 {
+    #[inline]
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        Update::update(self, buf);
+        Ok(buf.len())
+    }
+
+    #[inline]
+    fn flush(&mut self) -> std::io::Result<()> {
+        Ok(())
+    }
+}
+
+/// Builder for collision detection configuration.
+#[derive(Clone)]
+pub struct Builder {
+    detect_collision: bool,
+    safe_hash: bool,
+    ubc_check: bool,
+    reduced_round_collision: bool,
+}
+
+impl Default for Builder {
+    fn default() -> Self {
+        Self {
+            detect_collision: true,
+            safe_hash: true,
+            ubc_check: true,
+            reduced_round_collision: false,
+        }
+    }
+}
+
+impl Builder {
+    /// Should we detect collisions at all? Default: true
+    pub fn detect_collision(mut self, detect: bool) -> Self {
+        self.detect_collision = detect;
+        self
+    }
+
+    /// Should a fix be automatically be applied, or the original hash be returned? Default: true
+    pub fn safe_hash(mut self, safe_hash: bool) -> Self {
+        self.safe_hash = safe_hash;
+        self
+    }
+
+    /// Should unavoidable bitconditions be used to speed up the check? Default: true
+    pub fn use_ubc(mut self, ubc: bool) -> Self {
+        self.ubc_check = ubc;
+        self
+    }
+
+    /// Should reduced round collisions be used? Default: false
+    pub fn reduced_round_collision(mut self, reduced: bool) -> Self {
+        self.reduced_round_collision = reduced;
+        self
+    }
+
+    fn into_detection_state(self) -> Option<DetectionState> {
+        if self.detect_collision {
+            Some(DetectionState {
+                safe_hash: self.safe_hash,
+                reduced_round_collision: self.reduced_round_collision,
+                ubc_check: self.ubc_check,
+                found_collision: false,
+                ihv1: Default::default(),
+                ihv2: Default::default(),
+                m1: [0; 80],
+                m2: [0; 80],
+                state_58: Default::default(),
+                state_65: Default::default(),
+            })
+        } else {
+            None
+        }
+    }
+
+    /// Create a Sha1 with a specific collision detection configuration.
+    pub fn build(self) -> Sha1 {
+        let detection = self.into_detection_state();
+        Sha1 {
+            h: INITIAL_H,
+            block_len: 0,
+            detection,
+            buffer: Default::default(),
+        }
+    }
+}
+
+/// The internal state used to do collision detection.
+#[derive(Clone, Debug)]
+struct DetectionState {
+    safe_hash: bool,
+    ubc_check: bool,
+    reduced_round_collision: bool,
+    /// Has a collision been detected?
+    found_collision: bool,
+    ihv1: [u32; 5],
+    ihv2: [u32; 5],
+    m1: [u32; 80],
+    m2: [u32; 80],
+    /// Stores past states, for faster recompression.
+    state_58: [u32; 5],
+    state_65: [u32; 5],
+}
+
+impl Default for DetectionState {
+    fn default() -> Self {
+        Builder::default()
+            .into_detection_state()
+            .expect("enabled by default")
+    }
+}
+
+impl DetectionState {
+    fn reset(&mut self) {
+        // Do not reset the config, it needs to be preserved
+
+        self.found_collision = false;
+        self.ihv1 = Default::default();
+        self.ihv2 = Default::default();
+        self.m1 = [0; 80];
+        self.m2 = [0; 80];
+        self.state_58 = Default::default();
+        self.state_65 = Default::default();
+    }
+}
diff --git a/sha1-checked-0.10.0/src/ubc_check.rs b/sha1-checked-0.10.0/src/ubc_check.rs
new file mode 100644 (file)
index 0000000..2ce3445
--- /dev/null
@@ -0,0 +1,1175 @@
+//! Direct translation of the C code found at
+//! [ubc_check.c](https://github.com/cr-marcstevens/sha1collisiondetection/blob/master/lib/ubc_check.c).
+//!
+//! For the original license and source details see the comments in `src/checked.rs`.
+//!
+//! The original C code file was generated by the 'parse_bitrel' program in the tools section
+//! using the data files from directory 'tools/data/3565'.
+
+const DV_I_43_0_BIT: u32 = 1 << 0;
+const DV_I_44_0_BIT: u32 = 1 << 1;
+const DV_I_45_0_BIT: u32 = 1 << 2;
+const DV_I_46_0_BIT: u32 = 1 << 3;
+const DV_I_46_2_BIT: u32 = 1 << 4;
+const DV_I_47_0_BIT: u32 = 1 << 5;
+const DV_I_47_2_BIT: u32 = 1 << 6;
+const DV_I_48_0_BIT: u32 = 1 << 7;
+const DV_I_48_2_BIT: u32 = 1 << 8;
+const DV_I_49_0_BIT: u32 = 1 << 9;
+const DV_I_49_2_BIT: u32 = 1 << 10;
+const DV_I_50_0_BIT: u32 = 1 << 11;
+const DV_I_50_2_BIT: u32 = 1 << 12;
+const DV_I_51_0_BIT: u32 = 1 << 13;
+const DV_I_51_2_BIT: u32 = 1 << 14;
+const DV_I_52_0_BIT: u32 = 1 << 15;
+const DV_II_45_0_BIT: u32 = 1 << 16;
+const DV_II_46_0_BIT: u32 = 1 << 17;
+const DV_II_46_2_BIT: u32 = 1 << 18;
+const DV_II_47_0_BIT: u32 = 1 << 19;
+const DV_II_48_0_BIT: u32 = 1 << 20;
+const DV_II_49_0_BIT: u32 = 1 << 21;
+const DV_II_49_2_BIT: u32 = 1 << 22;
+const DV_II_50_0_BIT: u32 = 1 << 23;
+const DV_II_50_2_BIT: u32 = 1 << 24;
+const DV_II_51_0_BIT: u32 = 1 << 25;
+const DV_II_51_2_BIT: u32 = 1 << 26;
+const DV_II_52_0_BIT: u32 = 1 << 27;
+const DV_II_53_0_BIT: u32 = 1 << 28;
+const DV_II_54_0_BIT: u32 = 1 << 29;
+const DV_II_55_0_BIT: u32 = 1 << 30;
+const DV_II_56_0_BIT: u32 = 1 << 31;
+
+pub struct Info {
+    pub dv_type: u32,
+    pub dv_k: u32,
+    pub dv_b: u32,
+    pub testt: Testt,
+    pub maski: i32,
+    pub maskb: i32,
+    pub dm: [u32; 80],
+}
+
+#[derive(Copy, Clone)]
+#[repr(u32)]
+pub enum Testt {
+    T58 = 58,
+    T65 = 65,
+}
+
+/// Contains a list of SHA-1 Disturbance Vectors (DV) to check
+/// `dv_type`, `dv_k` and `dv_b` define the DV: I(K,B) or II(K,B) (see the paper)
+/// `dm[80]` is the expanded message block XOR-difference defined by the DV
+/// testt is the step to do the recompression from for collision detection
+/// `maski` and `maskb` define the bit to check for each DV in the dvmask returned by [`ubc_check`].
+pub const SHA1_DVS: [Info; 32] = [
+    Info {
+        dv_type: 1,
+        dv_k: 43,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 0,
+        dm: [
+            0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010, 0x98000000, 0x60000000, 0x8,
+            0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000,
+            0x8000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010,
+            0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000,
+            0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x10, 0xa0000000, 0x20000000,
+            0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000,
+            0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x49, 0x103, 0x80000009,
+            0x80000012, 0x80000202, 0x18, 0x164, 0x408, 0x800000e6, 0x8000004c, 0x803, 0x80000161,
+            0x80000599,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 44,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 1,
+        dm: [
+            0xb4000008, 0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010, 0x98000000,
+            0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000,
+            0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008,
+            0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010,
+            0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x10,
+            0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10,
+            0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+            0x49, 0x103, 0x80000009, 0x80000012, 0x80000202, 0x18, 0x164, 0x408, 0x800000e6,
+            0x8000004c, 0x803, 0x80000161,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 45,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 2,
+        dm: [
+            0xf4000014, 0xb4000008, 0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010,
+            0x98000000, 0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014,
+            0x28000000, 0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010, 0xf0000010,
+            0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000,
+            0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000,
+            0x10, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10,
+            0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+            0x49, 0x103, 0x80000009, 0x80000012, 0x80000202, 0x18, 0x164, 0x408, 0x800000e6,
+            0x8000004c, 0x803,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 46,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 3,
+        dm: [
+            0x2c000010, 0xf4000014, 0xb4000008, 0x8000000, 0x9800000c, 0xd8000010, 0x8000010,
+            0xb8000010, 0x98000000, 0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010,
+            0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010,
+            0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000,
+            0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000,
+            0x80000000, 0x10, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010,
+            0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004,
+            0x80000080, 0x80000006, 0x49, 0x103, 0x80000009, 0x80000012, 0x80000202, 0x18, 0x164,
+            0x408, 0x800000e6, 0x8000004c,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 46,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 4,
+        dm: [
+            0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040,
+            0xe0000042, 0x60000002, 0x80000001, 0x20, 0x3, 0x40000052, 0x40000040, 0xe0000052,
+            0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043,
+            0x40000022, 0x3, 0x40000042, 0xc0000043, 0xc0000022, 0x1, 0x40000002, 0xc0000043,
+            0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x2, 0x40, 0x80000002,
+            0x80000000, 0x80000002, 0x80000040, 0, 0x80000040, 0x80000000, 0x40, 0x80000000, 0x40,
+            0x80000002, 0, 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4,
+            0x9, 0x101, 0x9, 0x12, 0x202, 0x1a, 0x124, 0x40c, 0x26, 0x4a, 0x80a, 0x60, 0x590,
+            0x1020, 0x39a, 0x132,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 47,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 5,
+        dm: [
+            0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x8000000, 0x9800000c, 0xd8000010,
+            0x8000010, 0xb8000010, 0x98000000, 0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010,
+            0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010,
+            0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000,
+            0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000,
+            0x80000000, 0x10, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010,
+            0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004,
+            0x80000080, 0x80000006, 0x49, 0x103, 0x80000009, 0x80000012, 0x80000202, 0x18, 0x164,
+            0x408, 0x800000e6,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 47,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 6,
+        dm: [
+            0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043,
+            0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x20, 0x3, 0x40000052, 0x40000040,
+            0xe0000052, 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042,
+            0xc0000043, 0x40000022, 0x3, 0x40000042, 0xc0000043, 0xc0000022, 0x1, 0x40000002,
+            0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x2, 0x40,
+            0x80000002, 0x80000000, 0x80000002, 0x80000040, 0, 0x80000040, 0x80000000, 0x40,
+            0x80000000, 0x40, 0x80000002, 0, 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0x4, 0x80, 0x4, 0x9, 0x101, 0x9, 0x12, 0x202, 0x1a, 0x124, 0x40c, 0x26, 0x4a, 0x80a,
+            0x60, 0x590, 0x1020, 0x39a,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 48,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 7,
+        dm: [
+            0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x8000000, 0x9800000c,
+            0xd8000010, 0x8000010, 0xb8000010, 0x98000000, 0x60000000, 0x8, 0xc0000000, 0x90000014,
+            0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x8000018, 0x60000000,
+            0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008,
+            0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010,
+            0x90000000, 0x80000000, 0x10, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0,
+            0x20000010, 0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002,
+            0x80000004, 0x80000080, 0x80000006, 0x49, 0x103, 0x80000009, 0x80000012, 0x80000202,
+            0x18, 0x164, 0x408,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 48,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 8,
+        dm: [
+            0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032,
+            0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x20, 0x3, 0x40000052,
+            0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001,
+            0x40000042, 0xc0000043, 0x40000022, 0x3, 0x40000042, 0xc0000043, 0xc0000022, 0x1,
+            0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002,
+            0x2, 0x40, 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0, 0x80000040, 0x80000000,
+            0x40, 0x80000000, 0x40, 0x80000002, 0, 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0x4, 0x80, 0x4, 0x9, 0x101, 0x9, 0x12, 0x202, 0x1a, 0x124, 0x40c, 0x26, 0x4a,
+            0x80a, 0x60, 0x590, 0x1020,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 49,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 9,
+        dm: [
+            0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x8000000,
+            0x9800000c, 0xd8000010, 0x8000010, 0xb8000010, 0x98000000, 0x60000000, 0x8, 0xc0000000,
+            0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x8000018,
+            0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010,
+            0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010,
+            0x90000010, 0x90000000, 0x80000000, 0x10, 0xa0000000, 0x20000000, 0xa0000000,
+            0x20000010, 0, 0x20000010, 0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000, 0,
+            0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x49, 0x103, 0x80000009,
+            0x80000012, 0x80000202, 0x18, 0x164,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 49,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 10,
+        dm: [
+            0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000,
+            0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x20, 0x3,
+            0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001, 0x20000060,
+            0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x3, 0x40000042, 0xc0000043,
+            0xc0000022, 0x1, 0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042,
+            0x40000042, 0x40000002, 0x2, 0x40, 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0,
+            0x80000040, 0x80000000, 0x40, 0x80000000, 0x40, 0x80000002, 0, 0x80000000, 0x80000000,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x101, 0x9, 0x12, 0x202, 0x1a,
+            0x124, 0x40c, 0x26, 0x4a, 0x80a, 0x60, 0x590,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 50,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 11,
+        dm: [
+            0x800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008,
+            0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010, 0x98000000, 0x60000000, 0x8,
+            0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000,
+            0x8000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010,
+            0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000,
+            0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x10, 0xa0000000, 0x20000000,
+            0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10, 0x20000000, 0x10, 0xa0000000,
+            0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x49, 0x103, 0x80000009,
+            0x80000012, 0x80000202, 0x18,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 50,
+        dv_b: 2,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 12,
+        dm: [
+            0x20000030, 0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022,
+            0x20000000, 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001,
+            0x20, 0x3, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001,
+            0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x3, 0x40000042,
+            0xc0000043, 0xc0000022, 0x1, 0x40000002, 0xc0000043, 0x40000062, 0x80000001,
+            0x40000042, 0x40000042, 0x40000002, 0x2, 0x40, 0x80000002, 0x80000000, 0x80000002,
+            0x80000040, 0, 0x80000040, 0x80000000, 0x40, 0x80000000, 0x40, 0x80000002, 0,
+            0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x101, 0x9,
+            0x12, 0x202, 0x1a, 0x124, 0x40c, 0x26, 0x4a, 0x80a, 0x60,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 51,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 13,
+        dm: [
+            0xe8000000, 0x800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014,
+            0xb4000008, 0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010, 0x98000000,
+            0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000,
+            0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008,
+            0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010,
+            0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x10,
+            0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10,
+            0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+            0x49, 0x103, 0x80000009, 0x80000012, 0x80000202,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 51,
+        dv_b: 2,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 14,
+        dm: [
+            0xa0000003, 0x20000030, 0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053,
+            0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002,
+            0x80000001, 0x20, 0x3, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040,
+            0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x3,
+            0x40000042, 0xc0000043, 0xc0000022, 0x1, 0x40000002, 0xc0000043, 0x40000062,
+            0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x2, 0x40, 0x80000002, 0x80000000,
+            0x80000002, 0x80000040, 0, 0x80000040, 0x80000000, 0x40, 0x80000000, 0x40, 0x80000002,
+            0, 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x101,
+            0x9, 0x12, 0x202, 0x1a, 0x124, 0x40c, 0x26, 0x4a, 0x80a,
+        ],
+    },
+    Info {
+        dv_type: 1,
+        dv_k: 52,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 15,
+        dm: [
+            0x4000010, 0xe8000000, 0x800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010,
+            0xf4000014, 0xb4000008, 0x8000000, 0x9800000c, 0xd8000010, 0x8000010, 0xb8000010,
+            0x98000000, 0x60000000, 0x8, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014,
+            0x28000000, 0x20000010, 0x48000000, 0x8000018, 0x60000000, 0x90000010, 0xf0000010,
+            0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000,
+            0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000,
+            0x10, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0, 0x20000010, 0x20000000, 0x10,
+            0x20000000, 0x10, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0x1, 0x20, 0x1, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+            0x49, 0x103, 0x80000009, 0x80000012,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 45,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 16,
+        dm: [
+            0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0xc,
+            0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010, 0xb800001c, 0xe8000000,
+            0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010,
+            0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000, 0x10,
+            0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+            0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+            0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0,
+            0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+            0xc0000046, 0x4000004b, 0x80000107, 0x89, 0x14, 0x8000024b, 0x11b, 0x8000016d,
+            0x8000041a, 0x2e4, 0x80000054, 0x967,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 46,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 17,
+        dm: [
+            0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018,
+            0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010, 0xb800001c,
+            0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010,
+            0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000,
+            0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+            0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+            0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0,
+            0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+            0xc0000046, 0x4000004b, 0x80000107, 0x89, 0x14, 0x8000024b, 0x11b, 0x8000016d,
+            0x8000041a, 0x2e4, 0x80000054,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 46,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 18,
+        dm: [
+            0x90000070, 0xb0000053, 0x30000008, 0x43, 0xd0000072, 0xb0000010, 0xf0000062,
+            0xc0000042, 0x30, 0xe0000042, 0x20000060, 0xe0000041, 0x20000050, 0xc0000041,
+            0xe0000072, 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, 0xc0000002,
+            0xe0000042, 0x60000042, 0x80000002, 0, 0, 0x80000000, 0x2, 0x40, 0, 0x80000040,
+            0x80000000, 0x40, 0x80000001, 0x60, 0x80000003, 0x40000002, 0xc0000040, 0xc0000002,
+            0x80000000, 0x80000000, 0x80000002, 0x40, 0x2, 0x80000000, 0x80000000, 0x80000000, 0x2,
+            0x40, 0, 0x80000040, 0x80000002, 0, 0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0x4,
+            0x80, 0x4, 0x9, 0x105, 0x89, 0x16, 0x20b, 0x11b, 0x12d, 0x41e, 0x224, 0x50, 0x92e,
+            0x46c, 0x5b6, 0x106a, 0xb90, 0x152,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 47,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 19,
+        dm: [
+            0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004,
+            0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010,
+            0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+            0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000,
+            0x20000000, 0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000,
+            0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0,
+            0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005,
+            0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x89, 0x14, 0x8000024b, 0x11b,
+            0x8000016d, 0x8000041a, 0x2e4,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 48,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 20,
+        dm: [
+            0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c,
+            0x2c000004, 0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014,
+            0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000,
+            0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0,
+            0x20000010, 0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010,
+            0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000,
+            0x20000000, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000,
+            0x20000000, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022,
+            0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x89, 0x14, 0x8000024b,
+            0x11b, 0x8000016d, 0x8000041a,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 49,
+        dv_b: 0,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 21,
+        dm: [
+            0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010,
+            0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010,
+            0x8000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c,
+            0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000,
+            0x80000000, 0x10, 0, 0x20000010, 0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000,
+            0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x10,
+            0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107,
+            0x89, 0x14, 0x8000024b, 0x11b, 0x8000016d,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 49,
+        dv_b: 2,
+        testt: Testt::T58,
+        maski: 0,
+        maskb: 22,
+        dm: [
+            0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, 0x30000008, 0x43,
+            0xd0000072, 0xb0000010, 0xf0000062, 0xc0000042, 0x30, 0xe0000042, 0x20000060,
+            0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, 0x60000041,
+            0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0, 0,
+            0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000000, 0x40, 0x80000001, 0x60, 0x80000003,
+            0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, 0x40, 0x2,
+            0x80000000, 0x80000000, 0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000002, 0,
+            0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x105, 0x89, 0x16,
+            0x20b, 0x11b, 0x12d, 0x41e, 0x224, 0x50, 0x92e, 0x46c, 0x5b6,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 50,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 23,
+        dm: [
+            0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0xc000002,
+            0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018,
+            0x78000010, 0x8000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010,
+            0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0, 0,
+            0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000, 0x10, 0x60000000, 0x18,
+            0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000,
+            0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107,
+            0x89, 0x14, 0x8000024b, 0x11b,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 50,
+        dv_b: 2,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 24,
+        dm: [
+            0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053, 0x30000008,
+            0x43, 0xd0000072, 0xb0000010, 0xf0000062, 0xc0000042, 0x30, 0xe0000042, 0x20000060,
+            0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012, 0x60000041,
+            0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0, 0,
+            0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000000, 0x40, 0x80000001, 0x60, 0x80000003,
+            0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002, 0x40, 0x2,
+            0x80000000, 0x80000000, 0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000002, 0,
+            0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x105, 0x89, 0x16,
+            0x20b, 0x11b, 0x12d, 0x41e, 0x224, 0x50, 0x92e, 0x46c,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 51,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 25,
+        dm: [
+            0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014,
+            0xc000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0xc, 0xb8000010,
+            0x8000018, 0x78000010, 0x8000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004,
+            0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0,
+            0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000, 0x10, 0x60000000, 0x18,
+            0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000,
+            0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002,
+            0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107,
+            0x89, 0x14, 0x8000024b,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 51,
+        dv_b: 2,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 26,
+        dm: [
+            0x43, 0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053,
+            0x30000008, 0x43, 0xd0000072, 0xb0000010, 0xf0000062, 0xc0000042, 0x30, 0xe0000042,
+            0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012,
+            0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0,
+            0, 0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000000, 0x40, 0x80000001, 0x60,
+            0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002,
+            0x40, 0x2, 0x80000000, 0x80000000, 0x80000000, 0x2, 0x40, 0, 0x80000040, 0x80000002, 0,
+            0x80000000, 0x80000000, 0, 0, 0, 0, 0, 0, 0x4, 0x80, 0x4, 0x9, 0x105, 0x89, 0x16,
+            0x20b, 0x11b, 0x12d, 0x41e, 0x224, 0x50, 0x92e,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 52,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 27,
+        dm: [
+            0xc000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c,
+            0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0xc,
+            0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010, 0xb800001c, 0xe8000000,
+            0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010,
+            0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000, 0x10,
+            0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+            0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+            0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0,
+            0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+            0xc0000046, 0x4000004b, 0x80000107, 0x89, 0x14,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 53,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 28,
+        dm: [
+            0xcc000014, 0xc000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010,
+            0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018,
+            0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010, 0xb800001c,
+            0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010,
+            0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0x20000000,
+            0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+            0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+            0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0, 0, 0, 0, 0,
+            0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+            0xc0000046, 0x4000004b, 0x80000107, 0x89,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 54,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 29,
+        dm: [
+            0x400001c, 0xcc000014, 0xc000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a,
+            0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004,
+            0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010,
+            0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+            0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000,
+            0x20000000, 0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000,
+            0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0,
+            0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005,
+            0xc0000082, 0xc0000046, 0x4000004b, 0x80000107,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 55,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 30,
+        dm: [
+            0x10, 0x400001c, 0xcc000014, 0xc000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a,
+            0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c, 0x2c000004,
+            0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014, 0x70000010,
+            0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+            0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0, 0x20000010,
+            0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000,
+            0x20000000, 0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000, 0x20000000,
+            0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000, 0x20000000, 0,
+            0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022, 0x80000005,
+            0xc0000082, 0xc0000046, 0x4000004b,
+        ],
+    },
+    Info {
+        dv_type: 2,
+        dv_k: 56,
+        dv_b: 0,
+        testt: Testt::T65,
+        maski: 0,
+        maskb: 31,
+        dm: [
+            0x2600001a, 0x10, 0x400001c, 0xcc000014, 0xc000002, 0xc0000010, 0xb400001c, 0x3c000004,
+            0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0xc000002, 0xc0000010, 0xb400001c,
+            0x2c000004, 0xbc000018, 0xb0000010, 0xc, 0xb8000010, 0x8000018, 0x78000010, 0x8000014,
+            0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000,
+            0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0, 0, 0x20000000, 0x80000000, 0x10, 0,
+            0x20000010, 0x20000000, 0x10, 0x60000000, 0x18, 0xe0000000, 0x90000000, 0x30000010,
+            0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x10, 0x80000000, 0x20000000,
+            0x20000000, 0x20000000, 0x80000000, 0x10, 0, 0x20000010, 0xa0000000, 0, 0x20000000,
+            0x20000000, 0, 0, 0, 0, 0, 0, 0x1, 0x20, 0x1, 0x40000002, 0x40000041, 0x40000022,
+            0x80000005, 0xc0000082, 0xc0000046,
+        ],
+    },
+];
+
+/// Takes as input an expanded message block and verifies the unavoidable bitconditions
+/// for all listed DVs it returns a dvmask where each bit belonging to a DV is set if
+/// all unavoidable bitconditions for that DV have been met thus one needs to do the
+/// recompression check for each DV that has its bit set.
+#[inline]
+pub const fn ubc_check(w: &[u32; 80]) -> u32 {
+    let mut mask: u32 = !0;
+    mask &= ((w[44] ^ w[45]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_48_0_BIT
+            | DV_I_51_0_BIT
+            | DV_I_52_0_BIT
+            | DV_II_45_0_BIT
+            | DV_II_46_0_BIT
+            | DV_II_50_0_BIT
+            | DV_II_51_0_BIT);
+    mask &= ((w[49] ^ w[50]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_46_0_BIT
+            | DV_II_45_0_BIT
+            | DV_II_50_0_BIT
+            | DV_II_51_0_BIT
+            | DV_II_55_0_BIT
+            | DV_II_56_0_BIT);
+    mask &= ((w[48] ^ w[49]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_45_0_BIT
+            | DV_I_52_0_BIT
+            | DV_II_49_0_BIT
+            | DV_II_50_0_BIT
+            | DV_II_54_0_BIT
+            | DV_II_55_0_BIT);
+    mask &= ((w[47] ^ w[50] >> 25) & (1 << 4)).wrapping_sub((1) << 4)
+        | !(DV_I_47_0_BIT
+            | DV_I_49_0_BIT
+            | DV_I_51_0_BIT
+            | DV_II_45_0_BIT
+            | DV_II_51_0_BIT
+            | DV_II_56_0_BIT);
+    mask &= ((w[47] ^ w[48]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_44_0_BIT
+            | DV_I_51_0_BIT
+            | DV_II_48_0_BIT
+            | DV_II_49_0_BIT
+            | DV_II_53_0_BIT
+            | DV_II_54_0_BIT);
+    mask &= ((w[46] >> 4 ^ w[49] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_46_0_BIT
+            | DV_I_48_0_BIT
+            | DV_I_50_0_BIT
+            | DV_I_52_0_BIT
+            | DV_II_50_0_BIT
+            | DV_II_55_0_BIT);
+    mask &= ((w[46] ^ w[47]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_43_0_BIT
+            | DV_I_50_0_BIT
+            | DV_II_47_0_BIT
+            | DV_II_48_0_BIT
+            | DV_II_52_0_BIT
+            | DV_II_53_0_BIT);
+    mask &= ((w[45] >> 4 ^ w[48] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_45_0_BIT
+            | DV_I_47_0_BIT
+            | DV_I_49_0_BIT
+            | DV_I_51_0_BIT
+            | DV_II_49_0_BIT
+            | DV_II_54_0_BIT);
+    mask &= ((w[45] ^ w[46]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_49_0_BIT
+            | DV_I_52_0_BIT
+            | DV_II_46_0_BIT
+            | DV_II_47_0_BIT
+            | DV_II_51_0_BIT
+            | DV_II_52_0_BIT);
+    mask &= ((w[44] >> 4 ^ w[47] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_44_0_BIT
+            | DV_I_46_0_BIT
+            | DV_I_48_0_BIT
+            | DV_I_50_0_BIT
+            | DV_II_48_0_BIT
+            | DV_II_53_0_BIT);
+    mask &= ((w[43] >> 4 ^ w[46] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_43_0_BIT
+            | DV_I_45_0_BIT
+            | DV_I_47_0_BIT
+            | DV_I_49_0_BIT
+            | DV_II_47_0_BIT
+            | DV_II_52_0_BIT);
+    mask &= ((w[43] ^ w[44]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_47_0_BIT
+            | DV_I_50_0_BIT
+            | DV_I_51_0_BIT
+            | DV_II_45_0_BIT
+            | DV_II_49_0_BIT
+            | DV_II_50_0_BIT);
+    mask &= ((w[42] >> 4 ^ w[45] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_44_0_BIT
+            | DV_I_46_0_BIT
+            | DV_I_48_0_BIT
+            | DV_I_52_0_BIT
+            | DV_II_46_0_BIT
+            | DV_II_51_0_BIT);
+    mask &= ((w[41] >> 4 ^ w[44] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_43_0_BIT
+            | DV_I_45_0_BIT
+            | DV_I_47_0_BIT
+            | DV_I_51_0_BIT
+            | DV_II_45_0_BIT
+            | DV_II_50_0_BIT);
+    mask &= ((w[40] ^ w[41]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_44_0_BIT
+            | DV_I_47_0_BIT
+            | DV_I_48_0_BIT
+            | DV_II_46_0_BIT
+            | DV_II_47_0_BIT
+            | DV_II_56_0_BIT);
+    mask &= ((w[54] ^ w[55]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_51_0_BIT | DV_II_47_0_BIT | DV_II_50_0_BIT | DV_II_55_0_BIT | DV_II_56_0_BIT);
+    mask &= ((w[53] ^ w[54]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_50_0_BIT | DV_II_46_0_BIT | DV_II_49_0_BIT | DV_II_54_0_BIT | DV_II_55_0_BIT);
+    mask &= ((w[52] ^ w[53]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_49_0_BIT | DV_II_45_0_BIT | DV_II_48_0_BIT | DV_II_53_0_BIT | DV_II_54_0_BIT);
+    mask &= ((w[50] ^ w[53] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+        | !(DV_I_50_0_BIT | DV_I_52_0_BIT | DV_II_46_0_BIT | DV_II_48_0_BIT | DV_II_54_0_BIT);
+    mask &= ((w[50] ^ w[51]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_47_0_BIT | DV_II_46_0_BIT | DV_II_51_0_BIT | DV_II_52_0_BIT | DV_II_56_0_BIT);
+    mask &= ((w[49] ^ w[52] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+        | !(DV_I_49_0_BIT | DV_I_51_0_BIT | DV_II_45_0_BIT | DV_II_47_0_BIT | DV_II_53_0_BIT);
+    mask &= ((w[48] ^ w[51] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+        | !(DV_I_48_0_BIT | DV_I_50_0_BIT | DV_I_52_0_BIT | DV_II_46_0_BIT | DV_II_52_0_BIT);
+    mask &= ((w[42] ^ w[43]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_46_0_BIT | DV_I_49_0_BIT | DV_I_50_0_BIT | DV_II_48_0_BIT | DV_II_49_0_BIT);
+    mask &= ((w[41] ^ w[42]) >> 29 & 1).wrapping_sub(1)
+        | !(DV_I_45_0_BIT | DV_I_48_0_BIT | DV_I_49_0_BIT | DV_II_47_0_BIT | DV_II_48_0_BIT);
+    mask &= ((w[40] >> 4 ^ w[43] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_44_0_BIT | DV_I_46_0_BIT | DV_I_50_0_BIT | DV_II_49_0_BIT | DV_II_56_0_BIT);
+    mask &= ((w[39] >> 4 ^ w[42] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_43_0_BIT | DV_I_45_0_BIT | DV_I_49_0_BIT | DV_II_48_0_BIT | DV_II_55_0_BIT);
+    if mask & (DV_I_44_0_BIT | DV_I_48_0_BIT | DV_II_47_0_BIT | DV_II_54_0_BIT | DV_II_56_0_BIT)
+        != 0
+    {
+        mask &= ((w[38] >> 4 ^ w[41] >> 29) & 1).wrapping_sub(1)
+            | !(DV_I_44_0_BIT | DV_I_48_0_BIT | DV_II_47_0_BIT | DV_II_54_0_BIT | DV_II_56_0_BIT)
+    }
+    mask &= ((w[37] >> 4 ^ w[40] >> 29) & 1).wrapping_sub(1)
+        | !(DV_I_43_0_BIT | DV_I_47_0_BIT | DV_II_46_0_BIT | DV_II_53_0_BIT | DV_II_55_0_BIT);
+    if mask & (DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_51_0_BIT | DV_II_56_0_BIT) != 0 {
+        mask &= ((w[55] ^ w[56]) >> 29 & 1).wrapping_sub(1)
+            | !(DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_51_0_BIT | DV_II_56_0_BIT)
+    }
+    if mask & (DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_50_0_BIT | DV_II_56_0_BIT) != 0 {
+        mask &= ((w[52] ^ w[55] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_50_0_BIT | DV_II_56_0_BIT)
+    }
+    if mask & (DV_I_51_0_BIT | DV_II_47_0_BIT | DV_II_49_0_BIT | DV_II_55_0_BIT) != 0 {
+        mask &= ((w[51] ^ w[54] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_I_51_0_BIT | DV_II_47_0_BIT | DV_II_49_0_BIT | DV_II_55_0_BIT)
+    }
+    if mask & (DV_I_48_0_BIT | DV_II_47_0_BIT | DV_II_52_0_BIT | DV_II_53_0_BIT) != 0 {
+        mask &= ((w[51] ^ w[52]) >> 29 & 1).wrapping_sub(1)
+            | !(DV_I_48_0_BIT | DV_II_47_0_BIT | DV_II_52_0_BIT | DV_II_53_0_BIT)
+    }
+    if mask & (DV_I_46_0_BIT | DV_I_49_0_BIT | DV_II_45_0_BIT | DV_II_48_0_BIT) != 0 {
+        mask &= ((w[36] >> 4 ^ w[40] >> 29) & 1).wrapping_sub(1)
+            | !(DV_I_46_0_BIT | DV_I_49_0_BIT | DV_II_45_0_BIT | DV_II_48_0_BIT)
+    }
+    if mask & (DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_49_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[53] ^ w[56]) >> 29 & 1)
+            | !(DV_I_52_0_BIT | DV_II_48_0_BIT | DV_II_49_0_BIT)
+    }
+    if mask & (DV_I_50_0_BIT | DV_II_46_0_BIT | DV_II_47_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[51] ^ w[54]) >> 29 & 1)
+            | !(DV_I_50_0_BIT | DV_II_46_0_BIT | DV_II_47_0_BIT)
+    }
+    if mask & (DV_I_49_0_BIT | DV_I_51_0_BIT | DV_II_45_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[50] ^ w[52]) >> 29 & 1)
+            | !(DV_I_49_0_BIT | DV_I_51_0_BIT | DV_II_45_0_BIT)
+    }
+    if mask & (DV_I_48_0_BIT | DV_I_50_0_BIT | DV_I_52_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[49] ^ w[51]) >> 29 & 1)
+            | !(DV_I_48_0_BIT | DV_I_50_0_BIT | DV_I_52_0_BIT)
+    }
+    if mask & (DV_I_47_0_BIT | DV_I_49_0_BIT | DV_I_51_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[48] ^ w[50]) >> 29 & 1)
+            | !(DV_I_47_0_BIT | DV_I_49_0_BIT | DV_I_51_0_BIT)
+    }
+    if mask & (DV_I_46_0_BIT | DV_I_48_0_BIT | DV_I_50_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[47] ^ w[49]) >> 29 & 1)
+            | !(DV_I_46_0_BIT | DV_I_48_0_BIT | DV_I_50_0_BIT)
+    }
+    if mask & (DV_I_45_0_BIT | DV_I_47_0_BIT | DV_I_49_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[46] ^ w[48]) >> 29 & 1)
+            | !(DV_I_45_0_BIT | DV_I_47_0_BIT | DV_I_49_0_BIT)
+    }
+    mask &= ((w[45] ^ w[47]) & (1 << 6)).wrapping_sub(1 << 6)
+        | !(DV_I_47_2_BIT | DV_I_49_2_BIT | DV_I_51_2_BIT);
+    if mask & (DV_I_44_0_BIT | DV_I_46_0_BIT | DV_I_48_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[45] ^ w[47]) >> 29 & 1)
+            | !(DV_I_44_0_BIT | DV_I_46_0_BIT | DV_I_48_0_BIT)
+    }
+    mask &= ((w[44] ^ w[46]) >> 6 & 1).wrapping_sub(1)
+        | !(DV_I_46_2_BIT | DV_I_48_2_BIT | DV_I_50_2_BIT);
+    if mask & (DV_I_43_0_BIT | DV_I_45_0_BIT | DV_I_47_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[44] ^ w[46]) >> 29 & 1)
+            | !(DV_I_43_0_BIT | DV_I_45_0_BIT | DV_I_47_0_BIT)
+    }
+    mask &= (0u32).wrapping_sub((w[41] ^ w[42] >> 5) & (1 << 1))
+        | !(DV_I_48_2_BIT | DV_II_46_2_BIT | DV_II_51_2_BIT);
+    mask &= (0u32).wrapping_sub((w[40] ^ w[41] >> 5) & (1 << 1))
+        | !(DV_I_47_2_BIT | DV_I_51_2_BIT | DV_II_50_2_BIT);
+    if mask & (DV_I_44_0_BIT | DV_I_46_0_BIT | DV_II_56_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[40] ^ w[42]) >> 4 & 1)
+            | !(DV_I_44_0_BIT | DV_I_46_0_BIT | DV_II_56_0_BIT)
+    }
+    mask &= (0u32).wrapping_sub((w[39] ^ w[40] >> 5) & (1 << 1))
+        | !(DV_I_46_2_BIT | DV_I_50_2_BIT | DV_II_49_2_BIT);
+    if mask & (DV_I_43_0_BIT | DV_I_45_0_BIT | DV_II_55_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[39] ^ w[41]) >> 4 & 1)
+            | !(DV_I_43_0_BIT | DV_I_45_0_BIT | DV_II_55_0_BIT)
+    }
+    if mask & (DV_I_44_0_BIT | DV_II_54_0_BIT | DV_II_56_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[38] ^ w[40]) >> 4 & 1)
+            | !(DV_I_44_0_BIT | DV_II_54_0_BIT | DV_II_56_0_BIT)
+    }
+    if mask & (DV_I_43_0_BIT | DV_II_53_0_BIT | DV_II_55_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[37] ^ w[39]) >> 4 & 1)
+            | !(DV_I_43_0_BIT | DV_II_53_0_BIT | DV_II_55_0_BIT)
+    }
+    mask &= (0u32).wrapping_sub((w[36] ^ w[37] >> 5) & (1 << 1))
+        | !(DV_I_47_2_BIT | DV_I_50_2_BIT | DV_II_46_2_BIT);
+    if mask & (DV_I_45_0_BIT | DV_I_48_0_BIT | DV_II_47_0_BIT) != 0 {
+        mask &= ((w[35] >> 4 ^ w[39] >> 29) & 1).wrapping_sub(1)
+            | !(DV_I_45_0_BIT | DV_I_48_0_BIT | DV_II_47_0_BIT)
+    }
+    if mask & (DV_I_48_0_BIT | DV_II_48_0_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[63] ^ w[64] >> 5) & (1 << 0)) | !(DV_I_48_0_BIT | DV_II_48_0_BIT)
+    }
+    if mask & (DV_I_45_0_BIT | DV_II_45_0_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[63] ^ w[64] >> 5) & (1 << 1)) | !(DV_I_45_0_BIT | DV_II_45_0_BIT)
+    }
+    if mask & (DV_I_47_0_BIT | DV_II_47_0_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[62] ^ w[63] >> 5) & (1 << 0)) | !(DV_I_47_0_BIT | DV_II_47_0_BIT)
+    }
+    if mask & (DV_I_46_0_BIT | DV_II_46_0_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[61] ^ w[62] >> 5) & (1 << 0)) | !(DV_I_46_0_BIT | DV_II_46_0_BIT)
+    }
+    mask &=
+        (0u32).wrapping_sub((w[61] ^ w[62] >> 5) & (1 << 2)) | !(DV_I_46_2_BIT | DV_II_46_2_BIT);
+    if mask & (DV_I_45_0_BIT | DV_II_45_0_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[60] ^ w[61] >> 5) & (1 << 0)) | !(DV_I_45_0_BIT | DV_II_45_0_BIT)
+    }
+    if mask & (DV_II_51_0_BIT | DV_II_54_0_BIT) != 0 {
+        mask &= ((w[58] ^ w[59]) >> 29 & 1).wrapping_sub(1) | !(DV_II_51_0_BIT | DV_II_54_0_BIT)
+    }
+    if mask & (DV_II_50_0_BIT | DV_II_53_0_BIT) != 0 {
+        mask &= ((w[57] ^ w[58]) >> 29 & 1).wrapping_sub(1) | !(DV_II_50_0_BIT | DV_II_53_0_BIT)
+    }
+    if mask & (DV_II_52_0_BIT | DV_II_54_0_BIT) != 0 {
+        mask &= ((w[56] ^ w[59] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_II_52_0_BIT | DV_II_54_0_BIT)
+    }
+    if mask & (DV_II_51_0_BIT | DV_II_52_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[56] ^ w[59]) >> 29 & 1) | !(DV_II_51_0_BIT | DV_II_52_0_BIT)
+    }
+    if mask & (DV_II_49_0_BIT | DV_II_52_0_BIT) != 0 {
+        mask &= ((w[56] ^ w[57]) >> 29 & 1).wrapping_sub(1) | !(DV_II_49_0_BIT | DV_II_52_0_BIT)
+    }
+    if mask & (DV_II_51_0_BIT | DV_II_53_0_BIT) != 0 {
+        mask &= ((w[55] ^ w[58] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_II_51_0_BIT | DV_II_53_0_BIT)
+    }
+    if mask & (DV_II_50_0_BIT | DV_II_52_0_BIT) != 0 {
+        mask &= ((w[54] ^ w[57] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_II_50_0_BIT | DV_II_52_0_BIT)
+    }
+    if mask & (DV_II_49_0_BIT | DV_II_51_0_BIT) != 0 {
+        mask &= ((w[53] ^ w[56] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_II_49_0_BIT | DV_II_51_0_BIT)
+    }
+    mask &=
+        ((w[51] ^ w[50] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_I_50_2_BIT | DV_II_46_2_BIT);
+    mask &= ((w[48] ^ w[50]) & (1 << 6)).wrapping_sub(1 << 6) | !(DV_I_50_2_BIT | DV_II_46_2_BIT);
+    if mask & (DV_I_51_0_BIT | DV_I_52_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[48] ^ w[55]) >> 29 & 1) | !(DV_I_51_0_BIT | DV_I_52_0_BIT)
+    }
+    mask &= ((w[47] ^ w[49]) & (1 << 6)).wrapping_sub(1 << 6) | !(DV_I_49_2_BIT | DV_I_51_2_BIT);
+    mask &=
+        ((w[48] ^ w[47] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_I_47_2_BIT | DV_II_51_2_BIT);
+    mask &= ((w[46] ^ w[48]) & (1 << 6)).wrapping_sub(1 << 6) | !(DV_I_48_2_BIT | DV_I_50_2_BIT);
+    mask &=
+        ((w[47] ^ w[46] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_I_46_2_BIT | DV_II_50_2_BIT);
+    mask &=
+        (0u32).wrapping_sub((w[44] ^ w[45] >> 5) & (1 << 1)) | !(DV_I_51_2_BIT | DV_II_49_2_BIT);
+    mask &= ((w[43] ^ w[45]) & (1 << 6)).wrapping_sub(1 << 6) | !(DV_I_47_2_BIT | DV_I_49_2_BIT);
+    mask &= ((w[42] ^ w[44]) >> 6 & 1).wrapping_sub(1) | !(DV_I_46_2_BIT | DV_I_48_2_BIT);
+    mask &=
+        ((w[43] ^ w[42] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_II_46_2_BIT | DV_II_51_2_BIT);
+    mask &=
+        ((w[42] ^ w[41] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_I_51_2_BIT | DV_II_50_2_BIT);
+    mask &=
+        ((w[41] ^ w[40] >> 5) & (1 << 1)).wrapping_sub(1 << 1) | !(DV_I_50_2_BIT | DV_II_49_2_BIT);
+    if mask & (DV_I_52_0_BIT | DV_II_51_0_BIT) != 0 {
+        mask &= ((w[39] ^ w[43] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_I_52_0_BIT | DV_II_51_0_BIT)
+    }
+    if mask & (DV_I_51_0_BIT | DV_II_50_0_BIT) != 0 {
+        mask &= ((w[38] ^ w[42] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_I_51_0_BIT | DV_II_50_0_BIT)
+    }
+    if mask & (DV_I_48_2_BIT | DV_I_51_2_BIT) != 0 {
+        mask &=
+            (0u32).wrapping_sub((w[37] ^ w[38] >> 5) & (1 << 1)) | !(DV_I_48_2_BIT | DV_I_51_2_BIT)
+    }
+    if mask & (DV_I_50_0_BIT | DV_II_49_0_BIT) != 0 {
+        mask &= ((w[37] ^ w[41] >> 25) & (1 << 4)).wrapping_sub(1 << 4)
+            | !(DV_I_50_0_BIT | DV_II_49_0_BIT)
+    }
+    if mask & (DV_II_52_0_BIT | DV_II_54_0_BIT) != 0 {
+        mask &= (0u32).wrapping_sub((w[36] ^ w[38]) & (1 << 4)) | !(DV_II_52_0_BIT | DV_II_54_0_BIT)
+    }
+    mask &= (0u32).wrapping_sub((w[35] ^ w[36] >> 5) & (1 << 1)) | !(DV_I_46_2_BIT | DV_I_49_2_BIT);
+    if mask & (DV_I_51_0_BIT | DV_II_47_0_BIT) != 0 {
+        mask &= ((w[35] ^ w[39] >> 25) & (1 << 3)).wrapping_sub(1 << 3)
+            | !(DV_I_51_0_BIT | DV_II_47_0_BIT)
+    }
+    if mask != 0 {
+        if mask & DV_I_43_0_BIT != 0
+            && ((w[61] ^ w[62] >> 5) & (1 << 1) == 0
+                || (w[59] ^ w[63] >> 25) & (1 << 5) != 0
+                || (w[58] ^ w[63] >> 30) & (1 << 0) == 0)
+        {
+            mask &= !DV_I_43_0_BIT
+        }
+        if mask & DV_I_44_0_BIT != 0
+            && ((w[62] ^ w[63] >> 5) & (1 << 1) == 0
+                || (w[60] ^ w[64] >> 25) & (1 << 5) != 0
+                || (w[59] ^ w[64] >> 30) & (1 << 0) == 0)
+        {
+            mask &= !DV_I_44_0_BIT
+        }
+        if mask & DV_I_46_2_BIT != 0 {
+            mask &= !((w[40] ^ w[42]) >> 2) | !DV_I_46_2_BIT
+        }
+        if mask & DV_I_47_2_BIT != 0
+            && ((w[62] ^ w[63] >> 5) & (1 << 2) == 0 || (w[41] ^ w[43]) & (1 << 6) != 0)
+        {
+            mask &= !DV_I_47_2_BIT
+        }
+        if mask & DV_I_48_2_BIT != 0
+            && ((w[63] ^ w[64] >> 5) & (1 << 2) == 0 || (w[48] ^ w[49] << 5) & (1 << 6) != 0)
+        {
+            mask &= !DV_I_48_2_BIT
+        }
+        if mask & DV_I_49_2_BIT != 0
+            && ((w[49] ^ w[50] << 5) & (1 << 6) != 0
+                || (w[42] ^ w[50]) & (1 << 1) == 0
+                || (w[39] ^ w[40] << 5) & (1 << 6) != 0
+                || (w[38] ^ w[40]) & (1 << 1) == 0)
+        {
+            mask &= !DV_I_49_2_BIT
+        }
+        if mask & DV_I_50_0_BIT != 0 {
+            mask &= (w[36] ^ w[37]) << 7 | !DV_I_50_0_BIT
+        }
+        if mask & DV_I_50_2_BIT != 0 {
+            mask &= (w[43] ^ w[51]) << 11 | !DV_I_50_2_BIT
+        }
+        if mask & DV_I_51_0_BIT != 0 {
+            mask &= (w[37] ^ w[38]) << 9 | !DV_I_51_0_BIT
+        }
+        if mask & DV_I_51_2_BIT != 0
+            && ((w[51] ^ w[52] << 5) & (1 << 6) != 0
+                || (w[49] ^ w[51]) & (1 << 6) != 0
+                || (w[37] ^ w[37] >> 5) & (1 << 1) != 0
+                || (w[35] ^ w[39] >> 25) & (1 << 5) != 0)
+        {
+            mask &= !DV_I_51_2_BIT
+        }
+        if mask & DV_I_52_0_BIT != 0 {
+            mask &= (w[38] ^ w[39]) << 11 | !DV_I_52_0_BIT
+        }
+        if mask & DV_II_46_2_BIT != 0 {
+            mask &= (w[47] ^ w[51]) << 17 | !DV_II_46_2_BIT
+        }
+        if mask & DV_II_48_0_BIT != 0
+            && ((w[36] ^ w[40] >> 25) & (1 << 3) != 0 || (w[35] ^ w[40] << 2) & (1 << 30) == 0)
+        {
+            mask &= !DV_II_48_0_BIT
+        }
+        if mask & DV_II_49_0_BIT != 0
+            && ((w[37] ^ w[41] >> 25) & (1 << 3) != 0 || (w[36] ^ w[41] << 2) & (1 << 30) == 0)
+        {
+            mask &= !DV_II_49_0_BIT
+        }
+        if mask & DV_II_49_2_BIT != 0
+            && ((w[53] ^ w[54] << 5) & (1 << 6) != 0
+                || (w[51] ^ w[53]) & (1 << 6) != 0
+                || (w[50] ^ w[54]) & (1 << 1) == 0
+                || (w[45] ^ w[46] << 5) & (1 << 6) != 0
+                || (w[37] ^ w[41] >> 25) & (1 << 5) != 0
+                || (w[36] ^ w[41] >> 30) & (1 << 0) == 0)
+        {
+            mask &= !DV_II_49_2_BIT
+        }
+        if mask & DV_II_50_0_BIT != 0
+            && ((w[55] ^ w[58]) & (1 << 29) == 0
+                || (w[38] ^ w[42] >> 25) & (1 << 3) != 0
+                || (w[37] ^ w[42] << 2) & (1 << 30) == 0)
+        {
+            mask &= !DV_II_50_0_BIT
+        }
+        if mask & DV_II_50_2_BIT != 0
+            && ((w[54] ^ w[55] << 5) & (1 << 6) != 0
+                || (w[52] ^ w[54]) & (1 << 6) != 0
+                || (w[51] ^ w[55]) & (1 << 1) == 0
+                || (w[45] ^ w[47]) & (1 << 1) == 0
+                || (w[38] ^ w[42] >> 25) & (1 << 5) != 0
+                || (w[37] ^ w[42] >> 30) & (1 << 0) == 0)
+        {
+            mask &= !DV_II_50_2_BIT
+        }
+        if mask & DV_II_51_0_BIT != 0
+            && ((w[39] ^ w[43] >> 25) & (1 << 3) != 0 || (w[38] ^ w[43] << 2) & (1 << 30) == 0)
+        {
+            mask &= !DV_II_51_0_BIT
+        }
+        if mask & DV_II_51_2_BIT != 0
+            && ((w[55] ^ w[56] << 5) & (1 << 6) != 0
+                || (w[53] ^ w[55]) & (1 << 6) != 0
+                || (w[52] ^ w[56]) & (1 << 1) == 0
+                || (w[46] ^ w[48]) & (1 << 1) == 0
+                || (w[39] ^ w[43] >> 25) & (1 << 5) != 0
+                || (w[38] ^ w[43] >> 30) & (1 << 0) == 0)
+        {
+            mask &= !DV_II_51_2_BIT
+        }
+        if mask & DV_II_52_0_BIT != 0
+            && ((w[59] ^ w[60]) & (1 << 29) != 0
+                || (w[40] ^ w[44] >> 25) & (1 << 3) != 0
+                || (w[40] ^ w[44] >> 25) & (1 << 4) != 0
+                || (w[39] ^ w[44] << 2) & (1 << 30) == 0)
+        {
+            mask &= !DV_II_52_0_BIT
+        }
+        if mask & DV_II_53_0_BIT != 0
+            && ((w[58] ^ w[61]) & (1 << 29) == 0
+                || (w[57] ^ w[61] >> 25) & (1 << 4) != 0
+                || (w[41] ^ w[45] >> 25) & (1 << 3) != 0
+                || (w[41] ^ w[45] >> 25) & (1 << 4) != 0)
+        {
+            mask &= !DV_II_53_0_BIT
+        }
+        if mask & DV_II_54_0_BIT != 0
+            && ((w[58] ^ w[62] >> 25) & (1 << 4) != 0
+                || (w[42] ^ w[46] >> 25) & (1 << 3) != 0
+                || (w[42] ^ w[46] >> 25) & (1 << 4) != 0)
+        {
+            mask &= !DV_II_54_0_BIT
+        }
+        if mask & DV_II_55_0_BIT != 0
+            && ((w[59] ^ w[63] >> 25) & (1 << 4) != 0
+                || (w[57] ^ w[59] >> 25) & (1 << 4) != 0
+                || (w[43] ^ w[47] >> 25) & (1 << 3) != 0
+                || (w[43] ^ w[47] >> 25) & (1 << 4) != 0)
+        {
+            mask &= !DV_II_55_0_BIT
+        }
+        if mask & DV_II_56_0_BIT != 0
+            && ((w[60] ^ w[64] >> 25) & (1 << 4) != 0
+                || (w[44] ^ w[48] >> 25) & (1 << 3) != 0
+                || (w[44] ^ w[48] >> 25) & (1 << 4) != 0)
+        {
+            mask &= !DV_II_56_0_BIT
+        }
+    }
+    mask
+}