summaryrefslogtreecommitdiffstats
path: root/extra
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
commit7e5d7eea9c580ef4b41a765bde624af431942b96 (patch)
tree2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /extra
parentAdding debian version 1.70.0+dfsg1-9. (diff)
downloadrustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz
rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extra')
-rw-r--r--extra/bitflags-1.3.2/.cargo-checksum.json1
-rw-r--r--extra/bitflags-1.3.2/CHANGELOG.md206
-rw-r--r--extra/bitflags-1.3.2/CODE_OF_CONDUCT.md73
-rw-r--r--extra/bitflags-1.3.2/Cargo.toml58
-rw-r--r--extra/bitflags-1.3.2/LICENSE-APACHE201
-rw-r--r--extra/bitflags-1.3.2/LICENSE-MIT25
-rw-r--r--extra/bitflags-1.3.2/README.md32
-rw-r--r--extra/bitflags-1.3.2/src/example_generated.rs14
-rw-r--r--extra/bitflags-1.3.2/src/lib.rs1729
-rw-r--r--extra/bitflags-1.3.2/tests/basic.rs20
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/impls/copy.rs10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/impls/copy.stderr.beta27
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/impls/eq.rs10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/impls/eq.stderr.beta55
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.rs123
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.stderr.beta27
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.rs13
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.stderr.beta13
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.rs13
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.stderr.beta10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.rs18
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.stderr.beta18
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.rs9
-rw-r--r--extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.stderr.beta5
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/impls/convert.rs17
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/impls/default.rs10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/impls/inherent_methods.rs15
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/redefinition/core.rs14
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/redefinition/stringify.rs19
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/repr/c.rs10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/repr/transparent.rs10
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/visibility/bits_field.rs11
-rw-r--r--extra/bitflags-1.3.2/tests/compile-pass/visibility/pub_in.rs19
-rw-r--r--extra/bitflags-1.3.2/tests/compile.rs63
-rw-r--r--extra/bitflags/.cargo-checksum.json1
-rw-r--r--extra/bitflags/CHANGELOG.md442
-rw-r--r--extra/bitflags/CODE_OF_CONDUCT.md73
-rw-r--r--extra/bitflags/CONTRIBUTING.md9
-rw-r--r--extra/bitflags/Cargo.lock268
-rw-r--r--extra/bitflags/Cargo.toml95
-rw-r--r--extra/bitflags/LICENSE-APACHE201
-rw-r--r--extra/bitflags/LICENSE-MIT25
-rw-r--r--extra/bitflags/README.md77
-rw-r--r--extra/bitflags/SECURITY.md13
-rw-r--r--extra/bitflags/benches/parse.rs96
-rw-r--r--extra/bitflags/examples/custom_bits_type.rs97
-rw-r--r--extra/bitflags/examples/custom_derive.rs23
-rw-r--r--extra/bitflags/examples/fmt.rs49
-rw-r--r--extra/bitflags/examples/macro_free.rs61
-rw-r--r--extra/bitflags/examples/serde.rs36
-rw-r--r--extra/bitflags/spec.md552
-rw-r--r--extra/bitflags/src/example_generated.rs65
-rw-r--r--extra/bitflags/src/external.rs262
-rw-r--r--extra/bitflags/src/external/arbitrary.rs33
-rw-r--r--extra/bitflags/src/external/bytemuck.rs19
-rw-r--r--extra/bitflags/src/external/serde.rs93
-rw-r--r--extra/bitflags/src/internal.rs125
-rw-r--r--extra/bitflags/src/iter.rs145
-rw-r--r--extra/bitflags/src/lib.rs921
-rw-r--r--extra/bitflags/src/parser.rs247
-rw-r--r--extra/bitflags/src/public.rs543
-rw-r--r--extra/bitflags/src/tests.rs131
-rw-r--r--extra/bitflags/src/traits.rs430
-rw-r--r--extra/git2-curl/.cargo-checksum.json1
-rw-r--r--extra/git2-curl/CHANGELOG.md26
-rw-r--r--extra/git2-curl/Cargo.toml58
-rw-r--r--extra/git2-curl/LICENSE-APACHE201
-rw-r--r--extra/git2-curl/LICENSE-MIT25
-rw-r--r--extra/git2-curl/debian/patches/remove-zlib-ng-compat.patch10
-rw-r--r--extra/git2-curl/debian/patches/series1
-rw-r--r--extra/git2-curl/src/lib.rs291
-rw-r--r--extra/git2-curl/tests/all.rs74
-rw-r--r--extra/git2/.cargo-checksum.json1
-rw-r--r--extra/git2/CHANGELOG.md195
-rw-r--r--extra/git2/CONTRIBUTING.md62
-rw-r--r--extra/git2/Cargo.lock578
-rw-r--r--extra/git2/Cargo.toml77
-rw-r--r--extra/git2/LICENSE-APACHE201
-rw-r--r--extra/git2/LICENSE-MIT25
-rw-r--r--extra/git2/README.md71
-rwxr-xr-xextra/git2/ci/publish.sh46
-rw-r--r--extra/git2/debian/patches/disable-vendor.patch14
-rw-r--r--extra/git2/debian/patches/remove-zlib-ng-compat.patch10
-rw-r--r--extra/git2/debian/patches/series3
-rw-r--r--extra/git2/debian/patches/skip-credential_helper5-if-no-git.patch15
-rw-r--r--extra/git2/examples/add.rs81
-rw-r--r--extra/git2/examples/blame.rs104
-rw-r--r--extra/git2/examples/cat-file.rs149
-rw-r--r--extra/git2/examples/clone.rs126
-rw-r--r--extra/git2/examples/diff.rs368
-rw-r--r--extra/git2/examples/fetch.rs127
-rw-r--r--extra/git2/examples/init.rs145
-rw-r--r--extra/git2/examples/log.rs310
-rw-r--r--extra/git2/examples/ls-remote.rs51
-rw-r--r--extra/git2/examples/pull.rs208
-rw-r--r--extra/git2/examples/rev-list.rs105
-rw-r--r--extra/git2/examples/rev-parse.rs60
-rw-r--r--extra/git2/examples/status.rs441
-rw-r--r--extra/git2/examples/tag.rs127
-rw-r--r--extra/git2/src/apply.rs208
-rw-r--r--extra/git2/src/attr.rs175
-rw-r--r--extra/git2/src/blame.rs379
-rw-r--r--extra/git2/src/blob.rs208
-rw-r--r--extra/git2/src/branch.rs197
-rw-r--r--extra/git2/src/buf.rs71
-rw-r--r--extra/git2/src/build.rs861
-rw-r--r--extra/git2/src/call.rs246
-rw-r--r--extra/git2/src/cert.rs191
-rw-r--r--extra/git2/src/cherrypick.rs72
-rw-r--r--extra/git2/src/commit.rs473
-rw-r--r--extra/git2/src/config.rs777
-rw-r--r--extra/git2/src/cred.rs717
-rw-r--r--extra/git2/src/describe.rs201
-rw-r--r--extra/git2/src/diff.rs1863
-rw-r--r--extra/git2/src/email.rs183
-rw-r--r--extra/git2/src/error.rs399
-rw-r--r--extra/git2/src/index.rs929
-rw-r--r--extra/git2/src/indexer.rs255
-rw-r--r--extra/git2/src/lib.rs1668
-rw-r--r--extra/git2/src/mailmap.rs134
-rw-r--r--extra/git2/src/mempack.rs49
-rw-r--r--extra/git2/src/merge.rs194
-rw-r--r--extra/git2/src/message.rs349
-rw-r--r--extra/git2/src/note.rs147
-rw-r--r--extra/git2/src/object.rs248
-rw-r--r--extra/git2/src/odb.rs729
-rw-r--r--extra/git2/src/oid.rs259
-rw-r--r--extra/git2/src/oid_array.rs52
-rw-r--r--extra/git2/src/opts.rs206
-rw-r--r--extra/git2/src/packbuilder.rs413
-rw-r--r--extra/git2/src/panic.rs33
-rw-r--r--extra/git2/src/patch.rs235
-rw-r--r--extra/git2/src/pathspec.rs368
-rw-r--r--extra/git2/src/proxy_options.rs56
-rw-r--r--extra/git2/src/push_update.rs55
-rw-r--r--extra/git2/src/rebase.rs441
-rw-r--r--extra/git2/src/reference.rs586
-rw-r--r--extra/git2/src/reflog.rs196
-rw-r--r--extra/git2/src/refspec.rs122
-rw-r--r--extra/git2/src/remote.rs1123
-rw-r--r--extra/git2/src/remote_callbacks.rs518
-rw-r--r--extra/git2/src/repo.rs4226
-rw-r--r--extra/git2/src/revert.rs69
-rw-r--r--extra/git2/src/revspec.rs34
-rw-r--r--extra/git2/src/revwalk.rs316
-rw-r--r--extra/git2/src/signature.rs189
-rw-r--r--extra/git2/src/stash.rs348
-rw-r--r--extra/git2/src/status.rs435
-rw-r--r--extra/git2/src/string_array.rs136
-rw-r--r--extra/git2/src/submodule.rs471
-rw-r--r--extra/git2/src/tag.rs234
-rw-r--r--extra/git2/src/tagforeach.rs69
-rw-r--r--extra/git2/src/test.rs89
-rw-r--r--extra/git2/src/time.rs127
-rw-r--r--extra/git2/src/tracing.rs85
-rw-r--r--extra/git2/src/transaction.rs285
-rw-r--r--extra/git2/src/transport.rs429
-rw-r--r--extra/git2/src/tree.rs570
-rw-r--r--extra/git2/src/treebuilder.rs234
-rw-r--r--extra/git2/src/util.rs342
-rw-r--r--extra/git2/src/version.rs95
-rw-r--r--extra/git2/src/worktree.rs331
-rw-r--r--extra/git2/tests/add_extensions.rs21
-rw-r--r--extra/git2/tests/get_extensions.rs16
-rw-r--r--extra/git2/tests/global_state.rs47
-rw-r--r--extra/git2/tests/remove_extensions.rs19
-rw-r--r--extra/libgit2-sys/.cargo-checksum.json1
-rw-r--r--extra/libgit2-sys/CHANGELOG.md170
-rw-r--r--extra/libgit2-sys/Cargo.toml63
-rw-r--r--extra/libgit2-sys/LICENSE-APACHE201
-rw-r--r--extra/libgit2-sys/LICENSE-MIT25
-rw-r--r--extra/libgit2-sys/build.rs295
-rw-r--r--extra/libgit2-sys/debian/patches/disable-vendor.patch26
-rw-r--r--extra/libgit2-sys/debian/patches/remove-zlib-ng-compat.patch15
-rw-r--r--extra/libgit2-sys/debian/patches/series2
-rw-r--r--extra/libgit2-sys/lib.rs4312
176 files changed, 43330 insertions, 0 deletions
diff --git a/extra/bitflags-1.3.2/.cargo-checksum.json b/extra/bitflags-1.3.2/.cargo-checksum.json
new file mode 100644
index 000000000..3618c16b0
--- /dev/null
+++ b/extra/bitflags-1.3.2/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{},"package":"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"} \ No newline at end of file
diff --git a/extra/bitflags-1.3.2/CHANGELOG.md b/extra/bitflags-1.3.2/CHANGELOG.md
new file mode 100644
index 000000000..12fea1673
--- /dev/null
+++ b/extra/bitflags-1.3.2/CHANGELOG.md
@@ -0,0 +1,206 @@
+# 1.3.2
+
+- Allow `non_snake_case` in generated flags types ([#256])
+
+[#252]: https://github.com/bitflags/bitflags/pull/256
+
+# 1.3.1
+
+- Revert unconditional `#[repr(transparent)]` ([#252])
+
+[#252]: https://github.com/bitflags/bitflags/pull/252
+
+# 1.3.0 (yanked)
+
+- Add `#[repr(transparent)]` ([#187])
+
+- End `empty` doc comment with full stop ([#202])
+
+- Fix typo in crate root docs ([#206])
+
+- Document from_bits_unchecked unsafety ([#207])
+
+- Let `is_all` ignore extra bits ([#211])
+
+- Allows empty flag definition ([#225])
+
+- Making crate accessible from std ([#227])
+
+- Make `from_bits` a const fn ([#229])
+
+- Allow multiple bitflags structs in one macro invocation ([#235])
+
+- Add named functions to perform set operations ([#244])
+
+- Fix typos in method docs ([#245])
+
+- Modernization of the `bitflags` macro to take advantage of newer features and 2018 idioms ([#246])
+
+- Fix regression (in an unreleased feature) and simplify tests ([#247])
+
+- Use `Self` and fix bug when overriding `stringify!` ([#249])
+
+[#187]: https://github.com/bitflags/bitflags/pull/187
+[#202]: https://github.com/bitflags/bitflags/pull/202
+[#206]: https://github.com/bitflags/bitflags/pull/206
+[#207]: https://github.com/bitflags/bitflags/pull/207
+[#211]: https://github.com/bitflags/bitflags/pull/211
+[#225]: https://github.com/bitflags/bitflags/pull/225
+[#227]: https://github.com/bitflags/bitflags/pull/227
+[#229]: https://github.com/bitflags/bitflags/pull/229
+[#235]: https://github.com/bitflags/bitflags/pull/235
+[#244]: https://github.com/bitflags/bitflags/pull/244
+[#245]: https://github.com/bitflags/bitflags/pull/245
+[#246]: https://github.com/bitflags/bitflags/pull/246
+[#247]: https://github.com/bitflags/bitflags/pull/247
+[#249]: https://github.com/bitflags/bitflags/pull/249
+
+# 1.2.1
+
+- Remove extraneous `#[inline]` attributes ([#194])
+
+[#194]: https://github.com/bitflags/bitflags/pull/194
+
+# 1.2.0
+
+- Fix typo: {Lower, Upper}Exp - {Lower, Upper}Hex ([#183])
+
+- Add support for "unknown" bits ([#188])
+
+[#183]: https://github.com/rust-lang-nursery/bitflags/pull/183
+[#188]: https://github.com/rust-lang-nursery/bitflags/pull/188
+
+# 1.1.0
+
+This is a re-release of `1.0.5`, which was yanked due to a bug in the RLS.
+
+# 1.0.5
+
+- Use compiletest_rs flags supported by stable toolchain ([#171])
+
+- Put the user provided attributes first ([#173])
+
+- Make bitflags methods `const` on newer compilers ([#175])
+
+[#171]: https://github.com/rust-lang-nursery/bitflags/pull/171
+[#173]: https://github.com/rust-lang-nursery/bitflags/pull/173
+[#175]: https://github.com/rust-lang-nursery/bitflags/pull/175
+
+# 1.0.4
+
+- Support Rust 2018 style macro imports ([#165])
+
+ ```rust
+ use bitflags::bitflags;
+ ```
+
+[#165]: https://github.com/rust-lang-nursery/bitflags/pull/165
+
+# 1.0.3
+
+- Improve zero value flag handling and documentation ([#157])
+
+[#157]: https://github.com/rust-lang-nursery/bitflags/pull/157
+
+# 1.0.2
+
+- 30% improvement in compile time of bitflags crate ([#156])
+
+- Documentation improvements ([#153])
+
+- Implementation cleanup ([#149])
+
+[#156]: https://github.com/rust-lang-nursery/bitflags/pull/156
+[#153]: https://github.com/rust-lang-nursery/bitflags/pull/153
+[#149]: https://github.com/rust-lang-nursery/bitflags/pull/149
+
+# 1.0.1
+- Add support for `pub(restricted)` specifier on the bitflags struct ([#135])
+- Optimize performance of `all()` when called from a separate crate ([#136])
+
+[#135]: https://github.com/rust-lang-nursery/bitflags/pull/135
+[#136]: https://github.com/rust-lang-nursery/bitflags/pull/136
+
+# 1.0.0
+- **[breaking change]** Macro now generates [associated constants](https://doc.rust-lang.org/reference/items.html#associated-constants) ([#24])
+
+- **[breaking change]** Minimum supported version is Rust **1.20**, due to usage of associated constants
+
+- After being broken in 0.9, the `#[deprecated]` attribute is now supported again ([#112])
+
+- Other improvements to unit tests and documentation ([#106] and [#115])
+
+[#24]: https://github.com/rust-lang-nursery/bitflags/pull/24
+[#106]: https://github.com/rust-lang-nursery/bitflags/pull/106
+[#112]: https://github.com/rust-lang-nursery/bitflags/pull/112
+[#115]: https://github.com/rust-lang-nursery/bitflags/pull/115
+
+## How to update your code to use associated constants
+Assuming the following structure definition:
+```rust
+bitflags! {
+ struct Something: u8 {
+ const FOO = 0b01,
+ const BAR = 0b10
+ }
+}
+```
+In 0.9 and older you could do:
+```rust
+let x = FOO.bits | BAR.bits;
+```
+Now you must use:
+```rust
+let x = Something::FOO.bits | Something::BAR.bits;
+```
+
+# 0.9.1
+- Fix the implementation of `Formatting` traits when other formatting traits were present in scope ([#105])
+
+[#105]: https://github.com/rust-lang-nursery/bitflags/pull/105
+
+# 0.9.0
+- **[breaking change]** Use struct keyword instead of flags to define bitflag types ([#84])
+
+- **[breaking change]** Terminate const items with semicolons instead of commas ([#87])
+
+- Implement the `Hex`, `Octal`, and `Binary` formatting traits ([#86])
+
+- Printing an empty flag value with the `Debug` trait now prints "(empty)" instead of nothing ([#85])
+
+- The `bitflags!` macro can now be used inside of a fn body, to define a type local to that function ([#74])
+
+[#74]: https://github.com/rust-lang-nursery/bitflags/pull/74
+[#84]: https://github.com/rust-lang-nursery/bitflags/pull/84
+[#85]: https://github.com/rust-lang-nursery/bitflags/pull/85
+[#86]: https://github.com/rust-lang-nursery/bitflags/pull/86
+[#87]: https://github.com/rust-lang-nursery/bitflags/pull/87
+
+# 0.8.2
+- Update feature flag used when building bitflags as a dependency of the Rust toolchain
+
+# 0.8.1
+- Allow bitflags to be used as a dependency of the Rust toolchain
+
+# 0.8.0
+- Add support for the experimental `i128` and `u128` integer types ([#57])
+- Add set method: `flags.set(SOME_FLAG, true)` or `flags.set(SOME_FLAG, false)` ([#55])
+ This may break code that defines its own set method
+
+[#55]: https://github.com/rust-lang-nursery/bitflags/pull/55
+[#57]: https://github.com/rust-lang-nursery/bitflags/pull/57
+
+# 0.7.1
+*(yanked)*
+
+# 0.7.0
+- Implement the Extend trait ([#49])
+- Allow definitions inside the `bitflags!` macro to refer to items imported from other modules ([#51])
+
+[#49]: https://github.com/rust-lang-nursery/bitflags/pull/49
+[#51]: https://github.com/rust-lang-nursery/bitflags/pull/51
+
+# 0.6.0
+- The `no_std` feature was removed as it is now the default
+- The `assignment_operators` feature was remove as it is now enabled by default
+- Some clippy suggestions have been applied
diff --git a/extra/bitflags-1.3.2/CODE_OF_CONDUCT.md b/extra/bitflags-1.3.2/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..f7add90ae
--- /dev/null
+++ b/extra/bitflags-1.3.2/CODE_OF_CONDUCT.md
@@ -0,0 +1,73 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+education, socio-economic status, nationality, personal appearance, race,
+religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at coc@senaite.org. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org \ No newline at end of file
diff --git a/extra/bitflags-1.3.2/Cargo.toml b/extra/bitflags-1.3.2/Cargo.toml
new file mode 100644
index 000000000..9d54c725a
--- /dev/null
+++ b/extra/bitflags-1.3.2/Cargo.toml
@@ -0,0 +1,58 @@
+# 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 believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "bitflags"
+version = "1.3.2"
+authors = ["The Rust Project Developers"]
+exclude = ["bors.toml"]
+description = "A macro to generate structures which behave like bitflags.\n"
+homepage = "https://github.com/bitflags/bitflags"
+documentation = "https://docs.rs/bitflags"
+readme = "README.md"
+keywords = ["bit", "bitmask", "bitflags", "flags"]
+categories = ["no-std"]
+license = "MIT/Apache-2.0"
+repository = "https://github.com/bitflags/bitflags"
+[package.metadata.docs.rs]
+features = ["example_generated"]
+[dependencies.compiler_builtins]
+version = "0.1.2"
+optional = true
+
+[dependencies.core]
+version = "1.0.0"
+optional = true
+package = "rustc-std-workspace-core"
+[dev-dependencies.rustversion]
+version = "1.0"
+
+[dev-dependencies.serde]
+version = "1.0"
+
+[dev-dependencies.serde_derive]
+version = "1.0"
+
+[dev-dependencies.serde_json]
+version = "1.0"
+
+[dev-dependencies.trybuild]
+version = "1.0"
+
+[dev-dependencies.walkdir]
+version = "2.3"
+
+[features]
+default = []
+example_generated = []
+rustc-dep-of-std = ["core", "compiler_builtins"]
diff --git a/extra/bitflags-1.3.2/LICENSE-APACHE b/extra/bitflags-1.3.2/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/extra/bitflags-1.3.2/LICENSE-APACHE
@@ -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/extra/bitflags-1.3.2/LICENSE-MIT b/extra/bitflags-1.3.2/LICENSE-MIT
new file mode 100644
index 000000000..39d4bdb5a
--- /dev/null
+++ b/extra/bitflags-1.3.2/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 The Rust 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/extra/bitflags-1.3.2/README.md b/extra/bitflags-1.3.2/README.md
new file mode 100644
index 000000000..0da0f8536
--- /dev/null
+++ b/extra/bitflags-1.3.2/README.md
@@ -0,0 +1,32 @@
+bitflags
+========
+
+[![Rust](https://github.com/bitflags/bitflags/workflows/Rust/badge.svg)](https://github.com/bitflags/bitflags/actions)
+[![Join the chat at https://gitter.im/bitflags/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/bitflags/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge)
+[![Latest version](https://img.shields.io/crates/v/bitflags.svg)](https://crates.io/crates/bitflags)
+[![Documentation](https://docs.rs/bitflags/badge.svg)](https://docs.rs/bitflags)
+![License](https://img.shields.io/crates/l/bitflags.svg)
+
+A Rust macro to generate structures which behave like a set of bitflags
+
+- [Documentation](https://docs.rs/bitflags)
+- [Release notes](https://github.com/bitflags/bitflags/releases)
+
+## Usage
+
+Add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+bitflags = "1.3"
+```
+
+and this to your source code:
+
+```rust
+use bitflags::bitflags;
+```
+
+## Rust Version Support
+
+The minimum supported Rust version is 1.46 due to use of associated constants and const functions.
diff --git a/extra/bitflags-1.3.2/src/example_generated.rs b/extra/bitflags-1.3.2/src/example_generated.rs
new file mode 100644
index 000000000..cf188d99c
--- /dev/null
+++ b/extra/bitflags-1.3.2/src/example_generated.rs
@@ -0,0 +1,14 @@
+//! This module shows an example of code generated by the macro. **IT MUST NOT BE USED OUTSIDE THIS
+//! CRATE**.
+
+bitflags! {
+ /// This is the same `Flags` struct defined in the [crate level example](../index.html#example).
+ /// Note that this struct is just for documentation purposes only, it must not be used outside
+ /// this crate.
+ pub struct Flags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+ const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
+ }
+}
diff --git a/extra/bitflags-1.3.2/src/lib.rs b/extra/bitflags-1.3.2/src/lib.rs
new file mode 100644
index 000000000..935e432f1
--- /dev/null
+++ b/extra/bitflags-1.3.2/src/lib.rs
@@ -0,0 +1,1729 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! A typesafe bitmask flag generator useful for sets of C-style bitmask flags.
+//! It can be used for creating typesafe wrappers around C APIs.
+//!
+//! The `bitflags!` macro generates `struct`s that manage a set of flags. The
+//! flags should only be defined for integer types, otherwise unexpected type
+//! errors may occur at compile time.
+//!
+//! # Example
+//!
+//! ```
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! struct Flags: u32 {
+//! const A = 0b00000001;
+//! const B = 0b00000010;
+//! const C = 0b00000100;
+//! const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
+//! }
+//! }
+//!
+//! fn main() {
+//! let e1 = Flags::A | Flags::C;
+//! let e2 = Flags::B | Flags::C;
+//! assert_eq!((e1 | e2), Flags::ABC); // union
+//! assert_eq!((e1 & e2), Flags::C); // intersection
+//! assert_eq!((e1 - e2), Flags::A); // set difference
+//! assert_eq!(!e2, Flags::A); // set complement
+//! }
+//! ```
+//!
+//! See [`example_generated::Flags`](./example_generated/struct.Flags.html) for documentation of code
+//! generated by the above `bitflags!` expansion.
+//!
+//! The generated `struct`s can also be extended with type and trait
+//! implementations:
+//!
+//! ```
+//! use std::fmt;
+//!
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! struct Flags: u32 {
+//! const A = 0b00000001;
+//! const B = 0b00000010;
+//! }
+//! }
+//!
+//! impl Flags {
+//! pub fn clear(&mut self) {
+//! self.bits = 0; // The `bits` field can be accessed from within the
+//! // same module where the `bitflags!` macro was invoked.
+//! }
+//! }
+//!
+//! impl fmt::Display for Flags {
+//! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+//! write!(f, "hi!")
+//! }
+//! }
+//!
+//! fn main() {
+//! let mut flags = Flags::A | Flags::B;
+//! flags.clear();
+//! assert!(flags.is_empty());
+//! assert_eq!(format!("{}", flags), "hi!");
+//! assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B");
+//! assert_eq!(format!("{:?}", Flags::B), "B");
+//! }
+//! ```
+//!
+//! # Visibility
+//!
+//! The generated structs and their associated flag constants are not exported
+//! out of the current module by default. A definition can be exported out of
+//! the current module by adding `pub` before `struct`:
+//!
+//! ```
+//! mod example {
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! pub struct Flags1: u32 {
+//! const A = 0b00000001;
+//! }
+//!
+//! # pub
+//! struct Flags2: u32 {
+//! const B = 0b00000010;
+//! }
+//! }
+//! }
+//!
+//! fn main() {
+//! let flag1 = example::Flags1::A;
+//! let flag2 = example::Flags2::B; // error: const `B` is private
+//! }
+//! ```
+//!
+//! # Attributes
+//!
+//! Attributes can be attached to the generated `struct`s by placing them
+//! before the `struct` keyword.
+//!
+//! ## Representations
+//!
+//! It's valid to add a `#[repr(C)]` or `#[repr(transparent)]` attribute to a type
+//! generated by `bitflags!`. In these cases, the type is guaranteed to be a newtype.
+//!
+//! ```
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! #[repr(transparent)]
+//! struct Flags: u32 {
+//! const A = 0b00000001;
+//! const B = 0b00000010;
+//! const C = 0b00000100;
+//! }
+//! }
+//! ```
+//!
+//! # Trait implementations
+//!
+//! The `Copy`, `Clone`, `PartialEq`, `Eq`, `PartialOrd`, `Ord` and `Hash`
+//! traits are automatically derived for the `struct`s using the `derive` attribute.
+//! Additional traits can be derived by providing an explicit `derive`
+//! attribute on `struct`.
+//!
+//! The `Extend` and `FromIterator` traits are implemented for the `struct`s,
+//! too: `Extend` adds the union of the instances of the `struct` iterated over,
+//! while `FromIterator` calculates the union.
+//!
+//! The `Binary`, `Debug`, `LowerHex`, `Octal` and `UpperHex` traits are also
+//! implemented by displaying the bits value of the internal struct.
+//!
+//! ## Operators
+//!
+//! The following operator traits are implemented for the generated `struct`s:
+//!
+//! - `BitOr` and `BitOrAssign`: union
+//! - `BitAnd` and `BitAndAssign`: intersection
+//! - `BitXor` and `BitXorAssign`: toggle
+//! - `Sub` and `SubAssign`: set difference
+//! - `Not`: set complement
+//!
+//! # Methods
+//!
+//! The following methods are defined for the generated `struct`s:
+//!
+//! - `empty`: an empty set of flags
+//! - `all`: the set of all defined flags
+//! - `bits`: the raw value of the flags currently stored
+//! - `from_bits`: convert from underlying bit representation, unless that
+//! representation contains bits that do not correspond to a
+//! defined flag
+//! - `from_bits_truncate`: convert from underlying bit representation, dropping
+//! any bits that do not correspond to defined flags
+//! - `from_bits_unchecked`: convert from underlying bit representation, keeping
+//! all bits (even those not corresponding to defined
+//! flags)
+//! - `is_empty`: `true` if no flags are currently stored
+//! - `is_all`: `true` if currently set flags exactly equal all defined flags
+//! - `intersects`: `true` if there are flags common to both `self` and `other`
+//! - `contains`: `true` if all of the flags in `other` are contained within `self`
+//! - `insert`: inserts the specified flags in-place
+//! - `remove`: removes the specified flags in-place
+//! - `toggle`: the specified flags will be inserted if not present, and removed
+//! if they are.
+//! - `set`: inserts or removes the specified flags depending on the passed value
+//! - `intersection`: returns a new set of flags, containing only the flags present
+//! in both `self` and `other` (the argument to the function).
+//! - `union`: returns a new set of flags, containing any flags present in
+//! either `self` or `other` (the argument to the function).
+//! - `difference`: returns a new set of flags, containing all flags present in
+//! `self` without any of the flags present in `other` (the
+//! argument to the function).
+//! - `symmetric_difference`: returns a new set of flags, containing all flags
+//! present in either `self` or `other` (the argument
+//! to the function), but not both.
+//! - `complement`: returns a new set of flags, containing all flags which are
+//! not set in `self`, but which are allowed for this type.
+//!
+//! ## Default
+//!
+//! The `Default` trait is not automatically implemented for the generated structs.
+//!
+//! If your default value is equal to `0` (which is the same value as calling `empty()`
+//! on the generated struct), you can simply derive `Default`:
+//!
+//! ```
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! // Results in default value with bits: 0
+//! #[derive(Default)]
+//! struct Flags: u32 {
+//! const A = 0b00000001;
+//! const B = 0b00000010;
+//! const C = 0b00000100;
+//! }
+//! }
+//!
+//! fn main() {
+//! let derived_default: Flags = Default::default();
+//! assert_eq!(derived_default.bits(), 0);
+//! }
+//! ```
+//!
+//! If your default value is not equal to `0` you need to implement `Default` yourself:
+//!
+//! ```
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! struct Flags: u32 {
+//! const A = 0b00000001;
+//! const B = 0b00000010;
+//! const C = 0b00000100;
+//! }
+//! }
+//!
+//! // explicit `Default` implementation
+//! impl Default for Flags {
+//! fn default() -> Flags {
+//! Flags::A | Flags::C
+//! }
+//! }
+//!
+//! fn main() {
+//! let implemented_default: Flags = Default::default();
+//! assert_eq!(implemented_default, (Flags::A | Flags::C));
+//! }
+//! ```
+//!
+//! # Zero Flags
+//!
+//! Flags with a value equal to zero will have some strange behavior that one should be aware of.
+//!
+//! ```
+//! use bitflags::bitflags;
+//!
+//! bitflags! {
+//! struct Flags: u32 {
+//! const NONE = 0b00000000;
+//! const SOME = 0b00000001;
+//! }
+//! }
+//!
+//! fn main() {
+//! let empty = Flags::empty();
+//! let none = Flags::NONE;
+//! let some = Flags::SOME;
+//!
+//! // Zero flags are treated as always present
+//! assert!(empty.contains(Flags::NONE));
+//! assert!(none.contains(Flags::NONE));
+//! assert!(some.contains(Flags::NONE));
+//!
+//! // Zero flags will be ignored when testing for emptiness
+//! assert!(none.is_empty());
+//! }
+//! ```
+//!
+//! Users should generally avoid defining a flag with a value of zero.
+
+#![cfg_attr(not(test), no_std)]
+#![doc(html_root_url = "https://docs.rs/bitflags/1.3.2")]
+
+#[doc(hidden)]
+pub extern crate core as _core;
+
+/// The macro used to generate the flag structures.
+///
+/// See the [crate level docs](../bitflags/index.html) for complete documentation.
+///
+/// # Example
+///
+/// ```
+/// use bitflags::bitflags;
+///
+/// bitflags! {
+/// struct Flags: u32 {
+/// const A = 0b00000001;
+/// const B = 0b00000010;
+/// const C = 0b00000100;
+/// const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
+/// }
+/// }
+///
+/// fn main() {
+/// let e1 = Flags::A | Flags::C;
+/// let e2 = Flags::B | Flags::C;
+/// assert_eq!((e1 | e2), Flags::ABC); // union
+/// assert_eq!((e1 & e2), Flags::C); // intersection
+/// assert_eq!((e1 - e2), Flags::A); // set difference
+/// assert_eq!(!e2, Flags::A); // set complement
+/// }
+/// ```
+///
+/// The generated `struct`s can also be extended with type and trait
+/// implementations:
+///
+/// ```
+/// use std::fmt;
+///
+/// use bitflags::bitflags;
+///
+/// bitflags! {
+/// struct Flags: u32 {
+/// const A = 0b00000001;
+/// const B = 0b00000010;
+/// }
+/// }
+///
+/// impl Flags {
+/// pub fn clear(&mut self) {
+/// self.bits = 0; // The `bits` field can be accessed from within the
+/// // same module where the `bitflags!` macro was invoked.
+/// }
+/// }
+///
+/// impl fmt::Display for Flags {
+/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+/// write!(f, "hi!")
+/// }
+/// }
+///
+/// fn main() {
+/// let mut flags = Flags::A | Flags::B;
+/// flags.clear();
+/// assert!(flags.is_empty());
+/// assert_eq!(format!("{}", flags), "hi!");
+/// assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B");
+/// assert_eq!(format!("{:?}", Flags::B), "B");
+/// }
+/// ```
+#[macro_export(local_inner_macros)]
+macro_rules! bitflags {
+ (
+ $(#[$outer:meta])*
+ $vis:vis struct $BitFlags:ident: $T:ty {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:ident = $value:expr;
+ )*
+ }
+
+ $($t:tt)*
+ ) => {
+ $(#[$outer])*
+ #[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
+ $vis struct $BitFlags {
+ bits: $T,
+ }
+
+ __impl_bitflags! {
+ $BitFlags: $T {
+ $(
+ $(#[$inner $($args)*])*
+ $Flag = $value;
+ )*
+ }
+ }
+
+ bitflags! {
+ $($t)*
+ }
+ };
+ () => {};
+}
+
+// A helper macro to implement the `all` function.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_all_bitflags {
+ (
+ $BitFlags:ident: $T:ty {
+ $(
+ $(#[$attr:ident $($args:tt)*])*
+ $Flag:ident = $value:expr;
+ )+
+ }
+ ) => {
+ // See `Debug::fmt` for why this approach is taken.
+ #[allow(non_snake_case)]
+ trait __BitFlags {
+ $(
+ const $Flag: $T = 0;
+ )+
+ }
+ #[allow(non_snake_case)]
+ impl __BitFlags for $BitFlags {
+ $(
+ __impl_bitflags! {
+ #[allow(deprecated)]
+ $(? #[$attr $($args)*])*
+ const $Flag: $T = Self::$Flag.bits;
+ }
+ )+
+ }
+ Self { bits: $(<Self as __BitFlags>::$Flag)|+ }
+ };
+ (
+ $BitFlags:ident: $T:ty { }
+ ) => {
+ Self { bits: 0 }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_bitflags {
+ (
+ $BitFlags:ident: $T:ty {
+ $(
+ $(#[$attr:ident $($args:tt)*])*
+ $Flag:ident = $value:expr;
+ )*
+ }
+ ) => {
+ impl $crate::_core::fmt::Debug for $BitFlags {
+ fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
+ // This convoluted approach is to handle #[cfg]-based flag
+ // omission correctly. For example it needs to support:
+ //
+ // #[cfg(unix)] const A: Flag = /* ... */;
+ // #[cfg(windows)] const B: Flag = /* ... */;
+
+ // Unconditionally define a check for every flag, even disabled
+ // ones.
+ #[allow(non_snake_case)]
+ trait __BitFlags {
+ $(
+ #[inline]
+ fn $Flag(&self) -> bool { false }
+ )*
+ }
+
+ // Conditionally override the check for just those flags that
+ // are not #[cfg]ed away.
+ #[allow(non_snake_case)]
+ impl __BitFlags for $BitFlags {
+ $(
+ __impl_bitflags! {
+ #[allow(deprecated)]
+ #[inline]
+ $(? #[$attr $($args)*])*
+ fn $Flag(&self) -> bool {
+ if Self::$Flag.bits == 0 && self.bits != 0 {
+ false
+ } else {
+ self.bits & Self::$Flag.bits == Self::$Flag.bits
+ }
+ }
+ }
+ )*
+ }
+
+ let mut first = true;
+ $(
+ if <Self as __BitFlags>::$Flag(self) {
+ if !first {
+ f.write_str(" | ")?;
+ }
+ first = false;
+ f.write_str($crate::_core::stringify!($Flag))?;
+ }
+ )*
+ let extra_bits = self.bits & !Self::all().bits();
+ if extra_bits != 0 {
+ if !first {
+ f.write_str(" | ")?;
+ }
+ first = false;
+ f.write_str("0x")?;
+ $crate::_core::fmt::LowerHex::fmt(&extra_bits, f)?;
+ }
+ if first {
+ f.write_str("(empty)")?;
+ }
+ Ok(())
+ }
+ }
+ impl $crate::_core::fmt::Binary for $BitFlags {
+ fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
+ $crate::_core::fmt::Binary::fmt(&self.bits, f)
+ }
+ }
+ impl $crate::_core::fmt::Octal for $BitFlags {
+ fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
+ $crate::_core::fmt::Octal::fmt(&self.bits, f)
+ }
+ }
+ impl $crate::_core::fmt::LowerHex for $BitFlags {
+ fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
+ $crate::_core::fmt::LowerHex::fmt(&self.bits, f)
+ }
+ }
+ impl $crate::_core::fmt::UpperHex for $BitFlags {
+ fn fmt(&self, f: &mut $crate::_core::fmt::Formatter) -> $crate::_core::fmt::Result {
+ $crate::_core::fmt::UpperHex::fmt(&self.bits, f)
+ }
+ }
+
+ #[allow(dead_code)]
+ impl $BitFlags {
+ $(
+ $(#[$attr $($args)*])*
+ pub const $Flag: Self = Self { bits: $value };
+ )*
+
+ /// Returns an empty set of flags.
+ #[inline]
+ pub const fn empty() -> Self {
+ Self { bits: 0 }
+ }
+
+ /// Returns the set containing all flags.
+ #[inline]
+ pub const fn all() -> Self {
+ __impl_all_bitflags! {
+ $BitFlags: $T {
+ $(
+ $(#[$attr $($args)*])*
+ $Flag = $value;
+ )*
+ }
+ }
+ }
+
+ /// Returns the raw value of the flags currently stored.
+ #[inline]
+ pub const fn bits(&self) -> $T {
+ self.bits
+ }
+
+ /// Convert from underlying bit representation, unless that
+ /// representation contains bits that do not correspond to a flag.
+ #[inline]
+ pub const fn from_bits(bits: $T) -> $crate::_core::option::Option<Self> {
+ if (bits & !Self::all().bits()) == 0 {
+ $crate::_core::option::Option::Some(Self { bits })
+ } else {
+ $crate::_core::option::Option::None
+ }
+ }
+
+ /// Convert from underlying bit representation, dropping any bits
+ /// that do not correspond to flags.
+ #[inline]
+ pub const fn from_bits_truncate(bits: $T) -> Self {
+ Self { bits: bits & Self::all().bits }
+ }
+
+ /// Convert from underlying bit representation, preserving all
+ /// bits (even those not corresponding to a defined flag).
+ ///
+ /// # Safety
+ ///
+ /// The caller of the `bitflags!` macro can chose to allow or
+ /// disallow extra bits for their bitflags type.
+ ///
+ /// The caller of `from_bits_unchecked()` has to ensure that
+ /// all bits correspond to a defined flag or that extra bits
+ /// are valid for this bitflags type.
+ #[inline]
+ pub const unsafe fn from_bits_unchecked(bits: $T) -> Self {
+ Self { bits }
+ }
+
+ /// Returns `true` if no flags are currently stored.
+ #[inline]
+ pub const fn is_empty(&self) -> bool {
+ self.bits() == Self::empty().bits()
+ }
+
+ /// Returns `true` if all flags are currently set.
+ #[inline]
+ pub const fn is_all(&self) -> bool {
+ Self::all().bits | self.bits == self.bits
+ }
+
+ /// Returns `true` if there are flags common to both `self` and `other`.
+ #[inline]
+ pub const fn intersects(&self, other: Self) -> bool {
+ !(Self { bits: self.bits & other.bits}).is_empty()
+ }
+
+ /// Returns `true` if all of the flags in `other` are contained within `self`.
+ #[inline]
+ pub const fn contains(&self, other: Self) -> bool {
+ (self.bits & other.bits) == other.bits
+ }
+
+ /// Inserts the specified flags in-place.
+ #[inline]
+ pub fn insert(&mut self, other: Self) {
+ self.bits |= other.bits;
+ }
+
+ /// Removes the specified flags in-place.
+ #[inline]
+ pub fn remove(&mut self, other: Self) {
+ self.bits &= !other.bits;
+ }
+
+ /// Toggles the specified flags in-place.
+ #[inline]
+ pub fn toggle(&mut self, other: Self) {
+ self.bits ^= other.bits;
+ }
+
+ /// Inserts or removes the specified flags depending on the passed value.
+ #[inline]
+ pub fn set(&mut self, other: Self, value: bool) {
+ if value {
+ self.insert(other);
+ } else {
+ self.remove(other);
+ }
+ }
+
+ /// Returns the intersection between the flags in `self` and
+ /// `other`.
+ ///
+ /// Specifically, the returned set contains only the flags which are
+ /// present in *both* `self` *and* `other`.
+ ///
+ /// This is equivalent to using the `&` operator (e.g.
+ /// [`ops::BitAnd`]), as in `flags & other`.
+ ///
+ /// [`ops::BitAnd`]: https://doc.rust-lang.org/std/ops/trait.BitAnd.html
+ #[inline]
+ #[must_use]
+ pub const fn intersection(self, other: Self) -> Self {
+ Self { bits: self.bits & other.bits }
+ }
+
+ /// Returns the union of between the flags in `self` and `other`.
+ ///
+ /// Specifically, the returned set contains all flags which are
+ /// present in *either* `self` *or* `other`, including any which are
+ /// present in both (see [`Self::symmetric_difference`] if that
+ /// is undesirable).
+ ///
+ /// This is equivalent to using the `|` operator (e.g.
+ /// [`ops::BitOr`]), as in `flags | other`.
+ ///
+ /// [`ops::BitOr`]: https://doc.rust-lang.org/std/ops/trait.BitOr.html
+ #[inline]
+ #[must_use]
+ pub const fn union(self, other: Self) -> Self {
+ Self { bits: self.bits | other.bits }
+ }
+
+ /// Returns the difference between the flags in `self` and `other`.
+ ///
+ /// Specifically, the returned set contains all flags present in
+ /// `self`, except for the ones present in `other`.
+ ///
+ /// It is also conceptually equivalent to the "bit-clear" operation:
+ /// `flags & !other` (and this syntax is also supported).
+ ///
+ /// This is equivalent to using the `-` operator (e.g.
+ /// [`ops::Sub`]), as in `flags - other`.
+ ///
+ /// [`ops::Sub`]: https://doc.rust-lang.org/std/ops/trait.Sub.html
+ #[inline]
+ #[must_use]
+ pub const fn difference(self, other: Self) -> Self {
+ Self { bits: self.bits & !other.bits }
+ }
+
+ /// Returns the [symmetric difference][sym-diff] between the flags
+ /// in `self` and `other`.
+ ///
+ /// Specifically, the returned set contains the flags present which
+ /// are present in `self` or `other`, but that are not present in
+ /// both. Equivalently, it contains the flags present in *exactly
+ /// one* of the sets `self` and `other`.
+ ///
+ /// This is equivalent to using the `^` operator (e.g.
+ /// [`ops::BitXor`]), as in `flags ^ other`.
+ ///
+ /// [sym-diff]: https://en.wikipedia.org/wiki/Symmetric_difference
+ /// [`ops::BitXor`]: https://doc.rust-lang.org/std/ops/trait.BitXor.html
+ #[inline]
+ #[must_use]
+ pub const fn symmetric_difference(self, other: Self) -> Self {
+ Self { bits: self.bits ^ other.bits }
+ }
+
+ /// Returns the complement of this set of flags.
+ ///
+ /// Specifically, the returned set contains all the flags which are
+ /// not set in `self`, but which are allowed for this type.
+ ///
+ /// Alternatively, it can be thought of as the set difference
+ /// between [`Self::all()`] and `self` (e.g. `Self::all() - self`)
+ ///
+ /// This is equivalent to using the `!` operator (e.g.
+ /// [`ops::Not`]), as in `!flags`.
+ ///
+ /// [`Self::all()`]: Self::all
+ /// [`ops::Not`]: https://doc.rust-lang.org/std/ops/trait.Not.html
+ #[inline]
+ #[must_use]
+ pub const fn complement(self) -> Self {
+ Self::from_bits_truncate(!self.bits)
+ }
+
+ }
+
+ impl $crate::_core::ops::BitOr for $BitFlags {
+ type Output = Self;
+
+ /// Returns the union of the two sets of flags.
+ #[inline]
+ fn bitor(self, other: $BitFlags) -> Self {
+ Self { bits: self.bits | other.bits }
+ }
+ }
+
+ impl $crate::_core::ops::BitOrAssign for $BitFlags {
+ /// Adds the set of flags.
+ #[inline]
+ fn bitor_assign(&mut self, other: Self) {
+ self.bits |= other.bits;
+ }
+ }
+
+ impl $crate::_core::ops::BitXor for $BitFlags {
+ type Output = Self;
+
+ /// Returns the left flags, but with all the right flags toggled.
+ #[inline]
+ fn bitxor(self, other: Self) -> Self {
+ Self { bits: self.bits ^ other.bits }
+ }
+ }
+
+ impl $crate::_core::ops::BitXorAssign for $BitFlags {
+ /// Toggles the set of flags.
+ #[inline]
+ fn bitxor_assign(&mut self, other: Self) {
+ self.bits ^= other.bits;
+ }
+ }
+
+ impl $crate::_core::ops::BitAnd for $BitFlags {
+ type Output = Self;
+
+ /// Returns the intersection between the two sets of flags.
+ #[inline]
+ fn bitand(self, other: Self) -> Self {
+ Self { bits: self.bits & other.bits }
+ }
+ }
+
+ impl $crate::_core::ops::BitAndAssign for $BitFlags {
+ /// Disables all flags disabled in the set.
+ #[inline]
+ fn bitand_assign(&mut self, other: Self) {
+ self.bits &= other.bits;
+ }
+ }
+
+ impl $crate::_core::ops::Sub for $BitFlags {
+ type Output = Self;
+
+ /// Returns the set difference of the two sets of flags.
+ #[inline]
+ fn sub(self, other: Self) -> Self {
+ Self { bits: self.bits & !other.bits }
+ }
+ }
+
+ impl $crate::_core::ops::SubAssign for $BitFlags {
+ /// Disables all flags enabled in the set.
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ self.bits &= !other.bits;
+ }
+ }
+
+ impl $crate::_core::ops::Not for $BitFlags {
+ type Output = Self;
+
+ /// Returns the complement of this set of flags.
+ #[inline]
+ fn not(self) -> Self {
+ Self { bits: !self.bits } & Self::all()
+ }
+ }
+
+ impl $crate::_core::iter::Extend<$BitFlags> for $BitFlags {
+ fn extend<T: $crate::_core::iter::IntoIterator<Item=Self>>(&mut self, iterator: T) {
+ for item in iterator {
+ self.insert(item)
+ }
+ }
+ }
+
+ impl $crate::_core::iter::FromIterator<$BitFlags> for $BitFlags {
+ fn from_iter<T: $crate::_core::iter::IntoIterator<Item=Self>>(iterator: T) -> Self {
+ let mut result = Self::empty();
+ result.extend(iterator);
+ result
+ }
+ }
+ };
+
+ // Every attribute that the user writes on a const is applied to the
+ // corresponding const that we generate, but within the implementation of
+ // Debug and all() we want to ignore everything but #[cfg] attributes. In
+ // particular, including a #[deprecated] attribute on those items would fail
+ // to compile.
+ // https://github.com/bitflags/bitflags/issues/109
+ //
+ // Input:
+ //
+ // ? #[cfg(feature = "advanced")]
+ // ? #[deprecated(note = "Use something else.")]
+ // ? #[doc = r"High quality documentation."]
+ // fn f() -> i32 { /* ... */ }
+ //
+ // Output:
+ //
+ // #[cfg(feature = "advanced")]
+ // fn f() -> i32 { /* ... */ }
+ (
+ $(#[$filtered:meta])*
+ ? #[cfg $($cfgargs:tt)*]
+ $(? #[$rest:ident $($restargs:tt)*])*
+ fn $($item:tt)*
+ ) => {
+ __impl_bitflags! {
+ $(#[$filtered])*
+ #[cfg $($cfgargs)*]
+ $(? #[$rest $($restargs)*])*
+ fn $($item)*
+ }
+ };
+ (
+ $(#[$filtered:meta])*
+ // $next != `cfg`
+ ? #[$next:ident $($nextargs:tt)*]
+ $(? #[$rest:ident $($restargs:tt)*])*
+ fn $($item:tt)*
+ ) => {
+ __impl_bitflags! {
+ $(#[$filtered])*
+ // $next filtered out
+ $(? #[$rest $($restargs)*])*
+ fn $($item)*
+ }
+ };
+ (
+ $(#[$filtered:meta])*
+ fn $($item:tt)*
+ ) => {
+ $(#[$filtered])*
+ fn $($item)*
+ };
+
+ // Every attribute that the user writes on a const is applied to the
+ // corresponding const that we generate, but within the implementation of
+ // Debug and all() we want to ignore everything but #[cfg] attributes. In
+ // particular, including a #[deprecated] attribute on those items would fail
+ // to compile.
+ // https://github.com/bitflags/bitflags/issues/109
+ //
+ // const version
+ //
+ // Input:
+ //
+ // ? #[cfg(feature = "advanced")]
+ // ? #[deprecated(note = "Use something else.")]
+ // ? #[doc = r"High quality documentation."]
+ // const f: i32 { /* ... */ }
+ //
+ // Output:
+ //
+ // #[cfg(feature = "advanced")]
+ // const f: i32 { /* ... */ }
+ (
+ $(#[$filtered:meta])*
+ ? #[cfg $($cfgargs:tt)*]
+ $(? #[$rest:ident $($restargs:tt)*])*
+ const $($item:tt)*
+ ) => {
+ __impl_bitflags! {
+ $(#[$filtered])*
+ #[cfg $($cfgargs)*]
+ $(? #[$rest $($restargs)*])*
+ const $($item)*
+ }
+ };
+ (
+ $(#[$filtered:meta])*
+ // $next != `cfg`
+ ? #[$next:ident $($nextargs:tt)*]
+ $(? #[$rest:ident $($restargs:tt)*])*
+ const $($item:tt)*
+ ) => {
+ __impl_bitflags! {
+ $(#[$filtered])*
+ // $next filtered out
+ $(? #[$rest $($restargs)*])*
+ const $($item)*
+ }
+ };
+ (
+ $(#[$filtered:meta])*
+ const $($item:tt)*
+ ) => {
+ $(#[$filtered])*
+ const $($item)*
+ };
+}
+
+#[cfg(feature = "example_generated")]
+pub mod example_generated;
+
+#[cfg(test)]
+mod tests {
+ use std::collections::hash_map::DefaultHasher;
+ use std::hash::{Hash, Hasher};
+
+ bitflags! {
+ #[doc = "> The first principle is that you must not fool yourself — and"]
+ #[doc = "> you are the easiest person to fool."]
+ #[doc = "> "]
+ #[doc = "> - Richard Feynman"]
+ #[derive(Default)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ #[doc = "<pcwalton> macros are way better at generating code than trans is"]
+ const B = 0b00000010;
+ const C = 0b00000100;
+ #[doc = "* cmr bed"]
+ #[doc = "* strcat table"]
+ #[doc = "<strcat> wait what?"]
+ const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
+ }
+
+ struct _CfgFlags: u32 {
+ #[cfg(unix)]
+ const _CFG_A = 0b01;
+ #[cfg(windows)]
+ const _CFG_B = 0b01;
+ #[cfg(unix)]
+ const _CFG_C = Self::_CFG_A.bits | 0b10;
+ }
+
+ struct AnotherSetOfFlags: i8 {
+ const ANOTHER_FLAG = -1_i8;
+ }
+
+ struct LongFlags: u32 {
+ const LONG_A = 0b1111111111111111;
+ }
+ }
+
+ bitflags! {
+ struct EmptyFlags: u32 {
+ }
+ }
+
+ #[test]
+ fn test_bits() {
+ assert_eq!(Flags::empty().bits(), 0b00000000);
+ assert_eq!(Flags::A.bits(), 0b00000001);
+ assert_eq!(Flags::ABC.bits(), 0b00000111);
+
+ assert_eq!(AnotherSetOfFlags::empty().bits(), 0b00);
+ assert_eq!(AnotherSetOfFlags::ANOTHER_FLAG.bits(), !0_i8);
+
+ assert_eq!(EmptyFlags::empty().bits(), 0b00000000);
+ }
+
+ #[test]
+ fn test_from_bits() {
+ assert_eq!(Flags::from_bits(0), Some(Flags::empty()));
+ assert_eq!(Flags::from_bits(0b1), Some(Flags::A));
+ assert_eq!(Flags::from_bits(0b10), Some(Flags::B));
+ assert_eq!(Flags::from_bits(0b11), Some(Flags::A | Flags::B));
+ assert_eq!(Flags::from_bits(0b1000), None);
+
+ assert_eq!(
+ AnotherSetOfFlags::from_bits(!0_i8),
+ Some(AnotherSetOfFlags::ANOTHER_FLAG)
+ );
+
+ assert_eq!(EmptyFlags::from_bits(0), Some(EmptyFlags::empty()));
+ assert_eq!(EmptyFlags::from_bits(0b1), None);
+ }
+
+ #[test]
+ fn test_from_bits_truncate() {
+ assert_eq!(Flags::from_bits_truncate(0), Flags::empty());
+ assert_eq!(Flags::from_bits_truncate(0b1), Flags::A);
+ assert_eq!(Flags::from_bits_truncate(0b10), Flags::B);
+ assert_eq!(Flags::from_bits_truncate(0b11), (Flags::A | Flags::B));
+ assert_eq!(Flags::from_bits_truncate(0b1000), Flags::empty());
+ assert_eq!(Flags::from_bits_truncate(0b1001), Flags::A);
+
+ assert_eq!(
+ AnotherSetOfFlags::from_bits_truncate(0_i8),
+ AnotherSetOfFlags::empty()
+ );
+
+ assert_eq!(EmptyFlags::from_bits_truncate(0), EmptyFlags::empty());
+ assert_eq!(EmptyFlags::from_bits_truncate(0b1), EmptyFlags::empty());
+ }
+
+ #[test]
+ fn test_from_bits_unchecked() {
+ let extra = unsafe { Flags::from_bits_unchecked(0b1000) };
+ assert_eq!(unsafe { Flags::from_bits_unchecked(0) }, Flags::empty());
+ assert_eq!(unsafe { Flags::from_bits_unchecked(0b1) }, Flags::A);
+ assert_eq!(unsafe { Flags::from_bits_unchecked(0b10) }, Flags::B);
+
+ assert_eq!(
+ unsafe { Flags::from_bits_unchecked(0b11) },
+ (Flags::A | Flags::B)
+ );
+ assert_eq!(
+ unsafe { Flags::from_bits_unchecked(0b1000) },
+ (extra | Flags::empty())
+ );
+ assert_eq!(
+ unsafe { Flags::from_bits_unchecked(0b1001) },
+ (extra | Flags::A)
+ );
+
+ let extra = unsafe { EmptyFlags::from_bits_unchecked(0b1000) };
+ assert_eq!(
+ unsafe { EmptyFlags::from_bits_unchecked(0b1000) },
+ (extra | EmptyFlags::empty())
+ );
+ }
+
+ #[test]
+ fn test_is_empty() {
+ assert!(Flags::empty().is_empty());
+ assert!(!Flags::A.is_empty());
+ assert!(!Flags::ABC.is_empty());
+
+ assert!(!AnotherSetOfFlags::ANOTHER_FLAG.is_empty());
+
+ assert!(EmptyFlags::empty().is_empty());
+ assert!(EmptyFlags::all().is_empty());
+ }
+
+ #[test]
+ fn test_is_all() {
+ assert!(Flags::all().is_all());
+ assert!(!Flags::A.is_all());
+ assert!(Flags::ABC.is_all());
+
+ let extra = unsafe { Flags::from_bits_unchecked(0b1000) };
+ assert!(!extra.is_all());
+ assert!(!(Flags::A | extra).is_all());
+ assert!((Flags::ABC | extra).is_all());
+
+ assert!(AnotherSetOfFlags::ANOTHER_FLAG.is_all());
+
+ assert!(EmptyFlags::all().is_all());
+ assert!(EmptyFlags::empty().is_all());
+ }
+
+ #[test]
+ fn test_two_empties_do_not_intersect() {
+ let e1 = Flags::empty();
+ let e2 = Flags::empty();
+ assert!(!e1.intersects(e2));
+
+ assert!(AnotherSetOfFlags::ANOTHER_FLAG.intersects(AnotherSetOfFlags::ANOTHER_FLAG));
+ }
+
+ #[test]
+ fn test_empty_does_not_intersect_with_full() {
+ let e1 = Flags::empty();
+ let e2 = Flags::ABC;
+ assert!(!e1.intersects(e2));
+ }
+
+ #[test]
+ fn test_disjoint_intersects() {
+ let e1 = Flags::A;
+ let e2 = Flags::B;
+ assert!(!e1.intersects(e2));
+ }
+
+ #[test]
+ fn test_overlapping_intersects() {
+ let e1 = Flags::A;
+ let e2 = Flags::A | Flags::B;
+ assert!(e1.intersects(e2));
+ }
+
+ #[test]
+ fn test_contains() {
+ let e1 = Flags::A;
+ let e2 = Flags::A | Flags::B;
+ assert!(!e1.contains(e2));
+ assert!(e2.contains(e1));
+ assert!(Flags::ABC.contains(e2));
+
+ assert!(AnotherSetOfFlags::ANOTHER_FLAG.contains(AnotherSetOfFlags::ANOTHER_FLAG));
+
+ assert!(EmptyFlags::empty().contains(EmptyFlags::empty()));
+ }
+
+ #[test]
+ fn test_insert() {
+ let mut e1 = Flags::A;
+ let e2 = Flags::A | Flags::B;
+ e1.insert(e2);
+ assert_eq!(e1, e2);
+
+ let mut e3 = AnotherSetOfFlags::empty();
+ e3.insert(AnotherSetOfFlags::ANOTHER_FLAG);
+ assert_eq!(e3, AnotherSetOfFlags::ANOTHER_FLAG);
+ }
+
+ #[test]
+ fn test_remove() {
+ let mut e1 = Flags::A | Flags::B;
+ let e2 = Flags::A | Flags::C;
+ e1.remove(e2);
+ assert_eq!(e1, Flags::B);
+
+ let mut e3 = AnotherSetOfFlags::ANOTHER_FLAG;
+ e3.remove(AnotherSetOfFlags::ANOTHER_FLAG);
+ assert_eq!(e3, AnotherSetOfFlags::empty());
+ }
+
+ #[test]
+ fn test_operators() {
+ let e1 = Flags::A | Flags::C;
+ let e2 = Flags::B | Flags::C;
+ assert_eq!((e1 | e2), Flags::ABC); // union
+ assert_eq!((e1 & e2), Flags::C); // intersection
+ assert_eq!((e1 - e2), Flags::A); // set difference
+ assert_eq!(!e2, Flags::A); // set complement
+ assert_eq!(e1 ^ e2, Flags::A | Flags::B); // toggle
+ let mut e3 = e1;
+ e3.toggle(e2);
+ assert_eq!(e3, Flags::A | Flags::B);
+
+ let mut m4 = AnotherSetOfFlags::empty();
+ m4.toggle(AnotherSetOfFlags::empty());
+ assert_eq!(m4, AnotherSetOfFlags::empty());
+ }
+
+ #[test]
+ fn test_operators_unchecked() {
+ let extra = unsafe { Flags::from_bits_unchecked(0b1000) };
+ let e1 = Flags::A | Flags::C | extra;
+ let e2 = Flags::B | Flags::C;
+ assert_eq!((e1 | e2), (Flags::ABC | extra)); // union
+ assert_eq!((e1 & e2), Flags::C); // intersection
+ assert_eq!((e1 - e2), (Flags::A | extra)); // set difference
+ assert_eq!(!e2, Flags::A); // set complement
+ assert_eq!(!e1, Flags::B); // set complement
+ assert_eq!(e1 ^ e2, Flags::A | Flags::B | extra); // toggle
+ let mut e3 = e1;
+ e3.toggle(e2);
+ assert_eq!(e3, Flags::A | Flags::B | extra);
+ }
+
+ #[test]
+ fn test_set_ops_basic() {
+ let ab = Flags::A.union(Flags::B);
+ let ac = Flags::A.union(Flags::C);
+ let bc = Flags::B.union(Flags::C);
+ assert_eq!(ab.bits, 0b011);
+ assert_eq!(bc.bits, 0b110);
+ assert_eq!(ac.bits, 0b101);
+
+ assert_eq!(ab, Flags::B.union(Flags::A));
+ assert_eq!(ac, Flags::C.union(Flags::A));
+ assert_eq!(bc, Flags::C.union(Flags::B));
+
+ assert_eq!(ac, Flags::A | Flags::C);
+ assert_eq!(bc, Flags::B | Flags::C);
+ assert_eq!(ab.union(bc), Flags::ABC);
+
+ assert_eq!(ac, Flags::A | Flags::C);
+ assert_eq!(bc, Flags::B | Flags::C);
+
+ assert_eq!(ac.union(bc), ac | bc);
+ assert_eq!(ac.union(bc), Flags::ABC);
+ assert_eq!(bc.union(ac), Flags::ABC);
+
+ assert_eq!(ac.intersection(bc), ac & bc);
+ assert_eq!(ac.intersection(bc), Flags::C);
+ assert_eq!(bc.intersection(ac), Flags::C);
+
+ assert_eq!(ac.difference(bc), ac - bc);
+ assert_eq!(bc.difference(ac), bc - ac);
+ assert_eq!(ac.difference(bc), Flags::A);
+ assert_eq!(bc.difference(ac), Flags::B);
+
+ assert_eq!(bc.complement(), !bc);
+ assert_eq!(bc.complement(), Flags::A);
+ assert_eq!(ac.symmetric_difference(bc), Flags::A.union(Flags::B));
+ assert_eq!(bc.symmetric_difference(ac), Flags::A.union(Flags::B));
+ }
+
+ #[test]
+ fn test_set_ops_const() {
+ // These just test that these compile and don't cause use-site panics
+ // (would be possible if we had some sort of UB)
+ const INTERSECT: Flags = Flags::all().intersection(Flags::C);
+ const UNION: Flags = Flags::A.union(Flags::C);
+ const DIFFERENCE: Flags = Flags::all().difference(Flags::A);
+ const COMPLEMENT: Flags = Flags::C.complement();
+ const SYM_DIFFERENCE: Flags = UNION.symmetric_difference(DIFFERENCE);
+ assert_eq!(INTERSECT, Flags::C);
+ assert_eq!(UNION, Flags::A | Flags::C);
+ assert_eq!(DIFFERENCE, Flags::all() - Flags::A);
+ assert_eq!(COMPLEMENT, !Flags::C);
+ assert_eq!(SYM_DIFFERENCE, (Flags::A | Flags::C) ^ (Flags::all() - Flags::A));
+ }
+
+ #[test]
+ fn test_set_ops_unchecked() {
+ let extra = unsafe { Flags::from_bits_unchecked(0b1000) };
+ let e1 = Flags::A.union(Flags::C).union(extra);
+ let e2 = Flags::B.union(Flags::C);
+ assert_eq!(e1.bits, 0b1101);
+ assert_eq!(e1.union(e2), (Flags::ABC | extra));
+ assert_eq!(e1.intersection(e2), Flags::C);
+ assert_eq!(e1.difference(e2), Flags::A | extra);
+ assert_eq!(e2.difference(e1), Flags::B);
+ assert_eq!(e2.complement(), Flags::A);
+ assert_eq!(e1.complement(), Flags::B);
+ assert_eq!(e1.symmetric_difference(e2), Flags::A | Flags::B | extra); // toggle
+ }
+
+ #[test]
+ fn test_set_ops_exhaustive() {
+ // Define a flag that contains gaps to help exercise edge-cases,
+ // especially around "unknown" flags (e.g. ones outside of `all()`
+ // `from_bits_unchecked`).
+ // - when lhs and rhs both have different sets of unknown flags.
+ // - unknown flags at both ends, and in the middle
+ // - cases with "gaps".
+ bitflags! {
+ struct Test: u16 {
+ // Intentionally no `A`
+ const B = 0b000000010;
+ // Intentionally no `C`
+ const D = 0b000001000;
+ const E = 0b000010000;
+ const F = 0b000100000;
+ const G = 0b001000000;
+ // Intentionally no `H`
+ const I = 0b100000000;
+ }
+ }
+ let iter_test_flags =
+ || (0..=0b111_1111_1111).map(|bits| unsafe { Test::from_bits_unchecked(bits) });
+
+ for a in iter_test_flags() {
+ assert_eq!(
+ a.complement(),
+ Test::from_bits_truncate(!a.bits),
+ "wrong result: !({:?})",
+ a,
+ );
+ assert_eq!(a.complement(), !a, "named != op: !({:?})", a);
+ for b in iter_test_flags() {
+ // Check that the named operations produce the expected bitwise
+ // values.
+ assert_eq!(
+ a.union(b).bits,
+ a.bits | b.bits,
+ "wrong result: `{:?}` | `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(
+ a.intersection(b).bits,
+ a.bits & b.bits,
+ "wrong result: `{:?}` & `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(
+ a.symmetric_difference(b).bits,
+ a.bits ^ b.bits,
+ "wrong result: `{:?}` ^ `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(
+ a.difference(b).bits,
+ a.bits & !b.bits,
+ "wrong result: `{:?}` - `{:?}`",
+ a,
+ b,
+ );
+ // Note: Difference is checked as both `a - b` and `b - a`
+ assert_eq!(
+ b.difference(a).bits,
+ b.bits & !a.bits,
+ "wrong result: `{:?}` - `{:?}`",
+ b,
+ a,
+ );
+ // Check that the named set operations are equivalent to the
+ // bitwise equivalents
+ assert_eq!(a.union(b), a | b, "named != op: `{:?}` | `{:?}`", a, b,);
+ assert_eq!(
+ a.intersection(b),
+ a & b,
+ "named != op: `{:?}` & `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(
+ a.symmetric_difference(b),
+ a ^ b,
+ "named != op: `{:?}` ^ `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(a.difference(b), a - b, "named != op: `{:?}` - `{:?}`", a, b,);
+ // Note: Difference is checked as both `a - b` and `b - a`
+ assert_eq!(b.difference(a), b - a, "named != op: `{:?}` - `{:?}`", b, a,);
+ // Verify that the operations which should be symmetric are
+ // actually symmetric.
+ assert_eq!(a.union(b), b.union(a), "asymmetry: `{:?}` | `{:?}`", a, b,);
+ assert_eq!(
+ a.intersection(b),
+ b.intersection(a),
+ "asymmetry: `{:?}` & `{:?}`",
+ a,
+ b,
+ );
+ assert_eq!(
+ a.symmetric_difference(b),
+ b.symmetric_difference(a),
+ "asymmetry: `{:?}` ^ `{:?}`",
+ a,
+ b,
+ );
+ }
+ }
+ }
+
+ #[test]
+ fn test_set() {
+ let mut e1 = Flags::A | Flags::C;
+ e1.set(Flags::B, true);
+ e1.set(Flags::C, false);
+
+ assert_eq!(e1, Flags::A | Flags::B);
+ }
+
+ #[test]
+ fn test_assignment_operators() {
+ let mut m1 = Flags::empty();
+ let e1 = Flags::A | Flags::C;
+ // union
+ m1 |= Flags::A;
+ assert_eq!(m1, Flags::A);
+ // intersection
+ m1 &= e1;
+ assert_eq!(m1, Flags::A);
+ // set difference
+ m1 -= m1;
+ assert_eq!(m1, Flags::empty());
+ // toggle
+ m1 ^= e1;
+ assert_eq!(m1, e1);
+ }
+
+ #[test]
+ fn test_const_fn() {
+ const _M1: Flags = Flags::empty();
+
+ const M2: Flags = Flags::A;
+ assert_eq!(M2, Flags::A);
+
+ const M3: Flags = Flags::C;
+ assert_eq!(M3, Flags::C);
+ }
+
+ #[test]
+ fn test_extend() {
+ let mut flags;
+
+ flags = Flags::empty();
+ flags.extend([].iter().cloned());
+ assert_eq!(flags, Flags::empty());
+
+ flags = Flags::empty();
+ flags.extend([Flags::A, Flags::B].iter().cloned());
+ assert_eq!(flags, Flags::A | Flags::B);
+
+ flags = Flags::A;
+ flags.extend([Flags::A, Flags::B].iter().cloned());
+ assert_eq!(flags, Flags::A | Flags::B);
+
+ flags = Flags::B;
+ flags.extend([Flags::A, Flags::ABC].iter().cloned());
+ assert_eq!(flags, Flags::ABC);
+ }
+
+ #[test]
+ fn test_from_iterator() {
+ assert_eq!([].iter().cloned().collect::<Flags>(), Flags::empty());
+ assert_eq!(
+ [Flags::A, Flags::B].iter().cloned().collect::<Flags>(),
+ Flags::A | Flags::B
+ );
+ assert_eq!(
+ [Flags::A, Flags::ABC].iter().cloned().collect::<Flags>(),
+ Flags::ABC
+ );
+ }
+
+ #[test]
+ fn test_lt() {
+ let mut a = Flags::empty();
+ let mut b = Flags::empty();
+
+ assert!(!(a < b) && !(b < a));
+ b = Flags::B;
+ assert!(a < b);
+ a = Flags::C;
+ assert!(!(a < b) && b < a);
+ b = Flags::C | Flags::B;
+ assert!(a < b);
+ }
+
+ #[test]
+ fn test_ord() {
+ let mut a = Flags::empty();
+ let mut b = Flags::empty();
+
+ assert!(a <= b && a >= b);
+ a = Flags::A;
+ assert!(a > b && a >= b);
+ assert!(b < a && b <= a);
+ b = Flags::B;
+ assert!(b > a && b >= a);
+ assert!(a < b && a <= b);
+ }
+
+ fn hash<T: Hash>(t: &T) -> u64 {
+ let mut s = DefaultHasher::new();
+ t.hash(&mut s);
+ s.finish()
+ }
+
+ #[test]
+ fn test_hash() {
+ let mut x = Flags::empty();
+ let mut y = Flags::empty();
+ assert_eq!(hash(&x), hash(&y));
+ x = Flags::all();
+ y = Flags::ABC;
+ assert_eq!(hash(&x), hash(&y));
+ }
+
+ #[test]
+ fn test_default() {
+ assert_eq!(Flags::empty(), Flags::default());
+ }
+
+ #[test]
+ fn test_debug() {
+ assert_eq!(format!("{:?}", Flags::A | Flags::B), "A | B");
+ assert_eq!(format!("{:?}", Flags::empty()), "(empty)");
+ assert_eq!(format!("{:?}", Flags::ABC), "A | B | C | ABC");
+ let extra = unsafe { Flags::from_bits_unchecked(0xb8) };
+ assert_eq!(format!("{:?}", extra), "0xb8");
+ assert_eq!(format!("{:?}", Flags::A | extra), "A | 0xb8");
+
+ assert_eq!(
+ format!("{:?}", Flags::ABC | extra),
+ "A | B | C | ABC | 0xb8"
+ );
+
+ assert_eq!(format!("{:?}", EmptyFlags::empty()), "(empty)");
+ }
+
+ #[test]
+ fn test_binary() {
+ assert_eq!(format!("{:b}", Flags::ABC), "111");
+ assert_eq!(format!("{:#b}", Flags::ABC), "0b111");
+ let extra = unsafe { Flags::from_bits_unchecked(0b1010000) };
+ assert_eq!(format!("{:b}", Flags::ABC | extra), "1010111");
+ assert_eq!(format!("{:#b}", Flags::ABC | extra), "0b1010111");
+ }
+
+ #[test]
+ fn test_octal() {
+ assert_eq!(format!("{:o}", LongFlags::LONG_A), "177777");
+ assert_eq!(format!("{:#o}", LongFlags::LONG_A), "0o177777");
+ let extra = unsafe { LongFlags::from_bits_unchecked(0o5000000) };
+ assert_eq!(format!("{:o}", LongFlags::LONG_A | extra), "5177777");
+ assert_eq!(format!("{:#o}", LongFlags::LONG_A | extra), "0o5177777");
+ }
+
+ #[test]
+ fn test_lowerhex() {
+ assert_eq!(format!("{:x}", LongFlags::LONG_A), "ffff");
+ assert_eq!(format!("{:#x}", LongFlags::LONG_A), "0xffff");
+ let extra = unsafe { LongFlags::from_bits_unchecked(0xe00000) };
+ assert_eq!(format!("{:x}", LongFlags::LONG_A | extra), "e0ffff");
+ assert_eq!(format!("{:#x}", LongFlags::LONG_A | extra), "0xe0ffff");
+ }
+
+ #[test]
+ fn test_upperhex() {
+ assert_eq!(format!("{:X}", LongFlags::LONG_A), "FFFF");
+ assert_eq!(format!("{:#X}", LongFlags::LONG_A), "0xFFFF");
+ let extra = unsafe { LongFlags::from_bits_unchecked(0xe00000) };
+ assert_eq!(format!("{:X}", LongFlags::LONG_A | extra), "E0FFFF");
+ assert_eq!(format!("{:#X}", LongFlags::LONG_A | extra), "0xE0FFFF");
+ }
+
+ mod submodule {
+ bitflags! {
+ pub struct PublicFlags: i8 {
+ const X = 0;
+ }
+
+ struct PrivateFlags: i8 {
+ const Y = 0;
+ }
+ }
+
+ #[test]
+ fn test_private() {
+ let _ = PrivateFlags::Y;
+ }
+ }
+
+ #[test]
+ fn test_public() {
+ let _ = submodule::PublicFlags::X;
+ }
+
+ mod t1 {
+ mod foo {
+ pub type Bar = i32;
+ }
+
+ bitflags! {
+ /// baz
+ struct Flags: foo::Bar {
+ const A = 0b00000001;
+ #[cfg(foo)]
+ const B = 0b00000010;
+ #[cfg(foo)]
+ const C = 0b00000010;
+ }
+ }
+ }
+
+ #[test]
+ fn test_in_function() {
+ bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ #[cfg(any())] // false
+ const B = 2;
+ }
+ }
+ assert_eq!(Flags::all(), Flags::A);
+ assert_eq!(format!("{:?}", Flags::A), "A");
+ }
+
+ #[test]
+ fn test_deprecated() {
+ bitflags! {
+ pub struct TestFlags: u32 {
+ #[deprecated(note = "Use something else.")]
+ const ONE = 1;
+ }
+ }
+ }
+
+ #[test]
+ fn test_pub_crate() {
+ mod module {
+ bitflags! {
+ pub (crate) struct Test: u8 {
+ const FOO = 1;
+ }
+ }
+ }
+
+ assert_eq!(module::Test::FOO.bits(), 1);
+ }
+
+ #[test]
+ fn test_pub_in_module() {
+ mod module {
+ mod submodule {
+ bitflags! {
+ // `pub (in super)` means only the module `module` will
+ // be able to access this.
+ pub (in super) struct Test: u8 {
+ const FOO = 1;
+ }
+ }
+ }
+
+ mod test {
+ // Note: due to `pub (in super)`,
+ // this cannot be accessed directly by the testing code.
+ pub(super) fn value() -> u8 {
+ super::submodule::Test::FOO.bits()
+ }
+ }
+
+ pub fn value() -> u8 {
+ test::value()
+ }
+ }
+
+ assert_eq!(module::value(), 1)
+ }
+
+ #[test]
+ fn test_zero_value_flags() {
+ bitflags! {
+ struct Flags: u32 {
+ const NONE = 0b0;
+ const SOME = 0b1;
+ }
+ }
+
+ assert!(Flags::empty().contains(Flags::NONE));
+ assert!(Flags::SOME.contains(Flags::NONE));
+ assert!(Flags::NONE.is_empty());
+
+ assert_eq!(format!("{:?}", Flags::empty()), "NONE");
+ assert_eq!(format!("{:?}", Flags::SOME), "SOME");
+ }
+
+ #[test]
+ fn test_empty_bitflags() {
+ bitflags! {}
+ }
+
+ #[test]
+ fn test_u128_bitflags() {
+ bitflags! {
+ struct Flags128: u128 {
+ const A = 0x0000_0000_0000_0000_0000_0000_0000_0001;
+ const B = 0x0000_0000_0000_1000_0000_0000_0000_0000;
+ const C = 0x8000_0000_0000_0000_0000_0000_0000_0000;
+ const ABC = Self::A.bits | Self::B.bits | Self::C.bits;
+ }
+ }
+
+ assert_eq!(Flags128::ABC, Flags128::A | Flags128::B | Flags128::C);
+ assert_eq!(Flags128::A.bits, 0x0000_0000_0000_0000_0000_0000_0000_0001);
+ assert_eq!(Flags128::B.bits, 0x0000_0000_0000_1000_0000_0000_0000_0000);
+ assert_eq!(Flags128::C.bits, 0x8000_0000_0000_0000_0000_0000_0000_0000);
+ assert_eq!(
+ Flags128::ABC.bits,
+ 0x8000_0000_0000_1000_0000_0000_0000_0001
+ );
+ assert_eq!(format!("{:?}", Flags128::A), "A");
+ assert_eq!(format!("{:?}", Flags128::B), "B");
+ assert_eq!(format!("{:?}", Flags128::C), "C");
+ assert_eq!(format!("{:?}", Flags128::ABC), "A | B | C | ABC");
+ }
+
+ #[test]
+ fn test_serde_bitflags_serialize() {
+ let flags = SerdeFlags::A | SerdeFlags::B;
+
+ let serialized = serde_json::to_string(&flags).unwrap();
+
+ assert_eq!(serialized, r#"{"bits":3}"#);
+ }
+
+ #[test]
+ fn test_serde_bitflags_deserialize() {
+ let deserialized: SerdeFlags = serde_json::from_str(r#"{"bits":12}"#).unwrap();
+
+ let expected = SerdeFlags::C | SerdeFlags::D;
+
+ assert_eq!(deserialized.bits, expected.bits);
+ }
+
+ #[test]
+ fn test_serde_bitflags_roundtrip() {
+ let flags = SerdeFlags::A | SerdeFlags::B;
+
+ let deserialized: SerdeFlags = serde_json::from_str(&serde_json::to_string(&flags).unwrap()).unwrap();
+
+ assert_eq!(deserialized.bits, flags.bits);
+ }
+
+ bitflags! {
+ #[derive(serde::Serialize, serde::Deserialize)]
+ struct SerdeFlags: u32 {
+ const A = 1;
+ const B = 2;
+ const C = 4;
+ const D = 8;
+ }
+ }
+}
diff --git a/extra/bitflags-1.3.2/tests/basic.rs b/extra/bitflags-1.3.2/tests/basic.rs
new file mode 100644
index 000000000..73a52bec5
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/basic.rs
@@ -0,0 +1,20 @@
+#![no_std]
+
+use bitflags::bitflags;
+
+bitflags! {
+ /// baz
+ struct Flags: u32 {
+ const A = 0b00000001;
+ #[doc = "bar"]
+ const B = 0b00000010;
+ const C = 0b00000100;
+ #[doc = "foo"]
+ const ABC = Flags::A.bits | Flags::B.bits | Flags::C.bits;
+ }
+}
+
+#[test]
+fn basic() {
+ assert_eq!(Flags::ABC, Flags::A | Flags::B | Flags::C);
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.rs b/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.rs
new file mode 100644
index 000000000..38f4822f5
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.rs
@@ -0,0 +1,10 @@
+use bitflags::bitflags;
+
+bitflags! {
+ #[derive(Clone, Copy)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.stderr.beta
new file mode 100644
index 000000000..0c13aa502
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/impls/copy.stderr.beta
@@ -0,0 +1,27 @@
+error[E0119]: conflicting implementations of trait `std::clone::Clone` for type `Flags`
+ --> $DIR/copy.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(Clone, Copy)]
+ | | ----- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0119]: conflicting implementations of trait `std::marker::Copy` for type `Flags`
+ --> $DIR/copy.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(Clone, Copy)]
+ | | ---- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.rs b/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.rs
new file mode 100644
index 000000000..4abbd630c
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.rs
@@ -0,0 +1,10 @@
+use bitflags::bitflags;
+
+bitflags! {
+ #[derive(PartialEq, Eq)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.stderr.beta
new file mode 100644
index 000000000..8a1a3b410
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/impls/eq.stderr.beta
@@ -0,0 +1,55 @@
+error[E0119]: conflicting implementations of trait `std::cmp::PartialEq` for type `Flags`
+ --> $DIR/eq.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(PartialEq, Eq)]
+ | | --------- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0119]: conflicting implementations of trait `std::cmp::Eq` for type `Flags`
+ --> $DIR/eq.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(PartialEq, Eq)]
+ | | -- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0119]: conflicting implementations of trait `std::marker::StructuralPartialEq` for type `Flags`
+ --> $DIR/eq.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(PartialEq, Eq)]
+ | | --------- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0119]: conflicting implementations of trait `std::marker::StructuralEq` for type `Flags`
+ --> $DIR/eq.rs:3:1
+ |
+3 | / bitflags! {
+4 | | #[derive(PartialEq, Eq)]
+ | | -- first implementation here
+5 | | struct Flags: u32 {
+6 | | const A = 0b00000001;
+7 | | }
+8 | | }
+ | |_^ conflicting implementation for `Flags`
+ |
+ = note: this error originates in the derive macro `Eq` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.rs b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.rs
new file mode 100644
index 000000000..c2856b108
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.rs
@@ -0,0 +1,123 @@
+use std::{
+ fmt::{
+ self,
+ Debug,
+ Display,
+ LowerHex,
+ UpperHex,
+ Octal,
+ Binary,
+ },
+ ops::{
+ BitAnd,
+ BitOr,
+ BitXor,
+ BitAndAssign,
+ BitOrAssign,
+ BitXorAssign,
+ Not,
+ },
+};
+
+use bitflags::bitflags;
+
+// Ideally we'd actually want this to work, but currently need something like `num`'s `Zero`
+// With some design work it could be made possible
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct MyInt(u8);
+
+impl BitAnd for MyInt {
+ type Output = Self;
+
+ fn bitand(self, other: Self) -> Self {
+ MyInt(self.0 & other.0)
+ }
+}
+
+impl BitOr for MyInt {
+ type Output = Self;
+
+ fn bitor(self, other: Self) -> Self {
+ MyInt(self.0 | other.0)
+ }
+}
+
+impl BitXor for MyInt {
+ type Output = Self;
+
+ fn bitxor(self, other: Self) -> Self {
+ MyInt(self.0 ^ other.0)
+ }
+}
+
+impl BitAndAssign for MyInt {
+ fn bitand_assign(&mut self, other: Self) {
+ self.0 &= other.0
+ }
+}
+
+impl BitOrAssign for MyInt {
+ fn bitor_assign(&mut self, other: Self) {
+ self.0 |= other.0
+ }
+}
+
+impl BitXorAssign for MyInt {
+ fn bitxor_assign(&mut self, other: Self) {
+ self.0 ^= other.0
+ }
+}
+
+impl Debug for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Debug::fmt(&self.0, f)
+ }
+}
+
+impl Display for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.0, f)
+ }
+}
+
+impl LowerHex for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ LowerHex::fmt(&self.0, f)
+ }
+}
+
+impl UpperHex for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ UpperHex::fmt(&self.0, f)
+ }
+}
+
+impl Octal for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Octal::fmt(&self.0, f)
+ }
+}
+
+impl Binary for MyInt {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Binary::fmt(&self.0, f)
+ }
+}
+
+impl Not for MyInt {
+ type Output = MyInt;
+
+ fn not(self) -> Self {
+ MyInt(!self.0)
+ }
+}
+
+bitflags! {
+ struct Flags128: MyInt {
+ const A = MyInt(0b0000_0001u8);
+ const B = MyInt(0b0000_0010u8);
+ const C = MyInt(0b0000_0100u8);
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.stderr.beta
new file mode 100644
index 000000000..1f0fb5cf7
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_defined.stderr.beta
@@ -0,0 +1,27 @@
+error[E0308]: mismatched types
+ --> $DIR/all_defined.rs:115:1
+ |
+115 | / bitflags! {
+116 | | struct Flags128: MyInt {
+117 | | const A = MyInt(0b0000_0001u8);
+118 | | const B = MyInt(0b0000_0010u8);
+119 | | const C = MyInt(0b0000_0100u8);
+120 | | }
+121 | | }
+ | |_^ expected struct `MyInt`, found integer
+ |
+ = note: this error originates in the macro `__impl_all_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0308]: mismatched types
+ --> $DIR/all_defined.rs:115:1
+ |
+115 | / bitflags! {
+116 | | struct Flags128: MyInt {
+117 | | const A = MyInt(0b0000_0001u8);
+118 | | const B = MyInt(0b0000_0010u8);
+119 | | const C = MyInt(0b0000_0100u8);
+120 | | }
+121 | | }
+ | |_^ expected struct `MyInt`, found integer
+ |
+ = note: this error originates in the macro `__impl_bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.rs b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.rs
new file mode 100644
index 000000000..fff6b2cc1
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.rs
@@ -0,0 +1,13 @@
+use bitflags::bitflags;
+
+struct MyInt(u8);
+
+bitflags! {
+ struct Flags128: MyInt {
+ const A = MyInt(0b0000_0001);
+ const B = MyInt(0b0000_0010);
+ const C = MyInt(0b0000_0100);
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.stderr.beta
new file mode 100644
index 000000000..ee95f8365
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/non_integer_base/all_missing.stderr.beta
@@ -0,0 +1,13 @@
+error[E0204]: the trait `Copy` may not be implemented for this type
+ --> $DIR/all_missing.rs:5:1
+ |
+5 | / bitflags! {
+6 | | struct Flags128: MyInt {
+7 | | const A = MyInt(0b0000_0001);
+8 | | const B = MyInt(0b0000_0010);
+9 | | const C = MyInt(0b0000_0100);
+10 | | }
+11 | | }
+ | |_^ this field does not implement `Copy`
+ |
+ = note: this error originates in the derive macro `Copy` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.rs b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.rs
new file mode 100644
index 000000000..a6a3912ae
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.rs
@@ -0,0 +1,13 @@
+mod example {
+ use bitflags::bitflags;
+
+ bitflags! {
+ pub struct Flags1: u32 {
+ const FLAG_A = 0b00000001;
+ }
+ }
+}
+
+fn main() {
+ let flag1 = example::Flags1::FLAG_A.bits;
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.stderr.beta
new file mode 100644
index 000000000..58a046601
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_field.stderr.beta
@@ -0,0 +1,10 @@
+error[E0616]: field `bits` of struct `Flags1` is private
+ --> $DIR/private_field.rs:12:41
+ |
+12 | let flag1 = example::Flags1::FLAG_A.bits;
+ | ^^^^ private field
+ |
+help: a method `bits` also exists, call it with parentheses
+ |
+12 | let flag1 = example::Flags1::FLAG_A.bits();
+ | ^^
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.rs b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.rs
new file mode 100644
index 000000000..85a5b1863
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.rs
@@ -0,0 +1,18 @@
+mod example {
+ use bitflags::bitflags;
+
+ bitflags! {
+ pub struct Flags1: u32 {
+ const FLAG_A = 0b00000001;
+ }
+
+ struct Flags2: u32 {
+ const FLAG_B = 0b00000010;
+ }
+ }
+}
+
+fn main() {
+ let flag1 = example::Flags1::FLAG_A;
+ let flag2 = example::Flags2::FLAG_B;
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.stderr.beta
new file mode 100644
index 000000000..d23f83209
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/private_flags.stderr.beta
@@ -0,0 +1,18 @@
+error[E0603]: struct `Flags2` is private
+ --> $DIR/private_flags.rs:17:26
+ |
+17 | let flag2 = example::Flags2::FLAG_B;
+ | ^^^^^^ private struct
+ |
+note: the struct `Flags2` is defined here
+ --> $DIR/private_flags.rs:4:5
+ |
+4 | / bitflags! {
+5 | | pub struct Flags1: u32 {
+6 | | const FLAG_A = 0b00000001;
+7 | | }
+... |
+11 | | }
+12 | | }
+ | |_____^
+ = note: this error originates in the macro `bitflags` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.rs b/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.rs
new file mode 100644
index 000000000..b90f0ce92
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.rs
@@ -0,0 +1,9 @@
+use bitflags::bitflags;
+
+bitflags! {
+ pub struct Flags1: u32 {
+ pub const FLAG_A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.stderr.beta b/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.stderr.beta
new file mode 100644
index 000000000..b01122c7a
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-fail/visibility/pub_const.stderr.beta
@@ -0,0 +1,5 @@
+error: no rules expected the token `pub`
+ --> $DIR/pub_const.rs:5:9
+ |
+5 | pub const FLAG_A = 0b00000001;
+ | ^^^ no rules expected this token in macro call
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/impls/convert.rs b/extra/bitflags-1.3.2/tests/compile-pass/impls/convert.rs
new file mode 100644
index 000000000..1f02982a8
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/impls/convert.rs
@@ -0,0 +1,17 @@
+use bitflags::bitflags;
+
+bitflags! {
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+impl From<u32> for Flags {
+ fn from(v: u32) -> Flags {
+ Flags::from_bits_truncate(v)
+ }
+}
+
+fn main() {
+
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/impls/default.rs b/extra/bitflags-1.3.2/tests/compile-pass/impls/default.rs
new file mode 100644
index 000000000..a97b6536f
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/impls/default.rs
@@ -0,0 +1,10 @@
+use bitflags::bitflags;
+
+bitflags! {
+ #[derive(Default)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/impls/inherent_methods.rs b/extra/bitflags-1.3.2/tests/compile-pass/impls/inherent_methods.rs
new file mode 100644
index 000000000..3052c460e
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/impls/inherent_methods.rs
@@ -0,0 +1,15 @@
+use bitflags::bitflags;
+
+bitflags! {
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+impl Flags {
+ pub fn new() -> Flags {
+ Flags::A
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/redefinition/core.rs b/extra/bitflags-1.3.2/tests/compile-pass/redefinition/core.rs
new file mode 100644
index 000000000..475492159
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/redefinition/core.rs
@@ -0,0 +1,14 @@
+use bitflags::bitflags;
+
+// Checks for possible errors caused by overriding names used by `bitflags!` internally.
+
+mod core {}
+mod _core {}
+
+bitflags! {
+ struct Test: u8 {
+ const A = 1;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/redefinition/stringify.rs b/extra/bitflags-1.3.2/tests/compile-pass/redefinition/stringify.rs
new file mode 100644
index 000000000..b04f2f6a4
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/redefinition/stringify.rs
@@ -0,0 +1,19 @@
+use bitflags::bitflags;
+
+// Checks for possible errors caused by overriding names used by `bitflags!` internally.
+
+#[allow(unused_macros)]
+macro_rules! stringify {
+ ($($t:tt)*) => { "..." };
+}
+
+bitflags! {
+ struct Test: u8 {
+ const A = 1;
+ }
+}
+
+fn main() {
+ // Just make sure we don't call the redefined `stringify` macro
+ assert_eq!(format!("{:?}", Test::A), "A");
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/repr/c.rs b/extra/bitflags-1.3.2/tests/compile-pass/repr/c.rs
new file mode 100644
index 000000000..6feba36ed
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/repr/c.rs
@@ -0,0 +1,10 @@
+use bitflags::bitflags;
+
+bitflags! {
+ #[repr(C)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/repr/transparent.rs b/extra/bitflags-1.3.2/tests/compile-pass/repr/transparent.rs
new file mode 100644
index 000000000..e38db4dd1
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/repr/transparent.rs
@@ -0,0 +1,10 @@
+use bitflags::bitflags;
+
+bitflags! {
+ #[repr(transparent)]
+ struct Flags: u32 {
+ const A = 0b00000001;
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/visibility/bits_field.rs b/extra/bitflags-1.3.2/tests/compile-pass/visibility/bits_field.rs
new file mode 100644
index 000000000..33a7967e6
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/visibility/bits_field.rs
@@ -0,0 +1,11 @@
+use bitflags::bitflags;
+
+bitflags! {
+ pub struct Flags1: u32 {
+ const FLAG_A = 0b00000001;
+ }
+}
+
+fn main() {
+ assert_eq!(0b00000001, Flags1::FLAG_A.bits);
+}
diff --git a/extra/bitflags-1.3.2/tests/compile-pass/visibility/pub_in.rs b/extra/bitflags-1.3.2/tests/compile-pass/visibility/pub_in.rs
new file mode 100644
index 000000000..c11050e3b
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile-pass/visibility/pub_in.rs
@@ -0,0 +1,19 @@
+mod a {
+ mod b {
+ use bitflags::bitflags;
+
+ bitflags! {
+ pub(in crate::a) struct Flags: u32 {
+ const FLAG_A = 0b00000001;
+ }
+ }
+ }
+
+ pub fn flags() -> u32 {
+ b::Flags::FLAG_A.bits()
+ }
+}
+
+fn main() {
+ assert_eq!(0b00000001, a::flags());
+}
diff --git a/extra/bitflags-1.3.2/tests/compile.rs b/extra/bitflags-1.3.2/tests/compile.rs
new file mode 100644
index 000000000..ed02d01e9
--- /dev/null
+++ b/extra/bitflags-1.3.2/tests/compile.rs
@@ -0,0 +1,63 @@
+use std::{
+ fs,
+ ffi::OsStr,
+ io,
+ path::Path,
+};
+
+use walkdir::WalkDir;
+
+#[test]
+fn fail() {
+ prepare_stderr_files("tests/compile-fail").unwrap();
+
+ let t = trybuild::TestCases::new();
+ t.compile_fail("tests/compile-fail/**/*.rs");
+}
+
+#[test]
+fn pass() {
+ let t = trybuild::TestCases::new();
+ t.pass("tests/compile-pass/**/*.rs");
+}
+
+// Compiler messages may change between versions
+// We don't want to have to track these too closely for `bitflags`, but
+// having some message to check makes sure user-facing errors are sensical.
+//
+// The approach we use is to run the test on all compilers, but only check stderr
+// output on beta (which is the next stable release). We do this by default ignoring
+// any `.stderr` files in the `compile-fail` directory, and copying `.stderr.beta` files
+// when we happen to be running on a beta compiler.
+fn prepare_stderr_files(path: impl AsRef<Path>) -> io::Result<()> {
+ for entry in WalkDir::new(path) {
+ let entry = entry?;
+
+ if entry.path().extension().and_then(OsStr::to_str) == Some("beta") {
+ let renamed = entry.path().with_extension("");
+
+ // Unconditionally remove a corresponding `.stderr` file for a `.stderr.beta`
+ // file if it exists. On `beta` compilers, we'll recreate it. On other compilers,
+ // we don't want to end up checking it anyways.
+ if renamed.exists() {
+ fs::remove_file(&renamed)?;
+ }
+
+ rename_beta_stderr(entry.path(), renamed)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[rustversion::beta]
+fn rename_beta_stderr(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
+ fs::copy(from, to)?;
+
+ Ok(())
+}
+
+#[rustversion::not(beta)]
+fn rename_beta_stderr(_: impl AsRef<Path>, _: impl AsRef<Path>) -> io::Result<()> {
+ Ok(())
+}
diff --git a/extra/bitflags/.cargo-checksum.json b/extra/bitflags/.cargo-checksum.json
new file mode 100644
index 000000000..2b5cc8ea5
--- /dev/null
+++ b/extra/bitflags/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{},"package":"327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"} \ No newline at end of file
diff --git a/extra/bitflags/CHANGELOG.md b/extra/bitflags/CHANGELOG.md
new file mode 100644
index 000000000..5081ed576
--- /dev/null
+++ b/extra/bitflags/CHANGELOG.md
@@ -0,0 +1,442 @@
+# 2.4.1
+
+## What's Changed
+* Allow some new pedantic clippy lints by @KodrAus in https://github.com/bitflags/bitflags/pull/380
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.4.0...2.4.1
+
+# 2.4.0
+
+## What's Changed
+* Remove html_root_url by @eldruin in https://github.com/bitflags/bitflags/pull/368
+* Support unnamed flags by @KodrAus in https://github.com/bitflags/bitflags/pull/371
+* Update smoke test to verify all Clippy and rustc lints by @MitMaro in https://github.com/bitflags/bitflags/pull/374
+* Specify the behavior of bitflags by @KodrAus in https://github.com/bitflags/bitflags/pull/369
+
+## New Contributors
+* @eldruin made their first contribution in https://github.com/bitflags/bitflags/pull/368
+* @MitMaro made their first contribution in https://github.com/bitflags/bitflags/pull/374
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.3.3...2.4.0
+
+# 2.3.3
+
+## Changes to `-=`
+
+The `-=` operator was incorrectly changed to truncate bits that didn't correspond to valid flags in `2.3.0`. This has
+been fixed up so it once again behaves the same as `-` and `difference`.
+
+## Changes to `!`
+
+The `!` operator previously called `Self::from_bits_truncate`, which would truncate any bits that only partially
+overlapped with a valid flag. It will now use `bits & Self::all().bits()`, so any bits that overlap any bits
+specified by any flag will be respected. This is unlikely to have any practical implications, but enables defining
+a flag like `const ALL = !0` as a way to signal that any bit pattern is a known set of flags.
+
+## Changes to formatting
+
+Zero-valued flags will never be printed. You'll either get `0x0` for empty flags using debug formatting, or the
+set of flags with zero-valued flags omitted for others.
+
+Composite flags will no longer be redundantly printed if there are extra bits to print at the end that don't correspond
+to a valid flag.
+
+## What's Changed
+* Fix up incorrect sub assign behavior and other cleanups by @KodrAus in https://github.com/bitflags/bitflags/pull/366
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.3.2...2.3.3
+
+# 2.3.2
+
+## What's Changed
+* [doc] [src/lib.rs] delete redundant path prefix by @OccupyMars2025 in https://github.com/bitflags/bitflags/pull/361
+
+## New Contributors
+* @OccupyMars2025 made their first contribution in https://github.com/bitflags/bitflags/pull/361
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.3.1...2.3.2
+
+# 2.3.1
+
+## What's Changed
+* Fix Self in flags value expressions by @KodrAus in https://github.com/bitflags/bitflags/pull/355
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.3.0...2.3.1
+
+# 2.3.0
+
+## What's Changed
+* Support ejecting flags types from the bitflags macro by @KodrAus in https://github.com/bitflags/bitflags/pull/351
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.2.1...2.3.0
+
+# 2.2.1
+
+## What's Changed
+* Refactor attribute filtering to apply per-flag by @KodrAus in https://github.com/bitflags/bitflags/pull/345
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.2.0...2.2.1
+
+# 2.2.0
+
+## What's Changed
+* Create SECURITY.md by @KodrAus in https://github.com/bitflags/bitflags/pull/338
+* add docs to describe the behavior of multi-bit flags by @nicholasbishop in https://github.com/bitflags/bitflags/pull/340
+* Add support for bytemuck by @KodrAus in https://github.com/bitflags/bitflags/pull/336
+* Add a top-level macro for filtering attributes by @KodrAus in https://github.com/bitflags/bitflags/pull/341
+
+## New Contributors
+* @nicholasbishop made their first contribution in https://github.com/bitflags/bitflags/pull/340
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.1.0...2.2.0
+
+# 2.1.0
+
+## What's Changed
+* Add docs for the internal Field0 and examples of formatting/parsing by @KodrAus in https://github.com/bitflags/bitflags/pull/328
+* Add support for arbitrary by @KodrAus in https://github.com/bitflags/bitflags/pull/324
+* Fix up missing docs for consts within consts by @KodrAus in https://github.com/bitflags/bitflags/pull/330
+* Ignore clippy lint in generated code by @Jake-Shadle in https://github.com/bitflags/bitflags/pull/331
+
+## New Contributors
+* @Jake-Shadle made their first contribution in https://github.com/bitflags/bitflags/pull/331
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.0.2...2.1.0
+
+# 2.0.2
+
+## What's Changed
+* Fix up missing isize and usize Bits impls by @KodrAus in https://github.com/bitflags/bitflags/pull/321
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.0.1...2.0.2
+
+# 2.0.1
+
+## What's Changed
+* Fix up some docs issues by @KodrAus in https://github.com/bitflags/bitflags/pull/309
+* Make empty_flag() const. by @tormeh in https://github.com/bitflags/bitflags/pull/313
+* Fix formatting of multi-bit flags with partial overlap by @KodrAus in https://github.com/bitflags/bitflags/pull/316
+
+## New Contributors
+* @tormeh made their first contribution in https://github.com/bitflags/bitflags/pull/313
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.0.0...2.0.1
+
+# 2.0.0
+
+## What's Changed
+* Fix a typo and call out MSRV bump by @KodrAus in https://github.com/bitflags/bitflags/pull/259
+* BitFlags trait by @arturoc in https://github.com/bitflags/bitflags/pull/220
+* Add a hidden trait to discourage manual impls of BitFlags by @KodrAus in https://github.com/bitflags/bitflags/pull/261
+* Sanitize `Ok` by @konsumlamm in https://github.com/bitflags/bitflags/pull/266
+* Fix bug in `Debug` implementation by @konsumlamm in https://github.com/bitflags/bitflags/pull/268
+* Fix a typo in the generated documentation by @wackbyte in https://github.com/bitflags/bitflags/pull/271
+* Use SPDX license format by @atouchet in https://github.com/bitflags/bitflags/pull/272
+* serde tests fail in CI by @arturoc in https://github.com/bitflags/bitflags/pull/277
+* Fix beta test output by @KodrAus in https://github.com/bitflags/bitflags/pull/279
+* Add example to the README.md file by @tiaanl in https://github.com/bitflags/bitflags/pull/270
+* Iterator over all the enabled options by @arturoc in https://github.com/bitflags/bitflags/pull/278
+* from_bits_(truncate) fail with composite flags by @arturoc in https://github.com/bitflags/bitflags/pull/276
+* Add more platform coverage to CI by @KodrAus in https://github.com/bitflags/bitflags/pull/280
+* rework the way cfgs are handled by @KodrAus in https://github.com/bitflags/bitflags/pull/281
+* Split generated code into two types by @KodrAus in https://github.com/bitflags/bitflags/pull/282
+* expose bitflags iters using nameable types by @KodrAus in https://github.com/bitflags/bitflags/pull/286
+* Support creating flags from their names by @KodrAus in https://github.com/bitflags/bitflags/pull/287
+* Update README.md by @KodrAus in https://github.com/bitflags/bitflags/pull/288
+* Prepare for 2.0.0-rc.1 release by @KodrAus in https://github.com/bitflags/bitflags/pull/289
+* Add missing "if" to contains doc-comment in traits.rs by @rusty-snake in https://github.com/bitflags/bitflags/pull/291
+* Forbid unsafe_code by @fintelia in https://github.com/bitflags/bitflags/pull/294
+* serde: enable no-std support by @nim65s in https://github.com/bitflags/bitflags/pull/296
+* Add a parser for flags formatted as bar-separated-values by @KodrAus in https://github.com/bitflags/bitflags/pull/297
+* Prepare for 2.0.0-rc.2 release by @KodrAus in https://github.com/bitflags/bitflags/pull/299
+* Use strip_prefix instead of starts_with + slice by @QuinnPainter in https://github.com/bitflags/bitflags/pull/301
+* Fix up some clippy lints by @KodrAus in https://github.com/bitflags/bitflags/pull/302
+* Prepare for 2.0.0-rc.3 release by @KodrAus in https://github.com/bitflags/bitflags/pull/303
+* feat: Add minimum permissions to rust.yml workflow by @gabibguti in https://github.com/bitflags/bitflags/pull/305
+
+## New Contributors
+* @wackbyte made their first contribution in https://github.com/bitflags/bitflags/pull/271
+* @atouchet made their first contribution in https://github.com/bitflags/bitflags/pull/272
+* @tiaanl made their first contribution in https://github.com/bitflags/bitflags/pull/270
+* @rusty-snake made their first contribution in https://github.com/bitflags/bitflags/pull/291
+* @fintelia made their first contribution in https://github.com/bitflags/bitflags/pull/294
+* @nim65s made their first contribution in https://github.com/bitflags/bitflags/pull/296
+* @QuinnPainter made their first contribution in https://github.com/bitflags/bitflags/pull/301
+* @gabibguti made their first contribution in https://github.com/bitflags/bitflags/pull/305
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/1.3.2...2.0.0
+
+# 2.0.0-rc.3
+
+## What's Changed
+* Use strip_prefix instead of starts_with + slice by @QuinnPainter in https://github.com/bitflags/bitflags/pull/301
+* Fix up some clippy lints by @KodrAus in https://github.com/bitflags/bitflags/pull/302
+
+## New Contributors
+* @QuinnPainter made their first contribution in https://github.com/bitflags/bitflags/pull/301
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.0.0-rc.2...2.0.0-rc.3
+
+# 2.0.0-rc.2
+
+## Changes to `serde` serialization
+
+**⚠️ NOTE ⚠️** This release changes the default serialization you'll get if you `#[derive(Serialize, Deserialize)]`
+on your generated flags types. It will now use a formatted string for human-readable formats and the underlying bits
+type for compact formats.
+
+To keep the old behavior, see the [`bitflags-serde-legacy`](https://github.com/KodrAus/bitflags-serde-legacy) library.
+
+## What's Changed
+
+* Add missing "if" to contains doc-comment in traits.rs by @rusty-snake in https://github.com/bitflags/bitflags/pull/291
+* Forbid unsafe_code by @fintelia in https://github.com/bitflags/bitflags/pull/294
+* serde: enable no-std support by @nim65s in https://github.com/bitflags/bitflags/pull/296
+* Add a parser for flags formatted as bar-separated-values by @KodrAus in https://github.com/bitflags/bitflags/pull/297
+
+## New Contributors
+* @rusty-snake made their first contribution in https://github.com/bitflags/bitflags/pull/291
+* @fintelia made their first contribution in https://github.com/bitflags/bitflags/pull/294
+* @nim65s made their first contribution in https://github.com/bitflags/bitflags/pull/296
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/2.0.0-rc.1...2.0.0-rc.2
+
+# 2.0.0-rc.1
+
+This is a big release including a few years worth of work on a new `BitFlags` trait, iteration, and better macro organization for future extensibility.
+
+## What's Changed
+* Fix a typo and call out MSRV bump by @KodrAus in https://github.com/bitflags/bitflags/pull/259
+* BitFlags trait by @arturoc in https://github.com/bitflags/bitflags/pull/220
+* Add a hidden trait to discourage manual impls of BitFlags by @KodrAus in https://github.com/bitflags/bitflags/pull/261
+* Sanitize `Ok` by @konsumlamm in https://github.com/bitflags/bitflags/pull/266
+* Fix bug in `Debug` implementation by @konsumlamm in https://github.com/bitflags/bitflags/pull/268
+* Fix a typo in the generated documentation by @wackbyte in https://github.com/bitflags/bitflags/pull/271
+* Use SPDX license format by @atouchet in https://github.com/bitflags/bitflags/pull/272
+* serde tests fail in CI by @arturoc in https://github.com/bitflags/bitflags/pull/277
+* Fix beta test output by @KodrAus in https://github.com/bitflags/bitflags/pull/279
+* Add example to the README.md file by @tiaanl in https://github.com/bitflags/bitflags/pull/270
+* Iterator over all the enabled options by @arturoc in https://github.com/bitflags/bitflags/pull/278
+* from_bits_(truncate) fail with composite flags by @arturoc in https://github.com/bitflags/bitflags/pull/276
+* Add more platform coverage to CI by @KodrAus in https://github.com/bitflags/bitflags/pull/280
+* rework the way cfgs are handled by @KodrAus in https://github.com/bitflags/bitflags/pull/281
+* Split generated code into two types by @KodrAus in https://github.com/bitflags/bitflags/pull/282
+* expose bitflags iters using nameable types by @KodrAus in https://github.com/bitflags/bitflags/pull/286
+* Support creating flags from their names by @KodrAus in https://github.com/bitflags/bitflags/pull/287
+* Update README.md by @KodrAus in https://github.com/bitflags/bitflags/pull/288
+
+## New Contributors
+* @wackbyte made their first contribution in https://github.com/bitflags/bitflags/pull/271
+* @atouchet made their first contribution in https://github.com/bitflags/bitflags/pull/272
+* @tiaanl made their first contribution in https://github.com/bitflags/bitflags/pull/270
+
+**Full Changelog**: https://github.com/bitflags/bitflags/compare/1.3.2...2.0.0-rc.1
+
+# 1.3.2
+
+- Allow `non_snake_case` in generated flags types ([#256])
+
+[#256]: https://github.com/bitflags/bitflags/pull/256
+
+# 1.3.1
+
+- Revert unconditional `#[repr(transparent)]` ([#252])
+
+[#252]: https://github.com/bitflags/bitflags/pull/252
+
+# 1.3.0 (yanked)
+
+**This release bumps the Minimum Supported Rust Version to `1.46.0`**
+
+- Add `#[repr(transparent)]` ([#187])
+
+- End `empty` doc comment with full stop ([#202])
+
+- Fix typo in crate root docs ([#206])
+
+- Document from_bits_unchecked unsafety ([#207])
+
+- Let `is_all` ignore extra bits ([#211])
+
+- Allows empty flag definition ([#225])
+
+- Making crate accessible from std ([#227])
+
+- Make `from_bits` a const fn ([#229])
+
+- Allow multiple bitflags structs in one macro invocation ([#235])
+
+- Add named functions to perform set operations ([#244])
+
+- Fix typos in method docs ([#245])
+
+- Modernization of the `bitflags` macro to take advantage of newer features and 2018 idioms ([#246])
+
+- Fix regression (in an unreleased feature) and simplify tests ([#247])
+
+- Use `Self` and fix bug when overriding `stringify!` ([#249])
+
+[#187]: https://github.com/bitflags/bitflags/pull/187
+[#202]: https://github.com/bitflags/bitflags/pull/202
+[#206]: https://github.com/bitflags/bitflags/pull/206
+[#207]: https://github.com/bitflags/bitflags/pull/207
+[#211]: https://github.com/bitflags/bitflags/pull/211
+[#225]: https://github.com/bitflags/bitflags/pull/225
+[#227]: https://github.com/bitflags/bitflags/pull/227
+[#229]: https://github.com/bitflags/bitflags/pull/229
+[#235]: https://github.com/bitflags/bitflags/pull/235
+[#244]: https://github.com/bitflags/bitflags/pull/244
+[#245]: https://github.com/bitflags/bitflags/pull/245
+[#246]: https://github.com/bitflags/bitflags/pull/246
+[#247]: https://github.com/bitflags/bitflags/pull/247
+[#249]: https://github.com/bitflags/bitflags/pull/249
+
+# 1.2.1
+
+- Remove extraneous `#[inline]` attributes ([#194])
+
+[#194]: https://github.com/bitflags/bitflags/pull/194
+
+# 1.2.0
+
+- Fix typo: {Lower, Upper}Exp - {Lower, Upper}Hex ([#183])
+
+- Add support for "unknown" bits ([#188])
+
+[#183]: https://github.com/rust-lang-nursery/bitflags/pull/183
+[#188]: https://github.com/rust-lang-nursery/bitflags/pull/188
+
+# 1.1.0
+
+This is a re-release of `1.0.5`, which was yanked due to a bug in the RLS.
+
+# 1.0.5
+
+- Use compiletest_rs flags supported by stable toolchain ([#171])
+
+- Put the user provided attributes first ([#173])
+
+- Make bitflags methods `const` on newer compilers ([#175])
+
+[#171]: https://github.com/rust-lang-nursery/bitflags/pull/171
+[#173]: https://github.com/rust-lang-nursery/bitflags/pull/173
+[#175]: https://github.com/rust-lang-nursery/bitflags/pull/175
+
+# 1.0.4
+
+- Support Rust 2018 style macro imports ([#165])
+
+ ```rust
+ use bitflags::bitflags;
+ ```
+
+[#165]: https://github.com/rust-lang-nursery/bitflags/pull/165
+
+# 1.0.3
+
+- Improve zero value flag handling and documentation ([#157])
+
+[#157]: https://github.com/rust-lang-nursery/bitflags/pull/157
+
+# 1.0.2
+
+- 30% improvement in compile time of bitflags crate ([#156])
+
+- Documentation improvements ([#153])
+
+- Implementation cleanup ([#149])
+
+[#156]: https://github.com/rust-lang-nursery/bitflags/pull/156
+[#153]: https://github.com/rust-lang-nursery/bitflags/pull/153
+[#149]: https://github.com/rust-lang-nursery/bitflags/pull/149
+
+# 1.0.1
+- Add support for `pub(restricted)` specifier on the bitflags struct ([#135])
+- Optimize performance of `all()` when called from a separate crate ([#136])
+
+[#135]: https://github.com/rust-lang-nursery/bitflags/pull/135
+[#136]: https://github.com/rust-lang-nursery/bitflags/pull/136
+
+# 1.0.0
+- **[breaking change]** Macro now generates [associated constants](https://doc.rust-lang.org/reference/items.html#associated-constants) ([#24])
+
+- **[breaking change]** Minimum supported version is Rust **1.20**, due to usage of associated constants
+
+- After being broken in 0.9, the `#[deprecated]` attribute is now supported again ([#112])
+
+- Other improvements to unit tests and documentation ([#106] and [#115])
+
+[#24]: https://github.com/rust-lang-nursery/bitflags/pull/24
+[#106]: https://github.com/rust-lang-nursery/bitflags/pull/106
+[#112]: https://github.com/rust-lang-nursery/bitflags/pull/112
+[#115]: https://github.com/rust-lang-nursery/bitflags/pull/115
+
+## How to update your code to use associated constants
+Assuming the following structure definition:
+```rust
+bitflags! {
+ struct Something: u8 {
+ const FOO = 0b01,
+ const BAR = 0b10
+ }
+}
+```
+In 0.9 and older you could do:
+```rust
+let x = FOO.bits | BAR.bits;
+```
+Now you must use:
+```rust
+let x = Something::FOO.bits | Something::BAR.bits;
+```
+
+# 0.9.1
+- Fix the implementation of `Formatting` traits when other formatting traits were present in scope ([#105])
+
+[#105]: https://github.com/rust-lang-nursery/bitflags/pull/105
+
+# 0.9.0
+- **[breaking change]** Use struct keyword instead of flags to define bitflag types ([#84])
+
+- **[breaking change]** Terminate const items with semicolons instead of commas ([#87])
+
+- Implement the `Hex`, `Octal`, and `Binary` formatting traits ([#86])
+
+- Printing an empty flag value with the `Debug` trait now prints "(empty)" instead of nothing ([#85])
+
+- The `bitflags!` macro can now be used inside of a fn body, to define a type local to that function ([#74])
+
+[#74]: https://github.com/rust-lang-nursery/bitflags/pull/74
+[#84]: https://github.com/rust-lang-nursery/bitflags/pull/84
+[#85]: https://github.com/rust-lang-nursery/bitflags/pull/85
+[#86]: https://github.com/rust-lang-nursery/bitflags/pull/86
+[#87]: https://github.com/rust-lang-nursery/bitflags/pull/87
+
+# 0.8.2
+- Update feature flag used when building bitflags as a dependency of the Rust toolchain
+
+# 0.8.1
+- Allow bitflags to be used as a dependency of the Rust toolchain
+
+# 0.8.0
+- Add support for the experimental `i128` and `u128` integer types ([#57])
+- Add set method: `flags.set(SOME_FLAG, true)` or `flags.set(SOME_FLAG, false)` ([#55])
+ This may break code that defines its own set method
+
+[#55]: https://github.com/rust-lang-nursery/bitflags/pull/55
+[#57]: https://github.com/rust-lang-nursery/bitflags/pull/57
+
+# 0.7.1
+*(yanked)*
+
+# 0.7.0
+- Implement the Extend trait ([#49])
+- Allow definitions inside the `bitflags!` macro to refer to items imported from other modules ([#51])
+
+[#49]: https://github.com/rust-lang-nursery/bitflags/pull/49
+[#51]: https://github.com/rust-lang-nursery/bitflags/pull/51
+
+# 0.6.0
+- The `no_std` feature was removed as it is now the default
+- The `assignment_operators` feature was remove as it is now enabled by default
+- Some clippy suggestions have been applied
diff --git a/extra/bitflags/CODE_OF_CONDUCT.md b/extra/bitflags/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..f7add90ae
--- /dev/null
+++ b/extra/bitflags/CODE_OF_CONDUCT.md
@@ -0,0 +1,73 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+education, socio-economic status, nationality, personal appearance, race,
+religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at coc@senaite.org. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org \ No newline at end of file
diff --git a/extra/bitflags/CONTRIBUTING.md b/extra/bitflags/CONTRIBUTING.md
new file mode 100644
index 000000000..588336398
--- /dev/null
+++ b/extra/bitflags/CONTRIBUTING.md
@@ -0,0 +1,9 @@
+# Updating compile-fail test outputs
+
+`bitflags` uses the `trybuild` crate to integration test its macros. Since Rust error messages change frequently enough that `nightly` builds produce spurious failures, we only check the compiler output in `beta` builds. If you run:
+
+```
+TRYBUILD=overwrite cargo +beta test --all
+```
+
+it will run the tests and update the `trybuild` output files.
diff --git a/extra/bitflags/Cargo.lock b/extra/bitflags/Cargo.lock
new file mode 100644
index 000000000..53d2d7a68
--- /dev/null
+++ b/extra/bitflags/Cargo.lock
@@ -0,0 +1,268 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "arbitrary"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2e1373abdaa212b704512ec2bd8b26bd0b7d5c3f70117411a5d9a451383c859"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "basic-toml"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.4.1"
+dependencies = [
+ "arbitrary",
+ "bytemuck",
+ "compiler_builtins",
+ "rustc-std-workspace-core",
+ "rustversion",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_test",
+ "trybuild",
+ "zerocopy",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "compiler_builtins"
+version = "0.1.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a6d58e9c3408138099a396a98fd0d0e6cfb25d723594d2ae48b5004513fd5b"
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e0efad4403bfc52dc201159c4b842a246a14b98c64b55dfd0f2d89729dfeb8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+
+[[package]]
+name = "itoa"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustc-std-workspace-core"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1956f5517128a2b6f23ab2dadf1a976f4f5b27962e7724c2bf3d45e539ec098c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "serde"
+version = "1.0.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_test"
+version = "1.0.176"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "trybuild"
+version = "1.0.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1"
+dependencies = [
+ "basic-toml",
+ "glob",
+ "once_cell",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "termcolor",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zerocopy"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20707b61725734c595e840fb3704378a0cd2b9c74cc9e6e20724838fc6a1e2f9"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56097d5b91d711293a42be9289403896b68654625021732067eac7a4ca388a1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/extra/bitflags/Cargo.toml b/extra/bitflags/Cargo.toml
new file mode 100644
index 000000000..5fd2c7dc1
--- /dev/null
+++ b/extra/bitflags/Cargo.toml
@@ -0,0 +1,95 @@
+# 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.56.0"
+name = "bitflags"
+version = "2.4.1"
+authors = ["The Rust Project Developers"]
+exclude = [
+ "tests",
+ ".github",
+]
+description = """
+A macro to generate structures which behave like bitflags.
+"""
+homepage = "https://github.com/bitflags/bitflags"
+documentation = "https://docs.rs/bitflags"
+readme = "README.md"
+keywords = [
+ "bit",
+ "bitmask",
+ "bitflags",
+ "flags",
+]
+categories = ["no-std"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/bitflags/bitflags"
+
+[package.metadata.docs.rs]
+features = ["example_generated"]
+
+[dependencies.arbitrary]
+version = "1.0"
+optional = true
+
+[dependencies.bytemuck]
+version = "1.0"
+optional = true
+
+[dependencies.compiler_builtins]
+version = "0.1.2"
+optional = true
+
+[dependencies.core]
+version = "1.0.0"
+optional = true
+package = "rustc-std-workspace-core"
+
+[dependencies.serde]
+version = "1.0"
+optional = true
+default-features = false
+
+[dev-dependencies.arbitrary]
+version = "1.0"
+features = ["derive"]
+
+[dev-dependencies.bytemuck]
+version = "1.0"
+features = ["derive"]
+
+[dev-dependencies.rustversion]
+version = "1.0"
+
+[dev-dependencies.serde_derive]
+version = "1.0"
+
+[dev-dependencies.serde_json]
+version = "1.0"
+
+[dev-dependencies.serde_test]
+version = "1.0"
+
+[dev-dependencies.trybuild]
+version = "1.0"
+
+[dev-dependencies.zerocopy]
+version = "0.6"
+
+[features]
+example_generated = []
+rustc-dep-of-std = [
+ "core",
+ "compiler_builtins",
+]
+std = []
diff --git a/extra/bitflags/LICENSE-APACHE b/extra/bitflags/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/extra/bitflags/LICENSE-APACHE
@@ -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/extra/bitflags/LICENSE-MIT b/extra/bitflags/LICENSE-MIT
new file mode 100644
index 000000000..39d4bdb5a
--- /dev/null
+++ b/extra/bitflags/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2014 The Rust 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/extra/bitflags/README.md b/extra/bitflags/README.md
new file mode 100644
index 000000000..ecad515e1
--- /dev/null
+++ b/extra/bitflags/README.md
@@ -0,0 +1,77 @@
+bitflags
+========
+
+[![Rust](https://github.com/bitflags/bitflags/workflows/Rust/badge.svg)](https://github.com/bitflags/bitflags/actions)
+[![Latest version](https://img.shields.io/crates/v/bitflags.svg)](https://crates.io/crates/bitflags)
+[![Documentation](https://docs.rs/bitflags/badge.svg)](https://docs.rs/bitflags)
+![License](https://img.shields.io/crates/l/bitflags.svg)
+
+`bitflags` generates flags enums with well-defined semantics and ergonomic end-user APIs.
+
+You can use `bitflags` to:
+
+- provide more user-friendly bindings to C APIs where flags may or may not be fully known in advance.
+- generate efficient options types with string parsing and formatting support.
+
+You can't use `bitflags` to:
+
+- guarantee only bits corresponding to defined flags will ever be set. `bitflags` allows access to the underlying bits type so arbitrary bits may be set.
+- define bitfields. `bitflags` only generates types where set bits denote the presence of some combination of flags.
+
+- [Documentation](https://docs.rs/bitflags)
+- [Specification](https://github.com/bitflags/bitflags/blob/main/spec.md)
+- [Release notes](https://github.com/bitflags/bitflags/releases)
+
+## Usage
+
+Add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+bitflags = "2.4.1"
+```
+
+and this to your source code:
+
+```rust
+use bitflags::bitflags;
+```
+
+## Example
+
+Generate a flags structure:
+
+```rust
+use bitflags::bitflags;
+
+// The `bitflags!` macro generates `struct`s that manage a set of flags.
+bitflags! {
+ /// Represents a set of flags.
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+ struct Flags: u32 {
+ /// The value `A`, at bit position `0`.
+ const A = 0b00000001;
+ /// The value `B`, at bit position `1`.
+ const B = 0b00000010;
+ /// The value `C`, at bit position `2`.
+ const C = 0b00000100;
+
+ /// The combination of `A`, `B`, and `C`.
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+ }
+}
+
+fn main() {
+ let e1 = Flags::A | Flags::C;
+ let e2 = Flags::B | Flags::C;
+ assert_eq!((e1 | e2), Flags::ABC); // union
+ assert_eq!((e1 & e2), Flags::C); // intersection
+ assert_eq!((e1 - e2), Flags::A); // set difference
+ assert_eq!(!e2, Flags::A); // set complement
+}
+```
+
+## Rust Version Support
+
+The minimum supported Rust version is documented in the `Cargo.toml` file.
+This may be bumped in minor releases as necessary.
diff --git a/extra/bitflags/SECURITY.md b/extra/bitflags/SECURITY.md
new file mode 100644
index 000000000..790ac5b59
--- /dev/null
+++ b/extra/bitflags/SECURITY.md
@@ -0,0 +1,13 @@
+# Security Policy
+
+## Supported Versions
+
+Security updates are applied only to the latest release.
+
+## Reporting a Vulnerability
+
+If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released.
+
+Please disclose it at [security advisory](https://github.com/bitflags/bitflags/security/advisories/new).
+
+This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure.
diff --git a/extra/bitflags/benches/parse.rs b/extra/bitflags/benches/parse.rs
new file mode 100644
index 000000000..caa920345
--- /dev/null
+++ b/extra/bitflags/benches/parse.rs
@@ -0,0 +1,96 @@
+#![feature(test)]
+
+extern crate test;
+
+use std::{
+ fmt::{self, Display},
+ str::FromStr,
+};
+
+bitflags::bitflags! {
+ struct Flags10: u32 {
+ const A = 0b0000_0000_0000_0001;
+ const B = 0b0000_0000_0000_0010;
+ const C = 0b0000_0000_0000_0100;
+ const D = 0b0000_0000_0000_1000;
+ const E = 0b0000_0000_0001_0000;
+ const F = 0b0000_0000_0010_0000;
+ const G = 0b0000_0000_0100_0000;
+ const H = 0b0000_0000_1000_0000;
+ const I = 0b0000_0001_0000_0000;
+ const J = 0b0000_0010_0000_0000;
+ }
+}
+
+impl FromStr for Flags10 {
+ type Err = bitflags::parser::ParseError;
+
+ fn from_str(flags: &str) -> Result<Self, Self::Err> {
+ Ok(Flags10(flags.parse()?))
+ }
+}
+
+impl Display for Flags10 {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Display::fmt(&self.0, f)
+ }
+}
+
+#[bench]
+fn format_flags_1_present(b: &mut test::Bencher) {
+ b.iter(|| Flags10::J.to_string())
+}
+
+#[bench]
+fn format_flags_5_present(b: &mut test::Bencher) {
+ b.iter(|| (Flags10::F | Flags10::G | Flags10::H | Flags10::I | Flags10::J).to_string())
+}
+
+#[bench]
+fn format_flags_10_present(b: &mut test::Bencher) {
+ b.iter(|| {
+ (Flags10::A
+ | Flags10::B
+ | Flags10::C
+ | Flags10::D
+ | Flags10::E
+ | Flags10::F
+ | Flags10::G
+ | Flags10::H
+ | Flags10::I
+ | Flags10::J)
+ .to_string()
+ })
+}
+
+#[bench]
+fn parse_flags_1_10(b: &mut test::Bencher) {
+ b.iter(|| {
+ let flags: Flags10 = "J".parse().unwrap();
+ flags
+ })
+}
+
+#[bench]
+fn parse_flags_5_10(b: &mut test::Bencher) {
+ b.iter(|| {
+ let flags: Flags10 = "F | G | H | I | J".parse().unwrap();
+ flags
+ })
+}
+
+#[bench]
+fn parse_flags_10_10(b: &mut test::Bencher) {
+ b.iter(|| {
+ let flags: Flags10 = "A | B | C | D | E | F | G | H | I | J".parse().unwrap();
+ flags
+ })
+}
+
+#[bench]
+fn parse_flags_1_10_hex(b: &mut test::Bencher) {
+ b.iter(|| {
+ let flags: Flags10 = "0xFF".parse().unwrap();
+ flags
+ })
+}
diff --git a/extra/bitflags/examples/custom_bits_type.rs b/extra/bitflags/examples/custom_bits_type.rs
new file mode 100644
index 000000000..8924bfdf3
--- /dev/null
+++ b/extra/bitflags/examples/custom_bits_type.rs
@@ -0,0 +1,97 @@
+use std::ops::{BitAnd, BitOr, BitXor, Not};
+
+use bitflags::{Bits, Flag, Flags};
+
+// Define a custom container that can be used in flags types
+// Note custom bits types can't be used in `bitflags!`
+// without making the trait impls `const`. This is currently
+// unstable
+#[derive(Clone, Copy, Debug)]
+pub struct CustomBits([bool; 3]);
+
+impl Bits for CustomBits {
+ const EMPTY: Self = CustomBits([false; 3]);
+
+ const ALL: Self = CustomBits([true; 3]);
+}
+
+impl PartialEq for CustomBits {
+ fn eq(&self, other: &Self) -> bool {
+ self.0 == other.0
+ }
+}
+
+impl BitAnd for CustomBits {
+ type Output = Self;
+
+ fn bitand(self, other: Self) -> Self {
+ CustomBits([
+ self.0[0] & other.0[0],
+ self.0[1] & other.0[1],
+ self.0[2] & other.0[2],
+ ])
+ }
+}
+
+impl BitOr for CustomBits {
+ type Output = Self;
+
+ fn bitor(self, other: Self) -> Self {
+ CustomBits([
+ self.0[0] | other.0[0],
+ self.0[1] | other.0[1],
+ self.0[2] | other.0[2],
+ ])
+ }
+}
+
+impl BitXor for CustomBits {
+ type Output = Self;
+
+ fn bitxor(self, other: Self) -> Self {
+ CustomBits([
+ self.0[0] & other.0[0],
+ self.0[1] & other.0[1],
+ self.0[2] & other.0[2],
+ ])
+ }
+}
+
+impl Not for CustomBits {
+ type Output = Self;
+
+ fn not(self) -> Self {
+ CustomBits([!self.0[0], !self.0[1], !self.0[2]])
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CustomFlags(CustomBits);
+
+impl CustomFlags {
+ pub const A: Self = CustomFlags(CustomBits([true, false, false]));
+ pub const B: Self = CustomFlags(CustomBits([false, true, false]));
+ pub const C: Self = CustomFlags(CustomBits([false, false, true]));
+}
+
+impl Flags for CustomFlags {
+ const FLAGS: &'static [Flag<Self>] = &[
+ Flag::new("A", Self::A),
+ Flag::new("B", Self::B),
+ Flag::new("C", Self::C),
+ ];
+
+ type Bits = CustomBits;
+
+ fn bits(&self) -> Self::Bits {
+ self.0
+ }
+
+ fn from_bits_retain(bits: Self::Bits) -> Self {
+ CustomFlags(bits)
+ }
+}
+
+fn main() {
+ println!("{:?}", CustomFlags::A.union(CustomFlags::C));
+}
diff --git a/extra/bitflags/examples/custom_derive.rs b/extra/bitflags/examples/custom_derive.rs
new file mode 100644
index 000000000..5a85afb9c
--- /dev/null
+++ b/extra/bitflags/examples/custom_derive.rs
@@ -0,0 +1,23 @@
+//! An example of implementing the `BitFlags` trait manually for a flags type.
+
+use std::str;
+
+use bitflags::bitflags;
+
+// Define a flags type outside of the `bitflags` macro as a newtype
+// It can accept custom derives for libaries `bitflags` doesn't support natively
+#[derive(zerocopy::AsBytes, zerocopy::FromBytes)]
+#[repr(transparent)]
+pub struct ManualFlags(u32);
+
+// Next: use `impl Flags` instead of `struct Flags`
+bitflags! {
+ impl ManualFlags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+ }
+}
+
+fn main() {}
diff --git a/extra/bitflags/examples/fmt.rs b/extra/bitflags/examples/fmt.rs
new file mode 100644
index 000000000..724b2074c
--- /dev/null
+++ b/extra/bitflags/examples/fmt.rs
@@ -0,0 +1,49 @@
+//! An example of implementing Rust's standard formatting and parsing traits for flags types.
+
+use core::{fmt, str};
+
+bitflags::bitflags! {
+ // You can `#[derive]` the `Debug` trait, but implementing it manually
+ // can produce output like `A | B` instead of `Flags(A | B)`.
+ // #[derive(Debug)]
+ #[derive(PartialEq, Eq)]
+ pub struct Flags: u32 {
+ const A = 1;
+ const B = 2;
+ const C = 4;
+ const D = 8;
+ }
+}
+
+impl fmt::Debug for Flags {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ bitflags::parser::to_writer(self, f)
+ }
+}
+
+impl fmt::Display for Flags {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ bitflags::parser::to_writer(self, f)
+ }
+}
+
+impl str::FromStr for Flags {
+ type Err = bitflags::parser::ParseError;
+
+ fn from_str(flags: &str) -> Result<Self, Self::Err> {
+ bitflags::parser::from_str(flags)
+ }
+}
+
+fn main() -> Result<(), bitflags::parser::ParseError> {
+ let flags = Flags::A | Flags::B;
+
+ println!("{}", flags);
+
+ let formatted = flags.to_string();
+ let parsed: Flags = formatted.parse()?;
+
+ assert_eq!(flags, parsed);
+
+ Ok(())
+}
diff --git a/extra/bitflags/examples/macro_free.rs b/extra/bitflags/examples/macro_free.rs
new file mode 100644
index 000000000..756337900
--- /dev/null
+++ b/extra/bitflags/examples/macro_free.rs
@@ -0,0 +1,61 @@
+//! An example of implementing the `BitFlags` trait manually for a flags type.
+//!
+//! This example doesn't use any macros.
+
+use std::{fmt, str};
+
+use bitflags::{Flag, Flags};
+
+// First: Define your flags type. It just needs to be `Sized + 'static`.
+pub struct ManualFlags(u32);
+
+// Not required: Define some constants for valid flags
+impl ManualFlags {
+ pub const A: ManualFlags = ManualFlags(0b00000001);
+ pub const B: ManualFlags = ManualFlags(0b00000010);
+ pub const C: ManualFlags = ManualFlags(0b00000100);
+ pub const ABC: ManualFlags = ManualFlags(0b00000111);
+}
+
+// Next: Implement the `BitFlags` trait, specifying your set of valid flags
+// and iterators
+impl Flags for ManualFlags {
+ const FLAGS: &'static [Flag<Self>] = &[
+ Flag::new("A", Self::A),
+ Flag::new("B", Self::B),
+ Flag::new("C", Self::C),
+ ];
+
+ type Bits = u32;
+
+ fn bits(&self) -> u32 {
+ self.0
+ }
+
+ fn from_bits_retain(bits: u32) -> Self {
+ Self(bits)
+ }
+}
+
+// Not required: Add parsing support
+impl str::FromStr for ManualFlags {
+ type Err = bitflags::parser::ParseError;
+
+ fn from_str(input: &str) -> Result<Self, Self::Err> {
+ bitflags::parser::from_str(input)
+ }
+}
+
+// Not required: Add formatting support
+impl fmt::Display for ManualFlags {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ bitflags::parser::to_writer(self, f)
+ }
+}
+
+fn main() {
+ println!(
+ "{}",
+ ManualFlags::A.union(ManualFlags::B).union(ManualFlags::C)
+ );
+}
diff --git a/extra/bitflags/examples/serde.rs b/extra/bitflags/examples/serde.rs
new file mode 100644
index 000000000..22eae2db6
--- /dev/null
+++ b/extra/bitflags/examples/serde.rs
@@ -0,0 +1,36 @@
+//! An example of implementing `serde::Serialize` and `serde::Deserialize`.
+//! The `#[serde(transparent)]` attribute is recommended to serialize directly
+//! to the underlying bits type without wrapping it in a `serde` newtype.
+
+#[cfg(feature = "serde")]
+fn main() {
+ use serde_derive::*;
+
+ bitflags::bitflags! {
+ #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
+ #[serde(transparent)]
+ pub struct Flags: u32 {
+ const A = 1;
+ const B = 2;
+ const C = 4;
+ const D = 8;
+ }
+ }
+
+ let flags = Flags::A | Flags::B;
+
+ let serialized = serde_json::to_string(&flags).unwrap();
+
+ println!("{:?} -> {}", flags, serialized);
+
+ assert_eq!(serialized, r#""A | B""#);
+
+ let deserialized: Flags = serde_json::from_str(&serialized).unwrap();
+
+ println!("{} -> {:?}", serialized, flags);
+
+ assert_eq!(deserialized, flags);
+}
+
+#[cfg(not(feature = "serde"))]
+fn main() {}
diff --git a/extra/bitflags/spec.md b/extra/bitflags/spec.md
new file mode 100644
index 000000000..43dae1d79
--- /dev/null
+++ b/extra/bitflags/spec.md
@@ -0,0 +1,552 @@
+# Bitflags
+
+`bitflags` generates flags enums with well-defined semantics and ergonomic end-user APIs.
+
+You can use `bitflags` to:
+
+- provide more user-friendly bindings to C APIs where flags may or may not be fully known in advance.
+- generate efficient options types with string parsing and formatting support.
+
+You can't use `bitflags` to:
+
+- guarantee only bits corresponding to defined flags will ever be set. `bitflags` allows access to the underlying bits type so arbitrary bits may be set.
+- define bitfields. `bitflags` only generates types where set bits denote the presence of some combination of flags.
+
+## Definitions
+
+This section formally defines the terminology and semantics of `bitflags`. It's organized so more fundamental concepts are introduced before those that build on them. It may be helpful to start from the bottom of the section and refer back up to concepts defined earlier.
+
+Examples use `bitflags` syntax with `u8` as the bits type.
+
+### Bits type
+
+A type that defines a fixed number of bits at specific locations.
+
+----
+
+Bits types are typically fixed-width unsigned integers. For example, `u8` is a bits type that defines 8 bits; bit-0 through bit-7.
+
+### Bits value
+
+An instance of a bits type where each bit may be set (`1`) or unset (`0`).
+
+----
+
+Some examples of bits values for the bits type `u8` are:
+
+```rust
+0b0000_0000
+0b1111_1111
+0b1010_0101
+```
+
+#### Equality
+
+Two bits values are equal if their bits are in the same configuration; set bits in one are set in the other, and unset bits in one are unset in the other.
+
+#### Operations
+
+Bits values define the bitwise operators and (`&`), or (`|`), exclusive-or (`^`), and negation (`!`) that apply to each of their bits.
+
+### Flag
+
+A set of bits in a bits type that may have a unique name.
+
+----
+
+Bits are not required to be exclusive to a flag. Bits are not required to be contiguous.
+
+The following is a flag for `u8` with the name `A` that includes bit-0:
+
+```rust
+const A = 0b0000_0001;
+```
+
+The following is a flag for `u8` with the name `B` that includes bit-0, and bit-5:
+
+```rust
+const B = 0b0010_0001;
+```
+
+#### Named flag
+
+A flag with a name.
+
+----
+
+The following is a named flag, where the name is `A`:
+
+```rust
+const A = 0b0000_0001;
+```
+
+#### Unnamed flag
+
+A flag without a name.
+
+----
+
+The following is an unnamed flag:
+
+```rust
+const _ = 0b0000_0001;
+```
+
+#### Zero-bit flag
+
+A flag with a set of zero bits.
+
+----
+
+The following is a zero-bit flag:
+
+```rust
+const ZERO = 0b0000_0000;
+```
+
+#### Single-bit flag
+
+A flag with a set of one bit.
+
+----
+
+The following are single-bit flags:
+
+```rust
+const A = 0b0000_0001;
+const B = 0b0000_0010;
+```
+
+#### Multi-bit flag
+
+A flag with a set of more than one bit.
+
+----
+
+The following are multi-bit flags:
+
+```rust
+const A = 0b0000_0011;
+const B = 0b1111_1111;
+```
+
+### Flags type
+
+A set of defined flags over a specific bits type.
+
+#### Known bit
+
+A bit in any defined flag.
+
+----
+
+In the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+ const C = 0b0000_0100;
+}
+```
+
+the known bits are:
+
+```rust
+0b0000_0111
+```
+
+#### Unknown bit
+
+A bit not in any defined flag.
+
+----
+
+In the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+ const C = 0b0000_0100;
+}
+```
+
+the unknown bits are:
+
+```rust
+0b1111_1000
+```
+
+### Flags value
+
+An instance of a flags type using its specific bits value for storage.
+
+The flags value of a flag is one where each of its bits is set, and all others are unset.
+
+#### Contains
+
+Whether all set bits in a source flags value are also set in a target flags value.
+
+----
+
+Given the flags value:
+
+```rust
+0b0000_0011
+```
+
+the following flags values are contained:
+
+```rust
+0b0000_0000
+0b0000_0010
+0b0000_0001
+0b0000_0011
+```
+
+but the following flags values are not contained:
+
+```rust
+0b0000_1000
+0b0000_0110
+```
+
+#### Intersects
+
+Whether any set bits in a source flags value are also set in a target flags value.
+
+----
+
+Given the flags value:
+
+```rust
+0b0000_0011
+```
+
+the following flags intersect:
+
+```rust
+0b0000_0010
+0b0000_0001
+0b1111_1111
+```
+
+but the following flags values do not intersect:
+
+```rust
+0b0000_0000
+0b1111_0000
+```
+
+#### Empty
+
+Whether all bits in a flags value are unset.
+
+----
+
+The following flags value is empty:
+
+```rust
+0b0000_0000
+```
+
+The following flags values are not empty:
+
+```rust
+0b0000_0001
+0b0110_0000
+```
+
+#### All
+
+Whether all defined flags are contained in a flags value.
+
+----
+
+Given a flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+}
+```
+
+the following flags values all satisfy all:
+
+```rust
+0b0000_0011
+0b1000_0011
+0b1111_1111
+```
+
+### Operations
+
+Examples in this section all use the given flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+ const C = 0b0000_1100;
+}
+```
+
+#### Truncate
+
+Unset all unknown bits in a flags value.
+
+----
+
+Given the flags value:
+
+```rust
+0b1111_1111
+```
+
+the result of truncation will be:
+
+```rust
+0b0000_1111
+```
+
+----
+
+Truncating doesn't guarantee that a non-empty result will contain any defined flags. Given the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0101;
+}
+```
+
+and the following flags value:
+
+```rust
+0b0000_1110;
+```
+
+The result of truncation will be:
+
+```rust
+0b0000_0100;
+```
+
+which intersects the flag `A`, but doesn't contain it.
+
+This behavior is possible even when only operating with flags values containing defined flags. Given the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0101;
+ const B = 0b0000_0001;
+}
+```
+
+The result of `A ^ B` is `0b0000_0100`, which also doesn't contain any defined flag.
+
+----
+
+If all known bits are in the set of at least one defined single-bit flag, then all operations that produce non-empty results will always contain defined flags.
+
+#### Union
+
+The bitwise or (`|`) of the bits in two flags values.
+
+----
+
+The following are examples of the result of unioning flags values:
+
+```rust
+0b0000_0001 | 0b0000_0010 = 0b0000_0011
+0b0000_0000 | 0b1111_1111 = 0b1111_1111
+```
+
+#### Intersection
+
+The bitwise and (`&`) of the bits in two flags values.
+
+----
+
+The following are examples of the result of intersecting flags values:
+
+```rust
+0b0000_0001 & 0b0000_0010 = 0b0000_0000
+0b1111_1100 & 0b1111_0111 = 0b1111_0100
+0b1111_1111 & 0b1111_1111 = 0b1111_1111
+```
+
+#### Symmetric difference
+
+The bitwise exclusive-or (`^`) of the bits in two flags values.
+
+----
+
+The following are examples of the symmetric difference between two flags values:
+
+```rust
+0b0000_0001 ^ 0b0000_0010 = 0b0000_0011
+0b0000_1111 ^ 0b0000_0011 = 0b0000_1100
+0b1100_0000 ^ 0b0011_0000 = 0b1111_0000
+```
+
+#### Complement
+
+The bitwise negation (`!`) of the bits in a flags value, truncating the result.
+
+----
+
+The following are examples of the complement of a flags value:
+
+```rust
+!0b0000_0000 = 0b0000_1111
+!0b0000_1111 = 0b0000_0000
+!0b1111_1000 = 0b0000_0111
+```
+
+#### Difference
+
+The bitwise union (`|`) of the bits in one flags value and the bitwise negation (`!`) of the bits in another.
+
+----
+
+This operation is not equivalent to the intersection of one flags value with the complement of another (`&!`).
+The former will truncate the result, where difference will not.
+
+----
+
+The following are examples of the difference between two flags values:
+
+```rust
+0b0000_0001 & !0b0000_0010 = 0b0000_0001
+0b0000_1101 & !0b0000_0011 = 0b0000_1100
+0b1111_1111 & !0b0000_0001 = 0b1111_1110
+```
+
+### Iteration
+
+Yield the bits of a source flags value in a set of contained flags values.
+
+----
+
+To be most useful, each yielded flags value should set exactly the bits of a defined flag contained in the source. Any known bits that aren't in the set of any contained flag should be yielded together as a final flags value.
+
+----
+
+Given the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+ const AB = 0b0000_0011;
+}
+```
+
+and the following flags value:
+
+```rust
+0b0000_1111
+```
+
+When iterated it may yield a flags value for `A` and `B`, then a final flag with the unknown bits:
+
+```rust
+0b0000_0001
+0b0000_0010
+0b0000_1100
+```
+
+It may also yield a flags value for `AB`, then a final flag with the unknown bits:
+
+```rust
+0b0000_0011
+0b0000_1100
+```
+
+----
+
+Given the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0011;
+}
+```
+
+and the following flags value:
+
+```rust
+0b0000_0001
+```
+
+When iterated it will still yield a flags value for the known bit `0b0000_0001` even though it doesn't contain a flag.
+
+### Formatting
+
+Format and parse a flags value as text using the following grammar:
+
+- _Flags:_ (_Whitespace_ _Flag_ _Whitespace_)`|`*
+- _Flag:_ _Name_ | _Hex Number_
+- _Name:_ The name of any defined flag
+- _Hex Number_: `0x`([0-9a-fA-F])*
+- _Whitespace_: (\s)*
+
+Flags values can be formatted as _Flags_ by iterating over them, formatting each yielded flags value as a _Flag_. Any yielded flags value that sets exactly the bits of a defined flag with a name should be formatted as a _Name_. Otherwise it must be formatted as a _Hex Number_.
+
+Formatting and parsing supports three modes:
+
+- **Retain**: Formatting and parsing roundtrips exactly the bits of the source flags value. This is the default behavior.
+- **Truncate**: Flags values are truncated before formatting, and truncated after parsing.
+- **Strict**: A _Flag_ may only be formatted and parsed as a _Name_. _Hex numbers_ are not allowed. A consequence of this is that unknown bits and any bits that aren't in a contained named flag will be ignored. This is recommended for flags values serialized across API boundaries, like web services.
+
+Text that is empty or whitespace is an empty flags value.
+
+----
+
+Given the following flags type:
+
+```rust
+struct Flags {
+ const A = 0b0000_0001;
+ const B = 0b0000_0010;
+ const AB = 0b0000_0011;
+ const C = 0b0000_1100;
+}
+```
+
+The following are examples of how flags values can be formatted using any mode:
+
+```rust
+0b0000_0000 = ""
+0b0000_0001 = "A"
+0b0000_0010 = "B"
+0b0000_0011 = "A | B"
+0b0000_0011 = "AB"
+0b0000_1111 = "A | B | C"
+```
+
+Truncate mode will unset any unknown bits:
+
+```rust
+0b1000_0000 = ""
+0b1111_1111 = "A | B | C"
+0b0000_1000 = "0x8"
+```
+
+Retain mode will include any unknown bits as a final _Flag_:
+
+```rust
+0b1000_0000 = "0x80"
+0b1111_1111 = "A | B | C | 0xf0"
+0b0000_1000 = "0x8"
+```
+
+Strict mode will unset any unknown bits, as well as bits not contained in any defined named flags:
+
+```rust
+0b1000_0000 = ""
+0b1111_1111 = "A | B | C"
+0b0000_1000 = ""
+```
diff --git a/extra/bitflags/src/example_generated.rs b/extra/bitflags/src/example_generated.rs
new file mode 100644
index 000000000..abb1118fa
--- /dev/null
+++ b/extra/bitflags/src/example_generated.rs
@@ -0,0 +1,65 @@
+//! This module shows an example of code generated by the macro. **IT MUST NOT BE USED OUTSIDE THIS
+//! CRATE**.
+//!
+//! Usually, when you call the `bitflags!` macro, only the `Flags` type would be visible. In this
+//! example, the `Field0`, `Iter`, and `IterRaw` types are also exposed so that you can explore
+//! their APIs. The `Field0` type can be accessed as `self.0` on an instance of `Flags`.
+
+__declare_public_bitflags! {
+ /// This is the same `Flags` struct defined in the [crate level example](../index.html#example).
+ /// Note that this struct is just for documentation purposes only, it must not be used outside
+ /// this crate.
+ pub struct Flags
+}
+
+__declare_internal_bitflags! {
+ pub struct Field0: u32
+}
+
+__impl_internal_bitflags! {
+ Field0: u32, Flags {
+ // Field `A`.
+ ///
+ /// This flag has the value `0b00000001`.
+ const A = 0b00000001;
+ /// Field `B`.
+ ///
+ /// This flag has the value `0b00000010`.
+ const B = 0b00000010;
+ /// Field `C`.
+ ///
+ /// This flag has the value `0b00000100`.
+ const C = 0b00000100;
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+ }
+}
+
+__impl_public_bitflags_forward! {
+ Flags: u32, Field0
+}
+
+__impl_public_bitflags_ops! {
+ Flags
+}
+
+__impl_public_bitflags_iter! {
+ Flags: u32, Flags
+}
+
+__impl_public_bitflags_consts! {
+ Flags: u32 {
+ /// Field `A`.
+ ///
+ /// This flag has the value `0b00000001`.
+ const A = 0b00000001;
+ /// Field `B`.
+ ///
+ /// This flag has the value `0b00000010`.
+ const B = 0b00000010;
+ /// Field `C`.
+ ///
+ /// This flag has the value `0b00000100`.
+ const C = 0b00000100;
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+ }
+}
diff --git a/extra/bitflags/src/external.rs b/extra/bitflags/src/external.rs
new file mode 100644
index 000000000..efeaa8279
--- /dev/null
+++ b/extra/bitflags/src/external.rs
@@ -0,0 +1,262 @@
+//! Conditional trait implementations for external libraries.
+
+/*
+How do I support a new external library?
+
+Let's say we want to add support for `my_library`.
+
+First, we create a module under `external`, like `serde` with any specialized code.
+Ideally, any utilities in here should just work off the `Flags` trait and maybe a
+few other assumed bounds.
+
+Next, re-export the library from the `__private` module here.
+
+Next, define a macro like so:
+
+```rust
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(feature = "serde")]
+macro_rules! __impl_external_bitflags_my_library {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {
+ // Implementation goes here
+ };
+}
+
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(not(feature = "my_library"))]
+macro_rules! __impl_external_bitflags_my_library {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {};
+}
+```
+
+Note that the macro is actually defined twice; once for when the `my_library` feature
+is available, and once for when it's not. This is because the `__impl_external_bitflags_my_library`
+macro is called in an end-user's library, not in `bitflags`. In an end-user's library we don't
+know whether or not a particular feature of `bitflags` is enabled, so we unconditionally call
+the macro, where the body of that macro depends on the feature flag.
+
+Now, we add our macro call to the `__impl_external_bitflags` macro body:
+
+```rust
+__impl_external_bitflags_my_library! {
+ $InternalBitFlags: $T, $PublicBitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag;
+ )*
+ }
+}
+```
+*/
+
+pub(crate) mod __private {
+ #[cfg(feature = "serde")]
+ pub use serde;
+
+ #[cfg(feature = "arbitrary")]
+ pub use arbitrary;
+
+ #[cfg(feature = "bytemuck")]
+ pub use bytemuck;
+}
+
+/// Implements traits from external libraries for the internal bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_external_bitflags {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {
+ // Any new library traits impls should be added here
+ // Use `serde` as an example: generate code when the feature is available,
+ // and a no-op when it isn't
+
+ __impl_external_bitflags_serde! {
+ $InternalBitFlags: $T, $PublicBitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag;
+ )*
+ }
+ }
+
+ __impl_external_bitflags_arbitrary! {
+ $InternalBitFlags: $T, $PublicBitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag;
+ )*
+ }
+ }
+
+ __impl_external_bitflags_bytemuck! {
+ $InternalBitFlags: $T, $PublicBitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag;
+ )*
+ }
+ }
+ };
+}
+
+#[cfg(feature = "serde")]
+pub mod serde;
+
+/// Implement `Serialize` and `Deserialize` for the internal bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(feature = "serde")]
+macro_rules! __impl_external_bitflags_serde {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {
+ impl $crate::__private::serde::Serialize for $InternalBitFlags {
+ fn serialize<S: $crate::__private::serde::Serializer>(
+ &self,
+ serializer: S,
+ ) -> $crate::__private::core::result::Result<S::Ok, S::Error> {
+ $crate::serde::serialize(
+ &$PublicBitFlags::from_bits_retain(self.bits()),
+ serializer,
+ )
+ }
+ }
+
+ impl<'de> $crate::__private::serde::Deserialize<'de> for $InternalBitFlags {
+ fn deserialize<D: $crate::__private::serde::Deserializer<'de>>(
+ deserializer: D,
+ ) -> $crate::__private::core::result::Result<Self, D::Error> {
+ let flags: $PublicBitFlags = $crate::serde::deserialize(deserializer)?;
+
+ Ok(flags.0)
+ }
+ }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(not(feature = "serde"))]
+macro_rules! __impl_external_bitflags_serde {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {};
+}
+
+#[cfg(feature = "arbitrary")]
+pub mod arbitrary;
+
+#[cfg(feature = "bytemuck")]
+mod bytemuck;
+
+/// Implement `Arbitrary` for the internal bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(feature = "arbitrary")]
+macro_rules! __impl_external_bitflags_arbitrary {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {
+ impl<'a> $crate::__private::arbitrary::Arbitrary<'a> for $InternalBitFlags {
+ fn arbitrary(
+ u: &mut $crate::__private::arbitrary::Unstructured<'a>,
+ ) -> $crate::__private::arbitrary::Result<Self> {
+ $crate::arbitrary::arbitrary::<$PublicBitFlags>(u).map(|flags| flags.0)
+ }
+ }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(not(feature = "arbitrary"))]
+macro_rules! __impl_external_bitflags_arbitrary {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {};
+}
+
+/// Implement `Pod` and `Zeroable` for the internal bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(feature = "bytemuck")]
+macro_rules! __impl_external_bitflags_bytemuck {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {
+ // SAFETY: $InternalBitFlags is guaranteed to have the same ABI as $T,
+ // and $T implements Pod
+ unsafe impl $crate::__private::bytemuck::Pod for $InternalBitFlags where
+ $T: $crate::__private::bytemuck::Pod
+ {
+ }
+
+ // SAFETY: $InternalBitFlags is guaranteed to have the same ABI as $T,
+ // and $T implements Zeroable
+ unsafe impl $crate::__private::bytemuck::Zeroable for $InternalBitFlags where
+ $T: $crate::__private::bytemuck::Zeroable
+ {
+ }
+ };
+}
+
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+#[cfg(not(feature = "bytemuck"))]
+macro_rules! __impl_external_bitflags_bytemuck {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt;
+ )*
+ }
+ ) => {};
+}
diff --git a/extra/bitflags/src/external/arbitrary.rs b/extra/bitflags/src/external/arbitrary.rs
new file mode 100644
index 000000000..ea76f0a25
--- /dev/null
+++ b/extra/bitflags/src/external/arbitrary.rs
@@ -0,0 +1,33 @@
+//! Specialized fuzzing for flags types using `arbitrary`.
+
+use crate::Flags;
+
+/**
+Generate some arbitrary flags value with only known bits set.
+*/
+pub fn arbitrary<'a, B: Flags>(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<B>
+where
+ B::Bits: arbitrary::Arbitrary<'a>,
+{
+ B::from_bits(u.arbitrary()?).ok_or_else(|| arbitrary::Error::IncorrectFormat)
+}
+
+#[cfg(test)]
+mod tests {
+ use arbitrary::Arbitrary;
+
+ bitflags! {
+ #[derive(Arbitrary)]
+ struct Color: u32 {
+ const RED = 0x1;
+ const GREEN = 0x2;
+ const BLUE = 0x4;
+ }
+ }
+
+ #[test]
+ fn test_arbitrary() {
+ let mut unstructured = arbitrary::Unstructured::new(&[0_u8; 256]);
+ let _color = Color::arbitrary(&mut unstructured);
+ }
+}
diff --git a/extra/bitflags/src/external/bytemuck.rs b/extra/bitflags/src/external/bytemuck.rs
new file mode 100644
index 000000000..a0cd68c9d
--- /dev/null
+++ b/extra/bitflags/src/external/bytemuck.rs
@@ -0,0 +1,19 @@
+#[cfg(test)]
+mod tests {
+ use bytemuck::{Pod, Zeroable};
+
+ bitflags! {
+ #[derive(Pod, Zeroable, Clone, Copy)]
+ #[repr(transparent)]
+ struct Color: u32 {
+ const RED = 0x1;
+ const GREEN = 0x2;
+ const BLUE = 0x4;
+ }
+ }
+
+ #[test]
+ fn test_bytemuck() {
+ assert_eq!(0x1, bytemuck::cast::<Color, u32>(Color::RED));
+ }
+}
diff --git a/extra/bitflags/src/external/serde.rs b/extra/bitflags/src/external/serde.rs
new file mode 100644
index 000000000..be4f2edbf
--- /dev/null
+++ b/extra/bitflags/src/external/serde.rs
@@ -0,0 +1,93 @@
+//! Specialized serialization for flags types using `serde`.
+
+use crate::{
+ parser::{self, ParseHex, WriteHex},
+ Flags,
+};
+use core::{fmt, str};
+use serde::{
+ de::{Error, Visitor},
+ Deserialize, Deserializer, Serialize, Serializer,
+};
+
+/**
+Serialize a set of flags as a human-readable string or their underlying bits.
+
+Any unknown bits will be retained.
+*/
+pub fn serialize<B: Flags, S: Serializer>(flags: &B, serializer: S) -> Result<S::Ok, S::Error>
+where
+ B::Bits: WriteHex + Serialize,
+{
+ // Serialize human-readable flags as a string like `"A | B"`
+ if serializer.is_human_readable() {
+ serializer.collect_str(&parser::AsDisplay(flags))
+ }
+ // Serialize non-human-readable flags directly as the underlying bits
+ else {
+ flags.bits().serialize(serializer)
+ }
+}
+
+/**
+Deserialize a set of flags from a human-readable string or their underlying bits.
+
+Any unknown bits will be retained.
+*/
+pub fn deserialize<'de, B: Flags, D: Deserializer<'de>>(deserializer: D) -> Result<B, D::Error>
+where
+ B::Bits: ParseHex + Deserialize<'de>,
+{
+ if deserializer.is_human_readable() {
+ // Deserialize human-readable flags by parsing them from strings like `"A | B"`
+ struct FlagsVisitor<B>(core::marker::PhantomData<B>);
+
+ impl<'de, B: Flags> Visitor<'de> for FlagsVisitor<B>
+ where
+ B::Bits: ParseHex,
+ {
+ type Value = B;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a string value of `|` separated flags")
+ }
+
+ fn visit_str<E: Error>(self, flags: &str) -> Result<Self::Value, E> {
+ parser::from_str(flags).map_err(|e| E::custom(e))
+ }
+ }
+
+ deserializer.deserialize_str(FlagsVisitor(Default::default()))
+ } else {
+ // Deserialize non-human-readable flags directly from the underlying bits
+ let bits = B::Bits::deserialize(deserializer)?;
+
+ Ok(B::from_bits_retain(bits))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use serde_test::{assert_tokens, Configure, Token::*};
+ bitflags! {
+ #[derive(serde_derive::Serialize, serde_derive::Deserialize, Debug, PartialEq, Eq)]
+ #[serde(transparent)]
+ struct SerdeFlags: u32 {
+ const A = 1;
+ const B = 2;
+ const C = 4;
+ const D = 8;
+ }
+ }
+
+ #[test]
+ fn test_serde_bitflags_default() {
+ assert_tokens(&SerdeFlags::empty().readable(), &[Str("")]);
+
+ assert_tokens(&SerdeFlags::empty().compact(), &[U32(0)]);
+
+ assert_tokens(&(SerdeFlags::A | SerdeFlags::B).readable(), &[Str("A | B")]);
+
+ assert_tokens(&(SerdeFlags::A | SerdeFlags::B).compact(), &[U32(1 | 2)]);
+ }
+}
diff --git a/extra/bitflags/src/internal.rs b/extra/bitflags/src/internal.rs
new file mode 100644
index 000000000..aca1ac4db
--- /dev/null
+++ b/extra/bitflags/src/internal.rs
@@ -0,0 +1,125 @@
+//! Generate the internal `bitflags`-facing flags type.
+//!
+//! The code generated here is owned by `bitflags`, but still part of its public API.
+//! Changes to the types generated here need to be considered like any other public API change.
+
+/// Declare the `bitflags`-facing bitflags struct.
+///
+/// This type is part of the `bitflags` crate's public API, but not part of the user's.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __declare_internal_bitflags {
+ (
+ $vis:vis struct $InternalBitFlags:ident: $T:ty
+ ) => {
+ // NOTE: The ABI of this type is _guaranteed_ to be the same as `T`
+ // This is relied on by some external libraries like `bytemuck` to make
+ // its `unsafe` trait impls sound.
+ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+ #[repr(transparent)]
+ $vis struct $InternalBitFlags($T);
+ };
+}
+
+/// Implement functions on the private (bitflags-facing) bitflags type.
+///
+/// Methods and trait implementations can be freely added here without breaking end-users.
+/// If we want to expose new functionality to `#[derive]`, this is the place to do it.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_internal_bitflags {
+ (
+ $InternalBitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt = $value:expr;
+ )*
+ }
+ ) => {
+ // NOTE: This impl is also used to prevent using bits types from non-primitive types
+ // in the `bitflags` macro. If this approach is changed, this guard will need to be
+ // retained somehow
+ impl $crate::__private::PublicFlags for $PublicBitFlags {
+ type Primitive = $T;
+ type Internal = $InternalBitFlags;
+ }
+
+ impl $crate::__private::core::default::Default for $InternalBitFlags {
+ #[inline]
+ fn default() -> Self {
+ $InternalBitFlags::empty()
+ }
+ }
+
+ impl $crate::__private::core::fmt::Debug for $InternalBitFlags {
+ fn fmt(&self, f: &mut $crate::__private::core::fmt::Formatter<'_>) -> $crate::__private::core::fmt::Result {
+ if self.is_empty() {
+ // If no flags are set then write an empty hex flag to avoid
+ // writing an empty string. In some contexts, like serialization,
+ // an empty string is preferable, but it may be unexpected in
+ // others for a format not to produce any output.
+ //
+ // We can remove this `0x0` and remain compatible with `FromStr`,
+ // because an empty string will still parse to an empty set of flags,
+ // just like `0x0` does.
+ $crate::__private::core::write!(f, "{:#x}", <$T as $crate::Bits>::EMPTY)
+ } else {
+ $crate::__private::core::fmt::Display::fmt(self, f)
+ }
+ }
+ }
+
+ impl $crate::__private::core::fmt::Display for $InternalBitFlags {
+ fn fmt(&self, f: &mut $crate::__private::core::fmt::Formatter<'_>) -> $crate::__private::core::fmt::Result {
+ $crate::parser::to_writer(&$PublicBitFlags(*self), f)
+ }
+ }
+
+ impl $crate::__private::core::str::FromStr for $InternalBitFlags {
+ type Err = $crate::parser::ParseError;
+
+ fn from_str(s: &str) -> $crate::__private::core::result::Result<Self, Self::Err> {
+ $crate::parser::from_str::<$PublicBitFlags>(s).map(|flags| flags.0)
+ }
+ }
+
+ impl $crate::__private::core::convert::AsRef<$T> for $InternalBitFlags {
+ fn as_ref(&self) -> &$T {
+ &self.0
+ }
+ }
+
+ impl $crate::__private::core::convert::From<$T> for $InternalBitFlags {
+ fn from(bits: $T) -> Self {
+ Self::from_bits_retain(bits)
+ }
+ }
+
+ // The internal flags type offers a similar API to the public one
+
+ __impl_public_bitflags! {
+ $InternalBitFlags: $T, $PublicBitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag = $value;
+ )*
+ }
+ }
+
+ __impl_public_bitflags_ops! {
+ $InternalBitFlags
+ }
+
+ __impl_public_bitflags_iter! {
+ $InternalBitFlags: $T, $PublicBitFlags
+ }
+
+ impl $InternalBitFlags {
+ /// Returns a mutable reference to the raw value of the flags currently stored.
+ #[inline]
+ pub fn bits_mut(&mut self) -> &mut $T {
+ &mut self.0
+ }
+ }
+ };
+}
diff --git a/extra/bitflags/src/iter.rs b/extra/bitflags/src/iter.rs
new file mode 100644
index 000000000..7f7ce554c
--- /dev/null
+++ b/extra/bitflags/src/iter.rs
@@ -0,0 +1,145 @@
+/*!
+Yield the bits of a source flags value in a set of contained flags values.
+*/
+
+use crate::{Flag, Flags};
+
+/**
+An iterator over flags values.
+
+This iterator will yield flags values for contained, defined flags first, with any remaining bits yielded
+as a final flags value.
+*/
+pub struct Iter<B: 'static> {
+ inner: IterNames<B>,
+ done: bool,
+}
+
+impl<B: Flags> Iter<B> {
+ pub(crate) fn new(flags: &B) -> Self {
+ Iter {
+ inner: IterNames::new(flags),
+ done: false,
+ }
+ }
+}
+
+impl<B: 'static> Iter<B> {
+ // Used by the `bitflags` macro
+ #[doc(hidden)]
+ pub const fn __private_const_new(flags: &'static [Flag<B>], source: B, remaining: B) -> Self {
+ Iter {
+ inner: IterNames::__private_const_new(flags, source, remaining),
+ done: false,
+ }
+ }
+}
+
+impl<B: Flags> Iterator for Iter<B> {
+ type Item = B;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ match self.inner.next() {
+ Some((_, flag)) => Some(flag),
+ None if !self.done => {
+ self.done = true;
+
+ // After iterating through valid names, if there are any bits left over
+ // then return one final value that includes them. This makes `into_iter`
+ // and `from_iter` roundtrip
+ if !self.inner.remaining().is_empty() {
+ Some(B::from_bits_retain(self.inner.remaining.bits()))
+ } else {
+ None
+ }
+ }
+ None => None,
+ }
+ }
+}
+
+/**
+An iterator over flags values.
+
+This iterator only yields flags values for contained, defined, named flags. Any remaining bits
+won't be yielded, but can be found with the [`IterNames::remaining`] method.
+*/
+pub struct IterNames<B: 'static> {
+ flags: &'static [Flag<B>],
+ idx: usize,
+ source: B,
+ remaining: B,
+}
+
+impl<B: Flags> IterNames<B> {
+ pub(crate) fn new(flags: &B) -> Self {
+ IterNames {
+ flags: B::FLAGS,
+ idx: 0,
+ remaining: B::from_bits_retain(flags.bits()),
+ source: B::from_bits_retain(flags.bits()),
+ }
+ }
+}
+
+impl<B: 'static> IterNames<B> {
+ // Used by the bitflags macro
+ #[doc(hidden)]
+ pub const fn __private_const_new(flags: &'static [Flag<B>], source: B, remaining: B) -> Self {
+ IterNames {
+ flags,
+ idx: 0,
+ remaining,
+ source,
+ }
+ }
+
+ /// Get a flags value of any remaining bits that haven't been yielded yet.
+ ///
+ /// Once the iterator has finished, this method can be used to
+ /// check whether or not there are any bits that didn't correspond
+ /// to a contained, defined, named flag remaining.
+ pub fn remaining(&self) -> &B {
+ &self.remaining
+ }
+}
+
+impl<B: Flags> Iterator for IterNames<B> {
+ type Item = (&'static str, B);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ while let Some(flag) = self.flags.get(self.idx) {
+ // Short-circuit if our state is empty
+ if self.remaining.is_empty() {
+ return None;
+ }
+
+ self.idx += 1;
+
+ // Skip unnamed flags
+ if flag.name().is_empty() {
+ continue;
+ }
+
+ let bits = flag.value().bits();
+
+ // If the flag is set in the original source _and_ it has bits that haven't
+ // been covered by a previous flag yet then yield it. These conditions cover
+ // two cases for multi-bit flags:
+ //
+ // 1. When flags partially overlap, such as `0b00000001` and `0b00000101`, we'll
+ // yield both flags.
+ // 2. When flags fully overlap, such as in convenience flags that are a shorthand for others,
+ // we won't yield both flags.
+ if self.source.contains(B::from_bits_retain(bits))
+ && self.remaining.intersects(B::from_bits_retain(bits))
+ {
+ self.remaining.remove(B::from_bits_retain(bits));
+
+ return Some((flag.name(), B::from_bits_retain(bits)));
+ }
+ }
+
+ None
+ }
+}
diff --git a/extra/bitflags/src/lib.rs b/extra/bitflags/src/lib.rs
new file mode 100644
index 000000000..c8aff6873
--- /dev/null
+++ b/extra/bitflags/src/lib.rs
@@ -0,0 +1,921 @@
+// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+/*!
+Generate types for C-style flags with ergonomic APIs.
+
+# Getting started
+
+Add `bitflags` to your `Cargo.toml`:
+
+```toml
+[dependencies.bitflags]
+version = "2.4.1"
+```
+
+## Generating flags types
+
+Use the [`bitflags`] macro to generate flags types:
+
+```rust
+use bitflags::bitflags;
+
+bitflags! {
+ pub struct Flags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+ }
+}
+```
+
+See the docs for the `bitflags` macro for the full syntax.
+
+Also see the [`example_generated`] module for an example of what the `bitflags` macro generates for a flags type.
+
+### Externally defined flags
+
+If you're generating flags types for an external source, such as a C API, you can define
+an extra unnamed flag as a mask of all bits the external source may ever set. Usually this would be all bits (`!0`):
+
+```rust
+# use bitflags::bitflags;
+bitflags! {
+ pub struct Flags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+
+ // The source may set any bits
+ const _ = !0;
+ }
+}
+```
+
+Why should you do this? Generated methods like `all` and truncating operators like `!` only consider
+bits in defined flags. Adding an unnamed flag makes those methods consider additional bits,
+without generating additional constants for them. It helps compatibility when the external source
+may start setting additional bits at any time. The [known and unknown bits](#known-and-unknown-bits)
+section has more details on this behavior.
+
+### Custom derives
+
+You can derive some traits on generated flags types if you enable Cargo features. The following
+libraries are currently supported:
+
+- `serde`: Support `#[derive(Serialize, Deserialize)]`, using text for human-readable formats,
+and a raw number for binary formats.
+- `arbitrary`: Support `#[derive(Arbitrary)]`, only generating flags values with known bits.
+- `bytemuck`: Support `#[derive(Pod, Zeroable)]`, for casting between flags values and their
+underlying bits values.
+
+You can also define your own flags type outside of the [`bitflags`] macro and then use it to generate methods.
+This can be useful if you need a custom `#[derive]` attribute for a library that `bitflags` doesn't
+natively support:
+
+```rust
+# use std::fmt::Debug as SomeTrait;
+# use bitflags::bitflags;
+#[derive(SomeTrait)]
+pub struct Flags(u32);
+
+bitflags! {
+ impl Flags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+ }
+}
+```
+
+### Adding custom methods
+
+The [`bitflags`] macro supports attributes on generated flags types within the macro itself, while
+`impl` blocks can be added outside of it:
+
+```rust
+# use bitflags::bitflags;
+bitflags! {
+ // Attributes can be applied to flags types
+ #[repr(transparent)]
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+ pub struct Flags: u32 {
+ const A = 0b00000001;
+ const B = 0b00000010;
+ const C = 0b00000100;
+ }
+}
+
+// Impl blocks can be added to flags types
+impl Flags {
+ pub fn as_u64(&self) -> u64 {
+ self.bits() as u64
+ }
+}
+```
+
+## Working with flags values
+
+Use generated constants and standard bitwise operators to interact with flags values:
+
+```rust
+# use bitflags::bitflags;
+# bitflags! {
+# #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+# pub struct Flags: u32 {
+# const A = 0b00000001;
+# const B = 0b00000010;
+# const C = 0b00000100;
+# }
+# }
+// union
+let ab = Flags::A | Flags::B;
+
+// intersection
+let a = ab & Flags::A;
+
+// difference
+let b = ab - Flags::A;
+
+// complement
+let c = !ab;
+```
+
+See the docs for the [`Flags`] trait for more details on operators and how they behave.
+
+# Formatting and parsing
+
+`bitflags` defines a text format that can be used to convert any flags value to and from strings.
+
+See the [`parser`] module for more details.
+
+# Specification
+
+The terminology and behavior of generated flags types is
+[specified in the source repository](https://github.com/bitflags/bitflags/blob/main/spec.md).
+Details are repeated in these docs where appropriate, but is exhaustively listed in the spec. Some
+things are worth calling out explicitly here.
+
+## Flags types, flags values, flags
+
+The spec and these docs use consistent terminology to refer to things in the bitflags domain:
+
+- **Bits type**: A type that defines a fixed number of bits at specific locations.
+- **Flag**: A set of bits in a bits type that may have a unique name.
+- **Flags type**: A set of defined flags over a specific bits type.
+- **Flags value**: An instance of a flags type using its specific bits value for storage.
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct FlagsType: u8 {
+// -- Bits type
+// --------- Flags type
+ const A = 1;
+// ----- Flag
+ }
+}
+
+let flag = FlagsType::A;
+// ---- Flags value
+```
+
+## Known and unknown bits
+
+Any bits in a flag you define are called _known bits_. Any other bits are _unknown bits_.
+In the following flags type:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ const C = 1 << 2;
+ }
+}
+```
+
+The known bits are `0b0000_0111` and the unknown bits are `0b1111_1000`.
+
+`bitflags` doesn't guarantee that a flags value will only ever have known bits set, but some operators
+will unset any unknown bits they encounter. In a future version of `bitflags`, all operators will
+unset unknown bits.
+
+If you're using `bitflags` for flags types defined externally, such as from C, you probably want all
+bits to be considered known, in case that external source changes. You can do this using an unnamed
+flag, as described in [externally defined flags](#externally-defined-flags).
+
+## Zero-bit flags
+
+Flags with no bits set should be avoided because they interact strangely with [`Flags::contains`]
+and [`Flags::intersects`]. A zero-bit flag is always contained, but is never intersected. The
+names of zero-bit flags can be parsed, but are never formatted.
+
+## Multi-bit flags
+
+Flags that set multiple bits should be avoided unless each bit is also in a single-bit flag.
+Take the following flags type as an example:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ const B = 1 | 1 << 1;
+ }
+}
+```
+
+The result of `Flags::A ^ Flags::B` is `0b0000_0010`, which doesn't correspond to either
+`Flags::A` or `Flags::B` even though it's still a known bit.
+*/
+
+#![cfg_attr(not(any(feature = "std", test)), no_std)]
+#![cfg_attr(not(test), forbid(unsafe_code))]
+#![cfg_attr(test, allow(mixed_script_confusables))]
+
+#[doc(inline)]
+pub use traits::{Bits, Flag, Flags};
+
+pub mod iter;
+pub mod parser;
+
+mod traits;
+
+#[doc(hidden)]
+pub mod __private {
+ pub use crate::{external::__private::*, traits::__private::*};
+
+ pub use core;
+}
+
+#[allow(unused_imports)]
+pub use external::*;
+
+#[allow(deprecated)]
+pub use traits::BitFlags;
+
+/*
+How does the bitflags crate work?
+
+This library generates a `struct` in the end-user's crate with a bunch of constants on it that represent flags.
+The difference between `bitflags` and a lot of other libraries is that we don't actually control the generated `struct` in the end.
+It's part of the end-user's crate, so it belongs to them. That makes it difficult to extend `bitflags` with new functionality
+because we could end up breaking valid code that was already written.
+
+Our solution is to split the type we generate into two: the public struct owned by the end-user, and an internal struct owned by `bitflags` (us).
+To give you an example, let's say we had a crate that called `bitflags!`:
+
+```rust
+bitflags! {
+ pub struct MyFlags: u32 {
+ const A = 1;
+ const B = 2;
+ }
+}
+```
+
+What they'd end up with looks something like this:
+
+```rust
+pub struct MyFlags(<MyFlags as PublicFlags>::InternalBitFlags);
+
+const _: () = {
+ #[repr(transparent)]
+ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+ pub struct MyInternalBitFlags {
+ bits: u32,
+ }
+
+ impl PublicFlags for MyFlags {
+ type Internal = InternalBitFlags;
+ }
+};
+```
+
+If we want to expose something like a new trait impl for generated flags types, we add it to our generated `MyInternalBitFlags`,
+and let `#[derive]` on `MyFlags` pick up that implementation, if an end-user chooses to add one.
+
+The public API is generated in the `__impl_public_flags!` macro, and the internal API is generated in
+the `__impl_internal_flags!` macro.
+
+The macros are split into 3 modules:
+
+- `public`: where the user-facing flags types are generated.
+- `internal`: where the `bitflags`-facing flags types are generated.
+- `external`: where external library traits are implemented conditionally.
+*/
+
+/**
+Generate a flags type.
+
+# `struct` mode
+
+A declaration that begins with `$vis struct` will generate a `struct` for a flags type, along with
+methods and trait implementations for it. The body of the declaration defines flags as constants,
+where each constant is a flags value of the generated flags type.
+
+## Examples
+
+Generate a flags type using `u8` as the bits type:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ const C = 0b0000_0100;
+ }
+}
+```
+
+Flags types are private by default and accept standard visibility modifiers. Flags themselves
+are always public:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ pub struct Flags: u8 {
+ // Constants are always `pub`
+ const A = 1;
+ }
+}
+```
+
+Flags may refer to other flags using their [`Flags::bits`] value:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ const AB = Flags::A.bits() | Flags::B.bits();
+ }
+}
+```
+
+A single `bitflags` invocation may include zero or more flags type declarations:
+
+```
+# use bitflags::bitflags;
+bitflags! {}
+
+bitflags! {
+ struct Flags1: u8 {
+ const A = 1;
+ }
+
+ struct Flags2: u8 {
+ const A = 1;
+ }
+}
+```
+
+# `impl` mode
+
+A declaration that begins with `impl` will only generate methods and trait implementations for the
+`struct` defined outside of the `bitflags` macro.
+
+The struct itself must be a newtype using the bits type as its field.
+
+The syntax for `impl` mode is identical to `struct` mode besides the starting token.
+
+## Examples
+
+Implement flags methods and traits for a custom flags type using `u8` as its underlying bits type:
+
+```
+# use bitflags::bitflags;
+struct Flags(u8);
+
+bitflags! {
+ impl Flags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ const C = 0b0000_0100;
+ }
+}
+```
+
+# Named and unnamed flags
+
+Constants in the body of a declaration are flags. The identifier of the constant is the name of
+the flag. If the identifier is `_`, then the flag is unnamed. Unnamed flags don't appear in the
+generated API, but affect how bits are truncated.
+
+## Examples
+
+Adding an unnamed flag that makes all bits known:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+
+ const _ = !0;
+ }
+}
+```
+
+Flags types may define multiple unnamed flags:
+
+```
+# use bitflags::bitflags;
+bitflags! {
+ struct Flags: u8 {
+ const _ = 1;
+ const _ = 1 << 1;
+ }
+}
+```
+*/
+#[macro_export(local_inner_macros)]
+macro_rules! bitflags {
+ (
+ $(#[$outer:meta])*
+ $vis:vis struct $BitFlags:ident: $T:ty {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt = $value:expr;
+ )*
+ }
+
+ $($t:tt)*
+ ) => {
+ // Declared in the scope of the `bitflags!` call
+ // This type appears in the end-user's API
+ __declare_public_bitflags! {
+ $(#[$outer])*
+ $vis struct $BitFlags
+ }
+
+ // Workaround for: https://github.com/bitflags/bitflags/issues/320
+ __impl_public_bitflags_consts! {
+ $BitFlags: $T {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag = $value;
+ )*
+ }
+ }
+
+ #[allow(
+ dead_code,
+ deprecated,
+ unused_doc_comments,
+ unused_attributes,
+ unused_mut,
+ unused_imports,
+ non_upper_case_globals,
+ clippy::assign_op_pattern,
+ clippy::indexing_slicing,
+ clippy::same_name_method,
+ clippy::iter_without_into_iter,
+ )]
+ const _: () = {
+ // Declared in a "hidden" scope that can't be reached directly
+ // These types don't appear in the end-user's API
+ __declare_internal_bitflags! {
+ $vis struct InternalBitFlags: $T
+ }
+
+ __impl_internal_bitflags! {
+ InternalBitFlags: $T, $BitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag = $value;
+ )*
+ }
+ }
+
+ // This is where new library trait implementations can be added
+ __impl_external_bitflags! {
+ InternalBitFlags: $T, $BitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag;
+ )*
+ }
+ }
+
+ __impl_public_bitflags_forward! {
+ $BitFlags: $T, InternalBitFlags
+ }
+
+ __impl_public_bitflags_ops! {
+ $BitFlags
+ }
+
+ __impl_public_bitflags_iter! {
+ $BitFlags: $T, $BitFlags
+ }
+ };
+
+ bitflags! {
+ $($t)*
+ }
+ };
+ (
+ impl $BitFlags:ident: $T:ty {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt = $value:expr;
+ )*
+ }
+
+ $($t:tt)*
+ ) => {
+ __impl_public_bitflags_consts! {
+ $BitFlags: $T {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag = $value;
+ )*
+ }
+ }
+
+ #[allow(
+ dead_code,
+ deprecated,
+ unused_doc_comments,
+ unused_attributes,
+ unused_mut,
+ unused_imports,
+ non_upper_case_globals,
+ clippy::assign_op_pattern,
+ clippy::iter_without_into_iter,
+ )]
+ const _: () = {
+ __impl_public_bitflags! {
+ $BitFlags: $T, $BitFlags {
+ $(
+ $(#[$inner $($args)*])*
+ const $Flag = $value;
+ )*
+ }
+ }
+
+ __impl_public_bitflags_ops! {
+ $BitFlags
+ }
+
+ __impl_public_bitflags_iter! {
+ $BitFlags: $T, $BitFlags
+ }
+ };
+
+ bitflags! {
+ $($t)*
+ }
+ };
+ () => {};
+}
+
+/// Implement functions on bitflags types.
+///
+/// We need to be careful about adding new methods and trait implementations here because they
+/// could conflict with items added by the end-user.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_bitflags {
+ (
+ $PublicBitFlags:ident: $T:ty {
+ fn empty() $empty:block
+ fn all() $all:block
+ fn bits($bits0:ident) $bits:block
+ fn from_bits($from_bits0:ident) $from_bits:block
+ fn from_bits_truncate($from_bits_truncate0:ident) $from_bits_truncate:block
+ fn from_bits_retain($from_bits_retain0:ident) $from_bits_retain:block
+ fn from_name($from_name0:ident) $from_name:block
+ fn is_empty($is_empty0:ident) $is_empty:block
+ fn is_all($is_all0:ident) $is_all:block
+ fn intersects($intersects0:ident, $intersects1:ident) $intersects:block
+ fn contains($contains0:ident, $contains1:ident) $contains:block
+ fn insert($insert0:ident, $insert1:ident) $insert:block
+ fn remove($remove0:ident, $remove1:ident) $remove:block
+ fn toggle($toggle0:ident, $toggle1:ident) $toggle:block
+ fn set($set0:ident, $set1:ident, $set2:ident) $set:block
+ fn intersection($intersection0:ident, $intersection1:ident) $intersection:block
+ fn union($union0:ident, $union1:ident) $union:block
+ fn difference($difference0:ident, $difference1:ident) $difference:block
+ fn symmetric_difference($symmetric_difference0:ident, $symmetric_difference1:ident) $symmetric_difference:block
+ fn complement($complement0:ident) $complement:block
+ }
+ ) => {
+ #[allow(dead_code, deprecated, unused_attributes)]
+ impl $PublicBitFlags {
+ /// Get a flags value with all bits unset.
+ #[inline]
+ pub const fn empty() -> Self {
+ $empty
+ }
+
+ /// Get a flags value with all known bits set.
+ #[inline]
+ pub const fn all() -> Self {
+ $all
+ }
+
+ /// Get the underlying bits value.
+ ///
+ /// The returned value is exactly the bits set in this flags value.
+ #[inline]
+ pub const fn bits(&self) -> $T {
+ let $bits0 = self;
+ $bits
+ }
+
+ /// Convert from a bits value.
+ ///
+ /// This method will return `None` if any unknown bits are set.
+ #[inline]
+ pub const fn from_bits(bits: $T) -> $crate::__private::core::option::Option<Self> {
+ let $from_bits0 = bits;
+ $from_bits
+ }
+
+ /// Convert from a bits value, unsetting any unknown bits.
+ #[inline]
+ pub const fn from_bits_truncate(bits: $T) -> Self {
+ let $from_bits_truncate0 = bits;
+ $from_bits_truncate
+ }
+
+ /// Convert from a bits value exactly.
+ #[inline]
+ pub const fn from_bits_retain(bits: $T) -> Self {
+ let $from_bits_retain0 = bits;
+ $from_bits_retain
+ }
+
+ /// Get a flags value with the bits of a flag with the given name set.
+ ///
+ /// This method will return `None` if `name` is empty or doesn't
+ /// correspond to any named flag.
+ #[inline]
+ pub fn from_name(name: &str) -> $crate::__private::core::option::Option<Self> {
+ let $from_name0 = name;
+ $from_name
+ }
+
+ /// Whether all bits in this flags value are unset.
+ #[inline]
+ pub const fn is_empty(&self) -> bool {
+ let $is_empty0 = self;
+ $is_empty
+ }
+
+ /// Whether all known bits in this flags value are set.
+ #[inline]
+ pub const fn is_all(&self) -> bool {
+ let $is_all0 = self;
+ $is_all
+ }
+
+ /// Whether any set bits in a source flags value are also set in a target flags value.
+ #[inline]
+ pub const fn intersects(&self, other: Self) -> bool {
+ let $intersects0 = self;
+ let $intersects1 = other;
+ $intersects
+ }
+
+ /// Whether all set bits in a source flags value are also set in a target flags value.
+ #[inline]
+ pub const fn contains(&self, other: Self) -> bool {
+ let $contains0 = self;
+ let $contains1 = other;
+ $contains
+ }
+
+ /// The bitwise or (`|`) of the bits in two flags values.
+ #[inline]
+ pub fn insert(&mut self, other: Self) {
+ let $insert0 = self;
+ let $insert1 = other;
+ $insert
+ }
+
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `remove` won't truncate `other`, but the `!` operator will.
+ #[inline]
+ pub fn remove(&mut self, other: Self) {
+ let $remove0 = self;
+ let $remove1 = other;
+ $remove
+ }
+
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ #[inline]
+ pub fn toggle(&mut self, other: Self) {
+ let $toggle0 = self;
+ let $toggle1 = other;
+ $toggle
+ }
+
+ /// Call `insert` when `value` is `true` or `remove` when `value` is `false`.
+ #[inline]
+ pub fn set(&mut self, other: Self, value: bool) {
+ let $set0 = self;
+ let $set1 = other;
+ let $set2 = value;
+ $set
+ }
+
+ /// The bitwise and (`&`) of the bits in two flags values.
+ #[inline]
+ #[must_use]
+ pub const fn intersection(self, other: Self) -> Self {
+ let $intersection0 = self;
+ let $intersection1 = other;
+ $intersection
+ }
+
+ /// The bitwise or (`|`) of the bits in two flags values.
+ #[inline]
+ #[must_use]
+ pub const fn union(self, other: Self) -> Self {
+ let $union0 = self;
+ let $union1 = other;
+ $union
+ }
+
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `difference` won't truncate `other`, but the `!` operator will.
+ #[inline]
+ #[must_use]
+ pub const fn difference(self, other: Self) -> Self {
+ let $difference0 = self;
+ let $difference1 = other;
+ $difference
+ }
+
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ #[inline]
+ #[must_use]
+ pub const fn symmetric_difference(self, other: Self) -> Self {
+ let $symmetric_difference0 = self;
+ let $symmetric_difference1 = other;
+ $symmetric_difference
+ }
+
+ /// The bitwise negation (`!`) of the bits in a flags value, truncating the result.
+ #[inline]
+ #[must_use]
+ pub const fn complement(self) -> Self {
+ let $complement0 = self;
+ $complement
+ }
+ }
+ };
+}
+
+/// A macro that processed the input to `bitflags!` and shuffles attributes around
+/// based on whether or not they're "expression-safe".
+///
+/// This macro is a token-tree muncher that works on 2 levels:
+///
+/// For each attribute, we explicitly match on its identifier, like `cfg` to determine
+/// whether or not it should be considered expression-safe.
+///
+/// If you find yourself with an attribute that should be considered expression-safe
+/// and isn't, it can be added here.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __bitflags_expr_safe_attrs {
+ // Entrypoint: Move all flags and all attributes into `unprocessed` lists
+ // where they'll be munched one-at-a-time
+ (
+ $(#[$inner:ident $($args:tt)*])*
+ { $e:expr }
+ ) => {
+ __bitflags_expr_safe_attrs! {
+ expr: { $e },
+ attrs: {
+ // All attributes start here
+ unprocessed: [$(#[$inner $($args)*])*],
+ // Attributes that are safe on expressions go here
+ processed: [],
+ },
+ }
+ };
+ // Process the next attribute on the current flag
+ // `cfg`: The next flag should be propagated to expressions
+ // NOTE: You can copy this rules block and replace `cfg` with
+ // your attribute name that should be considered expression-safe
+ (
+ expr: { $e:expr },
+ attrs: {
+ unprocessed: [
+ // cfg matched here
+ #[cfg $($args:tt)*]
+ $($attrs_rest:tt)*
+ ],
+ processed: [$($expr:tt)*],
+ },
+ ) => {
+ __bitflags_expr_safe_attrs! {
+ expr: { $e },
+ attrs: {
+ unprocessed: [
+ $($attrs_rest)*
+ ],
+ processed: [
+ $($expr)*
+ // cfg added here
+ #[cfg $($args)*]
+ ],
+ },
+ }
+ };
+ // Process the next attribute on the current flag
+ // `$other`: The next flag should not be propagated to expressions
+ (
+ expr: { $e:expr },
+ attrs: {
+ unprocessed: [
+ // $other matched here
+ #[$other:ident $($args:tt)*]
+ $($attrs_rest:tt)*
+ ],
+ processed: [$($expr:tt)*],
+ },
+ ) => {
+ __bitflags_expr_safe_attrs! {
+ expr: { $e },
+ attrs: {
+ unprocessed: [
+ $($attrs_rest)*
+ ],
+ processed: [
+ // $other not added here
+ $($expr)*
+ ],
+ },
+ }
+ };
+ // Once all attributes on all flags are processed, generate the actual code
+ (
+ expr: { $e:expr },
+ attrs: {
+ unprocessed: [],
+ processed: [$(#[$expr:ident $($exprargs:tt)*])*],
+ },
+ ) => {
+ $(#[$expr $($exprargs)*])*
+ { $e }
+ }
+}
+
+/// Implement a flag, which may be a wildcard `_`.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __bitflags_flag {
+ (
+ {
+ name: _,
+ named: { $($named:tt)* },
+ unnamed: { $($unnamed:tt)* },
+ }
+ ) => {
+ $($unnamed)*
+ };
+ (
+ {
+ name: $Flag:ident,
+ named: { $($named:tt)* },
+ unnamed: { $($unnamed:tt)* },
+ }
+ ) => {
+ $($named)*
+ };
+}
+
+#[macro_use]
+mod public;
+#[macro_use]
+mod internal;
+#[macro_use]
+mod external;
+
+#[cfg(feature = "example_generated")]
+pub mod example_generated;
+
+#[cfg(test)]
+mod tests;
diff --git a/extra/bitflags/src/parser.rs b/extra/bitflags/src/parser.rs
new file mode 100644
index 000000000..130dc2e1f
--- /dev/null
+++ b/extra/bitflags/src/parser.rs
@@ -0,0 +1,247 @@
+/*!
+Parsing flags from text.
+
+Format and parse a flags value as text using the following grammar:
+
+- _Flags:_ (_Whitespace_ _Flag_ _Whitespace_)`|`*
+- _Flag:_ _Name_ | _Hex Number_
+- _Name:_ The name of any defined flag
+- _Hex Number_: `0x`([0-9a-fA-F])*
+- _Whitespace_: (\s)*
+
+As an example, this is how `Flags::A | Flags::B | 0x0c` can be represented as text:
+
+```text
+A | B | 0x0c
+```
+
+Alternatively, it could be represented without whitespace:
+
+```text
+A|B|0x0C
+```
+
+Note that identifiers are *case-sensitive*, so the following is *not equivalent*:
+
+```text
+a|b|0x0C
+```
+*/
+
+#![allow(clippy::let_unit_value)]
+
+use core::fmt::{self, Write};
+
+use crate::{Bits, Flags};
+
+/**
+Write a flags value as text.
+
+Any bits that aren't part of a contained flag will be formatted as a hex number.
+*/
+pub fn to_writer<B: Flags>(flags: &B, mut writer: impl Write) -> Result<(), fmt::Error>
+where
+ B::Bits: WriteHex,
+{
+ // A formatter for bitflags that produces text output like:
+ //
+ // A | B | 0xf6
+ //
+ // The names of set flags are written in a bar-separated-format,
+ // followed by a hex number of any remaining bits that are set
+ // but don't correspond to any flags.
+
+ // Iterate over known flag values
+ let mut first = true;
+ let mut iter = flags.iter_names();
+ for (name, _) in &mut iter {
+ if !first {
+ writer.write_str(" | ")?;
+ }
+
+ first = false;
+ writer.write_str(name)?;
+ }
+
+ // Append any extra bits that correspond to flags to the end of the format
+ let remaining = iter.remaining().bits();
+ if remaining != B::Bits::EMPTY {
+ if !first {
+ writer.write_str(" | ")?;
+ }
+
+ writer.write_str("0x")?;
+ remaining.write_hex(writer)?;
+ }
+
+ fmt::Result::Ok(())
+}
+
+pub(crate) struct AsDisplay<'a, B>(pub(crate) &'a B);
+
+impl<'a, B: Flags> fmt::Display for AsDisplay<'a, B>
+where
+ B::Bits: WriteHex,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ to_writer(self.0, f)
+ }
+}
+
+/**
+Parse a flags value from text.
+
+This function will fail on any names that don't correspond to defined flags.
+Unknown bits will be retained.
+*/
+pub fn from_str<B: Flags>(input: &str) -> Result<B, ParseError>
+where
+ B::Bits: ParseHex,
+{
+ let mut parsed_flags = B::empty();
+
+ // If the input is empty then return an empty set of flags
+ if input.trim().is_empty() {
+ return Ok(parsed_flags);
+ }
+
+ for flag in input.split('|') {
+ let flag = flag.trim();
+
+ // If the flag is empty then we've got missing input
+ if flag.is_empty() {
+ return Err(ParseError::empty_flag());
+ }
+
+ // If the flag starts with `0x` then it's a hex number
+ // Parse it directly to the underlying bits type
+ let parsed_flag = if let Some(flag) = flag.strip_prefix("0x") {
+ let bits =
+ <B::Bits>::parse_hex(flag).map_err(|_| ParseError::invalid_hex_flag(flag))?;
+
+ B::from_bits_retain(bits)
+ }
+ // Otherwise the flag is a name
+ // The generated flags type will determine whether
+ // or not it's a valid identifier
+ else {
+ B::from_name(flag).ok_or_else(|| ParseError::invalid_named_flag(flag))?
+ };
+
+ parsed_flags.insert(parsed_flag);
+ }
+
+ Ok(parsed_flags)
+}
+
+/**
+Encode a value as a hex string.
+
+Implementors of this trait should not write the `0x` prefix.
+*/
+pub trait WriteHex {
+ /// Write the value as hex.
+ fn write_hex<W: fmt::Write>(&self, writer: W) -> fmt::Result;
+}
+
+/**
+Parse a value from a hex string.
+*/
+pub trait ParseHex {
+ /// Parse the value from hex.
+ fn parse_hex(input: &str) -> Result<Self, ParseError>
+ where
+ Self: Sized;
+}
+
+/// An error encountered while parsing flags from text.
+#[derive(Debug)]
+pub struct ParseError(ParseErrorKind);
+
+#[derive(Debug)]
+#[allow(clippy::enum_variant_names)]
+enum ParseErrorKind {
+ EmptyFlag,
+ InvalidNamedFlag {
+ #[cfg(not(feature = "std"))]
+ got: (),
+ #[cfg(feature = "std")]
+ got: String,
+ },
+ InvalidHexFlag {
+ #[cfg(not(feature = "std"))]
+ got: (),
+ #[cfg(feature = "std")]
+ got: String,
+ },
+}
+
+impl ParseError {
+ /// An invalid hex flag was encountered.
+ pub fn invalid_hex_flag(flag: impl fmt::Display) -> Self {
+ let _flag = flag;
+
+ let got = {
+ #[cfg(feature = "std")]
+ {
+ _flag.to_string()
+ }
+ };
+
+ ParseError(ParseErrorKind::InvalidHexFlag { got })
+ }
+
+ /// A named flag that doesn't correspond to any on the flags type was encountered.
+ pub fn invalid_named_flag(flag: impl fmt::Display) -> Self {
+ let _flag = flag;
+
+ let got = {
+ #[cfg(feature = "std")]
+ {
+ _flag.to_string()
+ }
+ };
+
+ ParseError(ParseErrorKind::InvalidNamedFlag { got })
+ }
+
+ /// A hex or named flag wasn't found between separators.
+ pub const fn empty_flag() -> Self {
+ ParseError(ParseErrorKind::EmptyFlag)
+ }
+}
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.0 {
+ ParseErrorKind::InvalidNamedFlag { got } => {
+ let _got = got;
+
+ write!(f, "unrecognized named flag")?;
+
+ #[cfg(feature = "std")]
+ {
+ write!(f, " `{}`", _got)?;
+ }
+ }
+ ParseErrorKind::InvalidHexFlag { got } => {
+ let _got = got;
+
+ write!(f, "invalid hex flag")?;
+
+ #[cfg(feature = "std")]
+ {
+ write!(f, " `{}`", _got)?;
+ }
+ }
+ ParseErrorKind::EmptyFlag => {
+ write!(f, "encountered empty flag")?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseError {}
diff --git a/extra/bitflags/src/public.rs b/extra/bitflags/src/public.rs
new file mode 100644
index 000000000..967e0dacb
--- /dev/null
+++ b/extra/bitflags/src/public.rs
@@ -0,0 +1,543 @@
+//! Generate the user-facing flags type.
+//!
+//! The code here belongs to the end-user, so new trait implementations and methods can't be
+//! added without potentially breaking users.
+
+/// Declare the user-facing bitflags struct.
+///
+/// This type is guaranteed to be a newtype with a `bitflags`-facing type as its single field.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __declare_public_bitflags {
+ (
+ $(#[$outer:meta])*
+ $vis:vis struct $PublicBitFlags:ident
+ ) => {
+ $(#[$outer])*
+ $vis struct $PublicBitFlags(<$PublicBitFlags as $crate::__private::PublicFlags>::Internal);
+ };
+}
+
+/// Implement functions on the public (user-facing) bitflags type.
+///
+/// We need to be careful about adding new methods and trait implementations here because they
+/// could conflict with items added by the end-user.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_public_bitflags_forward {
+ (
+ $PublicBitFlags:ident: $T:ty, $InternalBitFlags:ident
+ ) => {
+ __impl_bitflags! {
+ $PublicBitFlags: $T {
+ fn empty() {
+ Self($InternalBitFlags::empty())
+ }
+
+ fn all() {
+ Self($InternalBitFlags::all())
+ }
+
+ fn bits(f) {
+ f.0.bits()
+ }
+
+ fn from_bits(bits) {
+ match $InternalBitFlags::from_bits(bits) {
+ $crate::__private::core::option::Option::Some(bits) => $crate::__private::core::option::Option::Some(Self(bits)),
+ $crate::__private::core::option::Option::None => $crate::__private::core::option::Option::None,
+ }
+ }
+
+ fn from_bits_truncate(bits) {
+ Self($InternalBitFlags::from_bits_truncate(bits))
+ }
+
+ fn from_bits_retain(bits) {
+ Self($InternalBitFlags::from_bits_retain(bits))
+ }
+
+ fn from_name(name) {
+ match $InternalBitFlags::from_name(name) {
+ $crate::__private::core::option::Option::Some(bits) => $crate::__private::core::option::Option::Some(Self(bits)),
+ $crate::__private::core::option::Option::None => $crate::__private::core::option::Option::None,
+ }
+ }
+
+ fn is_empty(f) {
+ f.0.is_empty()
+ }
+
+ fn is_all(f) {
+ f.0.is_all()
+ }
+
+ fn intersects(f, other) {
+ f.0.intersects(other.0)
+ }
+
+ fn contains(f, other) {
+ f.0.contains(other.0)
+ }
+
+ fn insert(f, other) {
+ f.0.insert(other.0)
+ }
+
+ fn remove(f, other) {
+ f.0.remove(other.0)
+ }
+
+ fn toggle(f, other) {
+ f.0.toggle(other.0)
+ }
+
+ fn set(f, other, value) {
+ f.0.set(other.0, value)
+ }
+
+ fn intersection(f, other) {
+ Self(f.0.intersection(other.0))
+ }
+
+ fn union(f, other) {
+ Self(f.0.union(other.0))
+ }
+
+ fn difference(f, other) {
+ Self(f.0.difference(other.0))
+ }
+
+ fn symmetric_difference(f, other) {
+ Self(f.0.symmetric_difference(other.0))
+ }
+
+ fn complement(f) {
+ Self(f.0.complement())
+ }
+ }
+ }
+ };
+}
+
+/// Implement functions on the public (user-facing) bitflags type.
+///
+/// We need to be careful about adding new methods and trait implementations here because they
+/// could conflict with items added by the end-user.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_public_bitflags {
+ (
+ $BitFlags:ident: $T:ty, $PublicBitFlags:ident {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt = $value:expr;
+ )*
+ }
+ ) => {
+ __impl_bitflags! {
+ $BitFlags: $T {
+ fn empty() {
+ Self(<$T as $crate::Bits>::EMPTY)
+ }
+
+ fn all() {
+ let mut truncated = <$T as $crate::Bits>::EMPTY;
+ let mut i = 0;
+
+ $(
+ __bitflags_expr_safe_attrs!(
+ $(#[$inner $($args)*])*
+ {{
+ let flag = <$PublicBitFlags as $crate::Flags>::FLAGS[i].value().bits();
+
+ truncated = truncated | flag;
+ i += 1;
+ }}
+ );
+ )*
+
+ let _ = i;
+ Self::from_bits_retain(truncated)
+ }
+
+ fn bits(f) {
+ f.0
+ }
+
+ fn from_bits(bits) {
+ let truncated = Self::from_bits_truncate(bits).0;
+
+ if truncated == bits {
+ $crate::__private::core::option::Option::Some(Self(bits))
+ } else {
+ $crate::__private::core::option::Option::None
+ }
+ }
+
+ fn from_bits_truncate(bits) {
+ Self(bits & Self::all().bits())
+ }
+
+ fn from_bits_retain(bits) {
+ Self(bits)
+ }
+
+ fn from_name(name) {
+ $(
+ __bitflags_flag!({
+ name: $Flag,
+ named: {
+ __bitflags_expr_safe_attrs!(
+ $(#[$inner $($args)*])*
+ {
+ if name == $crate::__private::core::stringify!($Flag) {
+ return $crate::__private::core::option::Option::Some(Self($PublicBitFlags::$Flag.bits()));
+ }
+ }
+ );
+ },
+ unnamed: {},
+ });
+ )*
+
+ let _ = name;
+ $crate::__private::core::option::Option::None
+ }
+
+ fn is_empty(f) {
+ f.bits() == <$T as $crate::Bits>::EMPTY
+ }
+
+ fn is_all(f) {
+ // NOTE: We check against `Self::all` here, not `Self::Bits::ALL`
+ // because the set of all flags may not use all bits
+ Self::all().bits() | f.bits() == f.bits()
+ }
+
+ fn intersects(f, other) {
+ f.bits() & other.bits() != <$T as $crate::Bits>::EMPTY
+ }
+
+ fn contains(f, other) {
+ f.bits() & other.bits() == other.bits()
+ }
+
+ fn insert(f, other) {
+ *f = Self::from_bits_retain(f.bits()).union(other);
+ }
+
+ fn remove(f, other) {
+ *f = Self::from_bits_retain(f.bits()).difference(other);
+ }
+
+ fn toggle(f, other) {
+ *f = Self::from_bits_retain(f.bits()).symmetric_difference(other);
+ }
+
+ fn set(f, other, value) {
+ if value {
+ f.insert(other);
+ } else {
+ f.remove(other);
+ }
+ }
+
+ fn intersection(f, other) {
+ Self::from_bits_retain(f.bits() & other.bits())
+ }
+
+ fn union(f, other) {
+ Self::from_bits_retain(f.bits() | other.bits())
+ }
+
+ fn difference(f, other) {
+ Self::from_bits_retain(f.bits() & !other.bits())
+ }
+
+ fn symmetric_difference(f, other) {
+ Self::from_bits_retain(f.bits() ^ other.bits())
+ }
+
+ fn complement(f) {
+ Self::from_bits_truncate(!f.bits())
+ }
+ }
+ }
+ };
+}
+
+/// Implement iterators on the public (user-facing) bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_public_bitflags_iter {
+ ($BitFlags:ident: $T:ty, $PublicBitFlags:ident) => {
+ impl $BitFlags {
+ /// Yield a set of contained flags values.
+ ///
+ /// Each yielded flags value will correspond to a defined named flag. Any unknown bits
+ /// will be yielded together as a final flags value.
+ #[inline]
+ pub const fn iter(&self) -> $crate::iter::Iter<$PublicBitFlags> {
+ $crate::iter::Iter::__private_const_new(
+ <$PublicBitFlags as $crate::Flags>::FLAGS,
+ $PublicBitFlags::from_bits_retain(self.bits()),
+ $PublicBitFlags::from_bits_retain(self.bits()),
+ )
+ }
+
+ /// Yield a set of contained named flags values.
+ ///
+ /// This method is like [`iter`](#method.iter), except only yields bits in contained named flags.
+ /// Any unknown bits, or bits not corresponding to a contained flag will not be yielded.
+ #[inline]
+ pub const fn iter_names(&self) -> $crate::iter::IterNames<$PublicBitFlags> {
+ $crate::iter::IterNames::__private_const_new(
+ <$PublicBitFlags as $crate::Flags>::FLAGS,
+ $PublicBitFlags::from_bits_retain(self.bits()),
+ $PublicBitFlags::from_bits_retain(self.bits()),
+ )
+ }
+ }
+
+ impl $crate::__private::core::iter::IntoIterator for $BitFlags {
+ type Item = $PublicBitFlags;
+ type IntoIter = $crate::iter::Iter<$PublicBitFlags>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+ }
+ };
+}
+
+/// Implement traits on the public (user-facing) bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_public_bitflags_ops {
+ ($PublicBitFlags:ident) => {
+ impl $crate::__private::core::fmt::Binary for $PublicBitFlags {
+ fn fmt(
+ &self,
+ f: &mut $crate::__private::core::fmt::Formatter,
+ ) -> $crate::__private::core::fmt::Result {
+ $crate::__private::core::fmt::Binary::fmt(&self.0, f)
+ }
+ }
+
+ impl $crate::__private::core::fmt::Octal for $PublicBitFlags {
+ fn fmt(
+ &self,
+ f: &mut $crate::__private::core::fmt::Formatter,
+ ) -> $crate::__private::core::fmt::Result {
+ $crate::__private::core::fmt::Octal::fmt(&self.0, f)
+ }
+ }
+
+ impl $crate::__private::core::fmt::LowerHex for $PublicBitFlags {
+ fn fmt(
+ &self,
+ f: &mut $crate::__private::core::fmt::Formatter,
+ ) -> $crate::__private::core::fmt::Result {
+ $crate::__private::core::fmt::LowerHex::fmt(&self.0, f)
+ }
+ }
+
+ impl $crate::__private::core::fmt::UpperHex for $PublicBitFlags {
+ fn fmt(
+ &self,
+ f: &mut $crate::__private::core::fmt::Formatter,
+ ) -> $crate::__private::core::fmt::Result {
+ $crate::__private::core::fmt::UpperHex::fmt(&self.0, f)
+ }
+ }
+
+ impl $crate::__private::core::ops::BitOr for $PublicBitFlags {
+ type Output = Self;
+
+ /// The bitwise or (`|`) of the bits in two flags values.
+ #[inline]
+ fn bitor(self, other: $PublicBitFlags) -> Self {
+ self.union(other)
+ }
+ }
+
+ impl $crate::__private::core::ops::BitOrAssign for $PublicBitFlags {
+ /// The bitwise or (`|`) of the bits in two flags values.
+ #[inline]
+ fn bitor_assign(&mut self, other: Self) {
+ self.insert(other);
+ }
+ }
+
+ impl $crate::__private::core::ops::BitXor for $PublicBitFlags {
+ type Output = Self;
+
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ #[inline]
+ fn bitxor(self, other: Self) -> Self {
+ self.symmetric_difference(other)
+ }
+ }
+
+ impl $crate::__private::core::ops::BitXorAssign for $PublicBitFlags {
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ #[inline]
+ fn bitxor_assign(&mut self, other: Self) {
+ self.toggle(other);
+ }
+ }
+
+ impl $crate::__private::core::ops::BitAnd for $PublicBitFlags {
+ type Output = Self;
+
+ /// The bitwise and (`&`) of the bits in two flags values.
+ #[inline]
+ fn bitand(self, other: Self) -> Self {
+ self.intersection(other)
+ }
+ }
+
+ impl $crate::__private::core::ops::BitAndAssign for $PublicBitFlags {
+ /// The bitwise and (`&`) of the bits in two flags values.
+ #[inline]
+ fn bitand_assign(&mut self, other: Self) {
+ *self = Self::from_bits_retain(self.bits()).intersection(other);
+ }
+ }
+
+ impl $crate::__private::core::ops::Sub for $PublicBitFlags {
+ type Output = Self;
+
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `difference` won't truncate `other`, but the `!` operator will.
+ #[inline]
+ fn sub(self, other: Self) -> Self {
+ self.difference(other)
+ }
+ }
+
+ impl $crate::__private::core::ops::SubAssign for $PublicBitFlags {
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `difference` won't truncate `other`, but the `!` operator will.
+ #[inline]
+ fn sub_assign(&mut self, other: Self) {
+ self.remove(other);
+ }
+ }
+
+ impl $crate::__private::core::ops::Not for $PublicBitFlags {
+ type Output = Self;
+
+ /// The bitwise negation (`!`) of the bits in a flags value, truncating the result.
+ #[inline]
+ fn not(self) -> Self {
+ self.complement()
+ }
+ }
+
+ impl $crate::__private::core::iter::Extend<$PublicBitFlags> for $PublicBitFlags {
+ /// The bitwise or (`|`) of the bits in each flags value.
+ fn extend<T: $crate::__private::core::iter::IntoIterator<Item = Self>>(
+ &mut self,
+ iterator: T,
+ ) {
+ for item in iterator {
+ self.insert(item)
+ }
+ }
+ }
+
+ impl $crate::__private::core::iter::FromIterator<$PublicBitFlags> for $PublicBitFlags {
+ /// The bitwise or (`|`) of the bits in each flags value.
+ fn from_iter<T: $crate::__private::core::iter::IntoIterator<Item = Self>>(
+ iterator: T,
+ ) -> Self {
+ use $crate::__private::core::iter::Extend;
+
+ let mut result = Self::empty();
+ result.extend(iterator);
+ result
+ }
+ }
+ };
+}
+
+/// Implement constants on the public (user-facing) bitflags type.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __impl_public_bitflags_consts {
+ (
+ $PublicBitFlags:ident: $T:ty {
+ $(
+ $(#[$inner:ident $($args:tt)*])*
+ const $Flag:tt = $value:expr;
+ )*
+ }
+ ) => {
+ impl $PublicBitFlags {
+ $(
+ __bitflags_flag!({
+ name: $Flag,
+ named: {
+ $(#[$inner $($args)*])*
+ #[allow(
+ deprecated,
+ non_upper_case_globals,
+ )]
+ pub const $Flag: Self = Self::from_bits_retain($value);
+ },
+ unnamed: {},
+ });
+ )*
+ }
+
+ impl $crate::Flags for $PublicBitFlags {
+ const FLAGS: &'static [$crate::Flag<$PublicBitFlags>] = &[
+ $(
+ __bitflags_flag!({
+ name: $Flag,
+ named: {
+ __bitflags_expr_safe_attrs!(
+ $(#[$inner $($args)*])*
+ {
+ #[allow(
+ deprecated,
+ non_upper_case_globals,
+ )]
+ $crate::Flag::new($crate::__private::core::stringify!($Flag), $PublicBitFlags::$Flag)
+ }
+ )
+ },
+ unnamed: {
+ __bitflags_expr_safe_attrs!(
+ $(#[$inner $($args)*])*
+ {
+ #[allow(
+ deprecated,
+ non_upper_case_globals,
+ )]
+ $crate::Flag::new("", $PublicBitFlags::from_bits_retain($value))
+ }
+ )
+ },
+ }),
+ )*
+ ];
+
+ type Bits = $T;
+
+ fn bits(&self) -> $T {
+ $PublicBitFlags::bits(self)
+ }
+
+ fn from_bits_retain(bits: $T) -> $PublicBitFlags {
+ $PublicBitFlags::from_bits_retain(bits)
+ }
+ }
+ };
+}
diff --git a/extra/bitflags/src/tests.rs b/extra/bitflags/src/tests.rs
new file mode 100644
index 000000000..ed52ad404
--- /dev/null
+++ b/extra/bitflags/src/tests.rs
@@ -0,0 +1,131 @@
+mod all;
+mod bits;
+mod complement;
+mod contains;
+mod difference;
+mod empty;
+mod eq;
+mod extend;
+mod flags;
+mod fmt;
+mod from_bits;
+mod from_bits_retain;
+mod from_bits_truncate;
+mod from_name;
+mod insert;
+mod intersection;
+mod intersects;
+mod is_all;
+mod is_empty;
+mod iter;
+mod parser;
+mod remove;
+mod symmetric_difference;
+mod union;
+
+bitflags! {
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestFlags: u8 {
+ /// 1
+ const A = 1;
+
+ /// 1 << 1
+ const B = 1 << 1;
+
+ /// 1 << 2
+ const C = 1 << 2;
+
+ /// 1 | (1 << 1) | (1 << 2)
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestFlagsInvert: u8 {
+ /// 1 | (1 << 1) | (1 << 2)
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+
+ /// 1
+ const A = 1;
+
+ /// 1 << 1
+ const B = 1 << 1;
+
+ /// 1 << 2
+ const C = 1 << 2;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestZero: u8 {
+ /// 0
+ const ZERO = 0;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestZeroOne: u8 {
+ /// 0
+ const ZERO = 0;
+
+ /// 1
+ const ONE = 1;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestUnicode: u8 {
+ /// 1
+ const 一 = 1;
+
+ /// 2
+ const 二 = 1 << 1;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestEmpty: u8 {}
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestOverlapping: u8 {
+ /// 1 | (1 << 1)
+ const AB = 1 | (1 << 1);
+
+ /// (1 << 1) | (1 << 2)
+ const BC = (1 << 1) | (1 << 2);
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestOverlappingFull: u8 {
+ /// 1
+ const A = 1;
+
+ /// 1
+ const B = 1;
+
+ /// 1
+ const C = 1;
+
+ /// 2
+ const D = 1 << 1;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestExternal: u8 {
+ /// 1
+ const A = 1;
+
+ /// 1 << 1
+ const B = 1 << 1;
+
+ /// 1 << 2
+ const C = 1 << 2;
+
+ /// 1 | (1 << 1) | (1 << 2)
+ const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();
+
+ /// External
+ const _ = !0;
+ }
+
+ #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
+ pub struct TestExternalFull: u8 {
+ /// External
+ const _ = !0;
+ }
+}
diff --git a/extra/bitflags/src/traits.rs b/extra/bitflags/src/traits.rs
new file mode 100644
index 000000000..28235142d
--- /dev/null
+++ b/extra/bitflags/src/traits.rs
@@ -0,0 +1,430 @@
+use core::{
+ fmt,
+ ops::{BitAnd, BitOr, BitXor, Not},
+};
+
+use crate::{
+ iter,
+ parser::{ParseError, ParseHex, WriteHex},
+};
+
+/**
+A defined flags value that may be named or unnamed.
+*/
+pub struct Flag<B> {
+ name: &'static str,
+ value: B,
+}
+
+impl<B> Flag<B> {
+ /**
+ Define a flag.
+
+ If `name` is non-empty then the flag is named, otherwise it's unnamed.
+ */
+ pub const fn new(name: &'static str, value: B) -> Self {
+ Flag { name, value }
+ }
+
+ /**
+ Get the name of this flag.
+
+ If the flag is unnamed then the returned string will be empty.
+ */
+ pub const fn name(&self) -> &'static str {
+ self.name
+ }
+
+ /**
+ Get the flags value of this flag.
+ */
+ pub const fn value(&self) -> &B {
+ &self.value
+ }
+
+ /**
+ Whether the flag is named.
+
+ If [`Flag::name`] returns a non-empty string then this method will return `true`.
+ */
+ pub const fn is_named(&self) -> bool {
+ !self.name.is_empty()
+ }
+
+ /**
+ Whether the flag is unnamed.
+
+ If [`Flag::name`] returns a non-empty string then this method will return `false`.
+ */
+ pub const fn is_unnamed(&self) -> bool {
+ self.name.is_empty()
+ }
+}
+
+/**
+A set of defined flags using a bits type as storage.
+
+## Implementing `Flags`
+
+This trait is implemented by the [`bitflags`](macro.bitflags.html) macro:
+
+```
+use bitflags::bitflags;
+
+bitflags! {
+ struct MyFlags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ }
+}
+```
+
+It can also be implemented manually:
+
+```
+use bitflags::{Flag, Flags};
+
+struct MyFlags(u8);
+
+impl Flags for MyFlags {
+ const FLAGS: &'static [Flag<Self>] = &[
+ Flag::new("A", MyFlags(1)),
+ Flag::new("B", MyFlags(1 << 1)),
+ ];
+
+ type Bits = u8;
+
+ fn from_bits_retain(bits: Self::Bits) -> Self {
+ MyFlags(bits)
+ }
+
+ fn bits(&self) -> Self::Bits {
+ self.0
+ }
+}
+```
+
+## Using `Flags`
+
+The `Flags` trait can be used generically to work with any flags types. In this example,
+we can count the number of defined named flags:
+
+```
+# use bitflags::{bitflags, Flags};
+fn defined_flags<F: Flags>() -> usize {
+ F::FLAGS.iter().filter(|f| f.is_named()).count()
+}
+
+bitflags! {
+ struct MyFlags: u8 {
+ const A = 1;
+ const B = 1 << 1;
+ const C = 1 << 2;
+
+ const _ = !0;
+ }
+}
+
+assert_eq!(3, defined_flags::<MyFlags>());
+```
+*/
+pub trait Flags: Sized + 'static {
+ /// The set of defined flags.
+ const FLAGS: &'static [Flag<Self>];
+
+ /// The underlying bits type.
+ type Bits: Bits;
+
+ /// Get a flags value with all bits unset.
+ fn empty() -> Self {
+ Self::from_bits_retain(Self::Bits::EMPTY)
+ }
+
+ /// Get a flags value with all known bits set.
+ fn all() -> Self {
+ let mut truncated = Self::Bits::EMPTY;
+
+ for flag in Self::FLAGS.iter() {
+ truncated = truncated | flag.value().bits();
+ }
+
+ Self::from_bits_retain(truncated)
+ }
+
+ /// Get the underlying bits value.
+ ///
+ /// The returned value is exactly the bits set in this flags value.
+ fn bits(&self) -> Self::Bits;
+
+ /// Convert from a bits value.
+ ///
+ /// This method will return `None` if any unknown bits are set.
+ fn from_bits(bits: Self::Bits) -> Option<Self> {
+ let truncated = Self::from_bits_truncate(bits);
+
+ if truncated.bits() == bits {
+ Some(truncated)
+ } else {
+ None
+ }
+ }
+
+ /// Convert from a bits value, unsetting any unknown bits.
+ fn from_bits_truncate(bits: Self::Bits) -> Self {
+ Self::from_bits_retain(bits & Self::all().bits())
+ }
+
+ /// Convert from a bits value exactly.
+ fn from_bits_retain(bits: Self::Bits) -> Self;
+
+ /// Get a flags value with the bits of a flag with the given name set.
+ ///
+ /// This method will return `None` if `name` is empty or doesn't
+ /// correspond to any named flag.
+ fn from_name(name: &str) -> Option<Self> {
+ // Don't parse empty names as empty flags
+ if name.is_empty() {
+ return None;
+ }
+
+ for flag in Self::FLAGS {
+ if flag.name() == name {
+ return Some(Self::from_bits_retain(flag.value().bits()));
+ }
+ }
+
+ None
+ }
+
+ /// Yield a set of contained flags values.
+ ///
+ /// Each yielded flags value will correspond to a defined named flag. Any unknown bits
+ /// will be yielded together as a final flags value.
+ fn iter(&self) -> iter::Iter<Self> {
+ iter::Iter::new(self)
+ }
+
+ /// Yield a set of contained named flags values.
+ ///
+ /// This method is like [`Flags::iter`], except only yields bits in contained named flags.
+ /// Any unknown bits, or bits not corresponding to a contained flag will not be yielded.
+ fn iter_names(&self) -> iter::IterNames<Self> {
+ iter::IterNames::new(self)
+ }
+
+ /// Whether all bits in this flags value are unset.
+ fn is_empty(&self) -> bool {
+ self.bits() == Self::Bits::EMPTY
+ }
+
+ /// Whether all known bits in this flags value are set.
+ fn is_all(&self) -> bool {
+ // NOTE: We check against `Self::all` here, not `Self::Bits::ALL`
+ // because the set of all flags may not use all bits
+ Self::all().bits() | self.bits() == self.bits()
+ }
+
+ /// Whether any set bits in a source flags value are also set in a target flags value.
+ fn intersects(&self, other: Self) -> bool
+ where
+ Self: Sized,
+ {
+ self.bits() & other.bits() != Self::Bits::EMPTY
+ }
+
+ /// Whether all set bits in a source flags value are also set in a target flags value.
+ fn contains(&self, other: Self) -> bool
+ where
+ Self: Sized,
+ {
+ self.bits() & other.bits() == other.bits()
+ }
+
+ /// The bitwise or (`|`) of the bits in two flags values.
+ fn insert(&mut self, other: Self)
+ where
+ Self: Sized,
+ {
+ *self = Self::from_bits_retain(self.bits()).union(other);
+ }
+
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `remove` won't truncate `other`, but the `!` operator will.
+ fn remove(&mut self, other: Self)
+ where
+ Self: Sized,
+ {
+ *self = Self::from_bits_retain(self.bits()).difference(other);
+ }
+
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ fn toggle(&mut self, other: Self)
+ where
+ Self: Sized,
+ {
+ *self = Self::from_bits_retain(self.bits()).symmetric_difference(other);
+ }
+
+ /// Call [`Flags::insert`] when `value` is `true` or [`Flags::remove`] when `value` is `false`.
+ fn set(&mut self, other: Self, value: bool)
+ where
+ Self: Sized,
+ {
+ if value {
+ self.insert(other);
+ } else {
+ self.remove(other);
+ }
+ }
+
+ /// The bitwise and (`&`) of the bits in two flags values.
+ #[must_use]
+ fn intersection(self, other: Self) -> Self {
+ Self::from_bits_retain(self.bits() & other.bits())
+ }
+
+ /// The bitwise or (`|`) of the bits in two flags values.
+ #[must_use]
+ fn union(self, other: Self) -> Self {
+ Self::from_bits_retain(self.bits() | other.bits())
+ }
+
+ /// The intersection of a source flags value with the complement of a target flags value (`&!`).
+ ///
+ /// This method is not equivalent to `self & !other` when `other` has unknown bits set.
+ /// `difference` won't truncate `other`, but the `!` operator will.
+ #[must_use]
+ fn difference(self, other: Self) -> Self {
+ Self::from_bits_retain(self.bits() & !other.bits())
+ }
+
+ /// The bitwise exclusive-or (`^`) of the bits in two flags values.
+ #[must_use]
+ fn symmetric_difference(self, other: Self) -> Self {
+ Self::from_bits_retain(self.bits() ^ other.bits())
+ }
+
+ /// The bitwise negation (`!`) of the bits in a flags value, truncating the result.
+ #[must_use]
+ fn complement(self) -> Self {
+ Self::from_bits_truncate(!self.bits())
+ }
+}
+
+/**
+A bits type that can be used as storage for a flags type.
+*/
+pub trait Bits:
+ Clone
+ + Copy
+ + PartialEq
+ + BitAnd<Output = Self>
+ + BitOr<Output = Self>
+ + BitXor<Output = Self>
+ + Not<Output = Self>
+ + Sized
+ + 'static
+{
+ /// A value with all bits unset.
+ const EMPTY: Self;
+
+ /// A value with all bits set.
+ const ALL: Self;
+}
+
+// Not re-exported: prevent custom `Bits` impls being used in the `bitflags!` macro,
+// or they may fail to compile based on crate features
+pub trait Primitive {}
+
+macro_rules! impl_bits {
+ ($($u:ty, $i:ty,)*) => {
+ $(
+ impl Bits for $u {
+ const EMPTY: $u = 0;
+ const ALL: $u = <$u>::MAX;
+ }
+
+ impl Bits for $i {
+ const EMPTY: $i = 0;
+ const ALL: $i = <$u>::MAX as $i;
+ }
+
+ impl ParseHex for $u {
+ fn parse_hex(input: &str) -> Result<Self, ParseError> {
+ <$u>::from_str_radix(input, 16).map_err(|_| ParseError::invalid_hex_flag(input))
+ }
+ }
+
+ impl ParseHex for $i {
+ fn parse_hex(input: &str) -> Result<Self, ParseError> {
+ <$i>::from_str_radix(input, 16).map_err(|_| ParseError::invalid_hex_flag(input))
+ }
+ }
+
+ impl WriteHex for $u {
+ fn write_hex<W: fmt::Write>(&self, mut writer: W) -> fmt::Result {
+ write!(writer, "{:x}", self)
+ }
+ }
+
+ impl WriteHex for $i {
+ fn write_hex<W: fmt::Write>(&self, mut writer: W) -> fmt::Result {
+ write!(writer, "{:x}", self)
+ }
+ }
+
+ impl Primitive for $i {}
+ impl Primitive for $u {}
+ )*
+ }
+}
+
+impl_bits! {
+ u8, i8,
+ u16, i16,
+ u32, i32,
+ u64, i64,
+ u128, i128,
+ usize, isize,
+}
+
+/// A trait for referencing the `bitflags`-owned internal type
+/// without exposing it publicly.
+pub trait PublicFlags {
+ /// The type of the underlying storage.
+ type Primitive: Primitive;
+
+ /// The type of the internal field on the generated flags type.
+ type Internal;
+}
+
+#[doc(hidden)]
+#[deprecated(note = "use the `Flags` trait instead")]
+pub trait BitFlags: ImplementedByBitFlagsMacro + Flags {
+ /// An iterator over enabled flags in an instance of the type.
+ type Iter: Iterator<Item = Self>;
+
+ /// An iterator over the raw names and bits for enabled flags in an instance of the type.
+ type IterNames: Iterator<Item = (&'static str, Self)>;
+}
+
+#[allow(deprecated)]
+impl<B: Flags> BitFlags for B {
+ type Iter = iter::Iter<Self>;
+ type IterNames = iter::IterNames<Self>;
+}
+
+impl<B: Flags> ImplementedByBitFlagsMacro for B {}
+
+/// A marker trait that signals that an implementation of `BitFlags` came from the `bitflags!` macro.
+///
+/// There's nothing stopping an end-user from implementing this trait, but we don't guarantee their
+/// manual implementations won't break between non-breaking releases.
+#[doc(hidden)]
+pub trait ImplementedByBitFlagsMacro {}
+
+pub(crate) mod __private {
+ pub use super::{ImplementedByBitFlagsMacro, PublicFlags};
+}
diff --git a/extra/git2-curl/.cargo-checksum.json b/extra/git2-curl/.cargo-checksum.json
new file mode 100644
index 000000000..3618c16b0
--- /dev/null
+++ b/extra/git2-curl/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{},"package":"bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"} \ No newline at end of file
diff --git a/extra/git2-curl/CHANGELOG.md b/extra/git2-curl/CHANGELOG.md
new file mode 100644
index 000000000..98867bc28
--- /dev/null
+++ b/extra/git2-curl/CHANGELOG.md
@@ -0,0 +1,26 @@
+# Changelog
+
+## 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/extra/git2-curl/Cargo.toml b/extra/git2-curl/Cargo.toml
new file mode 100644
index 000000000..f4240e0e6
--- /dev/null
+++ b/extra/git2-curl/Cargo.toml
@@ -0,0 +1,58 @@
+# 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.19.0"
+authors = [
+ "Josh Triplett <josh@joshtriplett.org>",
+ "Alex Crichton <alex@alexcrichton.com>",
+]
+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"
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/rust-lang/git2-rs"
+
+[[test]]
+name = "all"
+harness = false
+
+[dependencies.curl]
+version = "0.4.33"
+
+[dependencies.git2]
+version = "0.18"
+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]
diff --git a/extra/git2-curl/LICENSE-APACHE b/extra/git2-curl/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/extra/git2-curl/LICENSE-APACHE
@@ -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/extra/git2-curl/LICENSE-MIT b/extra/git2-curl/LICENSE-MIT
new file mode 100644
index 000000000..39e0ed660
--- /dev/null
+++ b/extra/git2-curl/LICENSE-MIT
@@ -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/extra/git2-curl/debian/patches/remove-zlib-ng-compat.patch b/extra/git2-curl/debian/patches/remove-zlib-ng-compat.patch
new file mode 100644
index 000000000..48b215a4c
--- /dev/null
+++ b/extra/git2-curl/debian/patches/remove-zlib-ng-compat.patch
@@ -0,0 +1,10 @@
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -56,7 +56,3 @@
+ version = "3.0"
+
+ [features]
+-zlib-ng-compat = [
+- "git2/zlib-ng-compat",
+- "curl/zlib-ng-compat",
+-]
diff --git a/extra/git2-curl/debian/patches/series b/extra/git2-curl/debian/patches/series
new file mode 100644
index 000000000..3ed6c2fdf
--- /dev/null
+++ b/extra/git2-curl/debian/patches/series
@@ -0,0 +1 @@
+remove-zlib-ng-compat.patch
diff --git a/extra/git2-curl/src/lib.rs b/extra/git2-curl/src/lib.rs
new file mode 100644
index 000000000..dbdbdc4a6
--- /dev/null
+++ b/extra/git2-curl/src/lib.rs
@@ -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.19")]
+#![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/extra/git2-curl/tests/all.rs b/extra/git2-curl/tests/all.rs
new file mode 100644
index 000000000..c7f09dd40
--- /dev/null
+++ b/extra/git2-curl/tests/all.rs
@@ -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/extra/git2/.cargo-checksum.json b/extra/git2/.cargo-checksum.json
new file mode 100644
index 000000000..d5f0c2b7c
--- /dev/null
+++ b/extra/git2/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{},"package":"23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"} \ No newline at end of file
diff --git a/extra/git2/CHANGELOG.md b/extra/git2/CHANGELOG.md
new file mode 100644
index 000000000..efdcfcace
--- /dev/null
+++ b/extra/git2/CHANGELOG.md
@@ -0,0 +1,195 @@
+# Changelog
+
+## 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/extra/git2/CONTRIBUTING.md b/extra/git2/CONTRIBUTING.md
new file mode 100644
index 000000000..1ab0961f1
--- /dev/null
+++ b/extra/git2/CONTRIBUTING.md
@@ -0,0 +1,62 @@
+# 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>
+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/extra/git2/Cargo.lock b/extra/git2/Cargo.lock
new file mode 100644
index 000000000..06e9aa423
--- /dev/null
+++ b/extra/git2/Cargo.lock
@@ -0,0 +1,578 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "jobserver",
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags 1.3.2",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "git2"
+version = "0.18.1"
+dependencies = [
+ "bitflags 2.4.0",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "structopt",
+ "tempfile",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.148"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.16.1+1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2a2bb3680b094add03bb3732ec520ece34da31a8cd2d633d1389d0f0fb60d0c"
+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.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
+dependencies = [
+ "cc",
+ "cmake",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[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.1.5+3.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
+dependencies = [
+ "bitflags 2.4.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "redox_syscall",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "time"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+dependencies = [
+ "libc",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
+name = "url"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/extra/git2/Cargo.toml b/extra/git2/Cargo.toml
new file mode 100644
index 000000000..da9aed14c
--- /dev/null
+++ b/extra/git2/Cargo.toml
@@ -0,0 +1,77 @@
+# 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.18.1"
+authors = [
+ "Josh Triplett <josh@joshtriplett.org>",
+ "Alex Crichton <alex@alexcrichton.com>",
+]
+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"
+
+[dependencies.bitflags]
+version = "2.1.0"
+
+[dependencies.libc]
+version = "0.2"
+
+[dependencies.libgit2-sys]
+version = "0.16.0"
+
+[dependencies.log]
+version = "0.4.8"
+
+[dependencies.url]
+version = "2.0"
+
+[dev-dependencies.structopt]
+version = "0.3"
+
+[dev-dependencies.tempfile]
+version = "3.1.0"
+
+[dev-dependencies.time]
+version = "0.1.39"
+
+[features]
+default = [
+ "ssh",
+ "https",
+ "ssh_key_from_memory",
+]
+https = [
+ "libgit2-sys/https",
+ "openssl-sys",
+ "openssl-probe",
+]
+ssh = ["libgit2-sys/ssh"]
+ssh_key_from_memory = ["libgit2-sys/ssh_key_from_memory"]
+unstable = []
+
+[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/extra/git2/LICENSE-APACHE b/extra/git2/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/extra/git2/LICENSE-APACHE
@@ -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/extra/git2/LICENSE-MIT b/extra/git2/LICENSE-MIT
new file mode 100644
index 000000000..39e0ed660
--- /dev/null
+++ b/extra/git2/LICENSE-MIT
@@ -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/extra/git2/README.md b/extra/git2/README.md
new file mode 100644
index 000000000..ba75127ac
--- /dev/null
+++ b/extra/git2/README.md
@@ -0,0 +1,71 @@
+# git2-rs
+
+[Documentation](https://docs.rs/git2)
+
+libgit2 bindings for Rust.
+
+```toml
+[dependencies]
+git2 = "0.18.1"
+```
+
+## 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.7.1 (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.
+
+You can enable the Cargo feature `vendored-libgit2` to always compile and
+statically link to a copy of libgit2. Sometimes the libgit2 on the system is
+required to be found and used even when `vendored-libgit2` is activated. In
+this case, you shall set the environment variable `LIBGIT2_NO_VENDOR=1`.
+
+## 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/extra/git2/ci/publish.sh b/extra/git2/ci/publish.sh
new file mode 100755
index 000000000..69c2556bc
--- /dev/null
+++ b/extra/git2/ci/publish.sh
@@ -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 https://crates.io/api/v1/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/extra/git2/debian/patches/disable-vendor.patch b/extra/git2/debian/patches/disable-vendor.patch
new file mode 100644
index 000000000..a4899d8bb
--- /dev/null
+++ b/extra/git2/debian/patches/disable-vendor.patch
@@ -0,0 +1,14 @@
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -70,11 +70,6 @@
+ ssh = ["libgit2-sys/ssh"]
+ ssh_key_from_memory = ["libgit2-sys/ssh_key_from_memory"]
+ 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]
diff --git a/extra/git2/debian/patches/remove-zlib-ng-compat.patch b/extra/git2/debian/patches/remove-zlib-ng-compat.patch
new file mode 100644
index 000000000..1ac449c02
--- /dev/null
+++ b/extra/git2/debian/patches/remove-zlib-ng-compat.patch
@@ -0,0 +1,10 @@
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -70,7 +70,6 @@
+ ssh = ["libgit2-sys/ssh"]
+ ssh_key_from_memory = ["libgit2-sys/ssh_key_from_memory"]
+ unstable = []
+-zlib-ng-compat = ["libgit2-sys/zlib-ng-compat"]
+
+ [target."cfg(all(unix, not(target_os = \"macos\")))".dependencies.openssl-probe]
+ version = "0.1"
diff --git a/extra/git2/debian/patches/series b/extra/git2/debian/patches/series
new file mode 100644
index 000000000..5e0e458b2
--- /dev/null
+++ b/extra/git2/debian/patches/series
@@ -0,0 +1,3 @@
+disable-vendor.patch
+remove-zlib-ng-compat.patch
+skip-credential_helper5-if-no-git.patch
diff --git a/extra/git2/debian/patches/skip-credential_helper5-if-no-git.patch b/extra/git2/debian/patches/skip-credential_helper5-if-no-git.patch
new file mode 100644
index 000000000..e5686020c
--- /dev/null
+++ b/extra/git2/debian/patches/skip-credential_helper5-if-no-git.patch
@@ -0,0 +1,15 @@
+Skip the "credential_helper5" test if git is not installled.
+Index: git2/src/cred.rs
+===================================================================
+--- git2.orig/src/cred.rs
++++ git2/src/cred.rs
+@@ -563,6 +563,9 @@ echo username=c
+
+ #[test]
+ fn credential_helper5() {
++ if !Path::new("/usr/bin/git").exists() {
++ return;
++ } //this test does not work if git is not installed
+ if cfg!(windows) {
+ return;
+ } // shell scripts don't work on Windows
diff --git a/extra/git2/examples/add.rs b/extra/git2/examples/add.rs
new file mode 100644
index 000000000..25e972c7a
--- /dev/null
+++ b/extra/git2/examples/add.rs
@@ -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 git2::Repository;
+use std::path::Path;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/blame.rs b/extra/git2/examples/blame.rs
new file mode 100644
index 000000000..7cb1b6947
--- /dev/null
+++ b/extra/git2/examples/blame.rs
@@ -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 git2::{BlameOptions, Repository};
+use std::io::{BufRead, BufReader};
+use std::path::Path;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+#[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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/cat-file.rs b/extra/git2/examples/cat-file.rs
new file mode 100644
index 000000000..0ce21b34a
--- /dev/null
+++ b/extra/git2/examples/cat-file.rs
@@ -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 git2::{Blob, Commit, ObjectType, Repository, Signature, Tag, Tree};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/clone.rs b/extra/git2/examples/clone.rs
new file mode 100644
index 000000000..5af73222f
--- /dev/null
+++ b/extra/git2/examples/clone.rs
@@ -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 git2::build::{CheckoutBuilder, RepoBuilder};
+use git2::{FetchOptions, Progress, RemoteCallbacks};
+use std::cell::RefCell;
+use std::io::{self, Write};
+use std::path::{Path, PathBuf};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/diff.rs b/extra/git2/examples/diff.rs
new file mode 100644
index 000000000..62f165db3
--- /dev/null
+++ b/extra/git2/examples/diff.rs
@@ -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 git2::{Blob, Diff, DiffOptions, Error, Object, ObjectType, Oid, Repository};
+use git2::{DiffDelta, DiffFindOptions, DiffFormat, DiffHunk, DiffLine};
+use std::str;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+#[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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/fetch.rs b/extra/git2/examples/fetch.rs
new file mode 100644
index 000000000..64374a6d4
--- /dev/null
+++ b/extra/git2/examples/fetch.rs
@@ -0,0 +1,127 @@
+/*
+ * 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 git2::{AutotagOption, FetchOptions, RemoteCallbacks, Repository};
+use std::io::{self, Write};
+use std::str;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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, true, AutotagOption::Unspecified, None)?;
+
+ Ok(())
+}
+
+fn main() {
+ let args = Args::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/init.rs b/extra/git2/examples/init.rs
new file mode 100644
index 000000000..3e447cbde
--- /dev/null
+++ b/extra/git2/examples/init.rs
@@ -0,0 +1,145 @@
+/*
+ * libgit2 "init" example - shows how to initialize a new repo
+ *
+ * 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 git2::{Error, Repository, RepositoryInitMode, RepositoryInitOptions};
+use std::path::{Path, PathBuf};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/log.rs b/extra/git2/examples/log.rs
new file mode 100644
index 000000000..ad3bb354d
--- /dev/null
+++ b/extra/git2/examples/log.rs
@@ -0,0 +1,310 @@
+/*
+ * 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 git2::{Commit, DiffOptions, ObjectType, Repository, Signature, Time};
+use git2::{DiffFormat, Error, Pathspec};
+use std::str;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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, sign) = match time.offset_minutes() {
+ n if n < 0 => (-n, '-'),
+ n => (n, '+'),
+ };
+ let (hours, minutes) = (offset / 60, offset % 60);
+ let ts = time::Timespec::new(time.seconds() + (time.offset_minutes() as i64) * 60, 0);
+ let time = time::at(ts);
+
+ println!(
+ "{}{} {}{:02}{:02}",
+ prefix,
+ time.strftime("%a %b %e %T %Y").unwrap(),
+ sign,
+ hours,
+ minutes
+ );
+}
+
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/ls-remote.rs b/extra/git2/examples/ls-remote.rs
new file mode 100644
index 000000000..180845941
--- /dev/null
+++ b/extra/git2/examples/ls-remote.rs
@@ -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 git2::{Direction, Repository};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/pull.rs b/extra/git2/examples/pull.rs
new file mode 100644
index 000000000..61251b481
--- /dev/null
+++ b/extra/git2/examples/pull.rs
@@ -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 git2::Repository;
+use std::io::{self, Write};
+use std::str;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/rev-list.rs b/extra/git2/examples/rev-list.rs
new file mode 100644
index 000000000..9b4987728
--- /dev/null
+++ b/extra/git2/examples/rev-list.rs
@@ -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 git2::{Error, Oid, Repository, Revwalk};
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/rev-parse.rs b/extra/git2/examples/rev-parse.rs
new file mode 100644
index 000000000..a465f15a4
--- /dev/null
+++ b/extra/git2/examples/rev-parse.rs
@@ -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 git2::Repository;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/status.rs b/extra/git2/examples/status.rs
new file mode 100644
index 000000000..4f7bc791c
--- /dev/null
+++ b/extra/git2/examples/status.rs
@@ -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 git2::{Error, ErrorCode, Repository, StatusOptions, SubmoduleIgnore};
+use std::str;
+use std::time::Duration;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/examples/tag.rs b/extra/git2/examples/tag.rs
new file mode 100644
index 000000000..c44c2887d
--- /dev/null
+++ b/extra/git2/examples/tag.rs
@@ -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 git2::{Commit, Error, Repository, Tag};
+use std::str;
+use structopt::StructOpt;
+
+#[derive(StructOpt)]
+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::from_args();
+ match run(&args) {
+ Ok(()) => {}
+ Err(e) => println!("error: {}", e),
+ }
+}
diff --git a/extra/git2/src/apply.rs b/extra/git2/src/apply.rs
new file mode 100644
index 000000000..34dc811a0
--- /dev/null
+++ b/extra/git2/src/apply.rs
@@ -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/extra/git2/src/attr.rs b/extra/git2/src/attr.rs
new file mode 100644
index 000000000..33b1d2d4a
--- /dev/null
+++ b/extra/git2/src/attr.rs
@@ -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/extra/git2/src/blame.rs b/extra/git2/src/blame.rs
new file mode 100644
index 000000000..4bf41fed1
--- /dev/null
+++ b/extra/git2/src/blame.rs
@@ -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/extra/git2/src/blob.rs b/extra/git2/src/blob.rs
new file mode 100644
index 000000000..5c4a6ce6b
--- /dev/null
+++ b/extra/git2/src/blob.rs
@@ -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/extra/git2/src/branch.rs b/extra/git2/src/branch.rs
new file mode 100644
index 000000000..e1eba99c2
--- /dev/null
+++ b/extra/git2/src/branch.rs
@@ -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/extra/git2/src/buf.rs b/extra/git2/src/buf.rs
new file mode 100644
index 000000000..fd2bcbf96
--- /dev/null
+++ b/extra/git2/src/buf.rs
@@ -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/extra/git2/src/build.rs b/extra/git2/src/build.rs
new file mode 100644
index 000000000..d3c95f655
--- /dev/null
+++ b/extra/git2/src/build.rs
@@ -0,0 +1,861 @@
+//! 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)
+ }
+
+ /// 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.
+ ///
+ /// If no paths are specified, then all files are checked out. Otherwise
+ /// only these specified paths are checked out.
+ 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/extra/git2/src/call.rs b/extra/git2/src/call.rs
new file mode 100644
index 000000000..d9fd23468
--- /dev/null
+++ b/extra/git2/src/call.rs
@@ -0,0 +1,246 @@
+#![macro_use]
+use libc;
+
+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 {
+ // nowadays this unwrap is safe as `Error::last_error` always returns
+ // `Some`.
+ Error::last_error(code).unwrap()
+}
+
+mod impls {
+ use std::ffi::CString;
+ use std::ptr;
+
+ use libc;
+
+ 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::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/extra/git2/src/cert.rs b/extra/git2/src/cert.rs
new file mode 100644
index 000000000..b232cc3ce
--- /dev/null
+++ b/extra/git2/src/cert.rs
@@ -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/extra/git2/src/cherrypick.rs b/extra/git2/src/cherrypick.rs
new file mode 100644
index 000000000..659b73089
--- /dev/null
+++ b/extra/git2/src/cherrypick.rs
@@ -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/extra/git2/src/commit.rs b/extra/git2/src/commit.rs
new file mode 100644
index 000000000..4887e927e
--- /dev/null
+++ b/extra/git2/src/commit.rs
@@ -0,0 +1,473 @@
+use libc;
+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/extra/git2/src/config.rs b/extra/git2/src/config.rs
new file mode 100644
index 000000000..ae5c4ff63
--- /dev/null
+++ b/extra/git2/src/config.rs
@@ -0,0 +1,777 @@
+use libc;
+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.
+ ///
+ /// This method will return an error if this `Config` is not a 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.
+ ///
+ /// This method will return an error if this `Config` is not a 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/extra/git2/src/cred.rs b/extra/git2/src/cred.rs
new file mode 100644
index 000000000..49afb4239
--- /dev/null
+++ b/extra/git2/src/cred.rs
@@ -0,0 +1,717 @@
+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 url;
+
+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");
+ 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());
+ 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::Highest, 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 !Path::new("/usr/bin/git").exists() {
+ return;
+ } //this test does not work if git is not installed
+ 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/extra/git2/src/describe.rs b/extra/git2/src/describe.rs
new file mode 100644
index 000000000..cbaa1893b
--- /dev/null
+++ b/extra/git2/src/describe.rs
@@ -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/extra/git2/src/diff.rs b/extra/git2/src/diff.rs
new file mode 100644
index 000000000..16595509d
--- /dev/null
+++ b/extra/git2/src/diff.rs
@@ -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 an 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/extra/git2/src/email.rs b/extra/git2/src/email.rs
new file mode 100644
index 000000000..d3ebc0384
--- /dev/null
+++ b/extra/git2/src/email.rs
@@ -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/extra/git2/src/error.rs b/extra/git2/src/error.rs
new file mode 100644
index 000000000..6f1c4d4c7
--- /dev/null
+++ b/extra/git2/src/error.rs
@@ -0,0 +1,399 @@
+use libc::c_int;
+use std::env::JoinPathsError;
+use std::error;
+use std::ffi::{CStr, 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: String,
+}
+
+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.
+ ///
+ /// Historically this function returned `Some` or `None` based on the return
+ /// value of `git_error_last` but nowadays it always returns `Some` so it's
+ /// safe to unwrap the return value. This API will change in the next major
+ /// version.
+ pub fn last_error(code: c_int) -> Option<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();
+ Some(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();
+ 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.to_string(),
+ }
+ }
+
+ /// 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,
+ _ => 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,
+ };
+ }
+
+ /// 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,
+ )
+ }
+
+ /// 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
+ }
+}
+
+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/extra/git2/src/index.rs b/extra/git2/src/index.rs
new file mode 100644
index 000000000..0291d3cb9
--- /dev/null
+++ b/extra/git2/src/index.rs
@@ -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/extra/git2/src/indexer.rs b/extra/git2/src/indexer.rs
new file mode 100644
index 000000000..0aaf353d5
--- /dev/null
+++ b/extra/git2/src/indexer.rs
@@ -0,0 +1,255 @@
+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).unwrap(),
+ ))
+ } 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/extra/git2/src/lib.rs b/extra/git2/src/lib.rs
new file mode 100644
index 000000000..3dd6fe92e
--- /dev/null
+++ b/extra/git2/src/lib.rs
@@ -0,0 +1,1668 @@
+//! # 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.18")]
+#![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,
+}
+
+/// 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,
+ /// 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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;
+ }
+}
+
+#[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_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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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)]
+ 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/extra/git2/src/mailmap.rs b/extra/git2/src/mailmap.rs
new file mode 100644
index 000000000..096b3227c
--- /dev/null
+++ b/extra/git2/src/mailmap.rs
@@ -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/extra/git2/src/mempack.rs b/extra/git2/src/mempack.rs
new file mode 100644
index 000000000..a78070791
--- /dev/null
+++ b/extra/git2/src/mempack.rs
@@ -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/extra/git2/src/merge.rs b/extra/git2/src/merge.rs
new file mode 100644
index 000000000..6bd30c10d
--- /dev/null
+++ b/extra/git2/src/merge.rs
@@ -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/extra/git2/src/message.rs b/extra/git2/src/message.rs
new file mode 100644
index 000000000..a7041da3a
--- /dev/null
+++ b/extra/git2/src/message.rs
@@ -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/extra/git2/src/note.rs b/extra/git2/src/note.rs
new file mode 100644
index 000000000..50e5800fe
--- /dev/null
+++ b/extra/git2/src/note.rs
@@ -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/extra/git2/src/object.rs b/extra/git2/src/object.rs
new file mode 100644
index 000000000..fcae0066c
--- /dev/null
+++ b/extra/git2/src/object.rs
@@ -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/extra/git2/src/odb.rs b/extra/git2/src/odb.rs
new file mode 100644
index 000000000..7f6da5eb3
--- /dev/null
+++ b/extra/git2/src/odb.rs
@@ -0,0 +1,729 @@
+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(len)
+ }
+ }
+ }
+}
+
+/// 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).unwrap())
+ } 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());
+ }
+}
diff --git a/extra/git2/src/oid.rs b/extra/git2/src/oid.rs
new file mode 100644
index 000000000..145458aec
--- /dev/null
+++ b/extra/git2/src/oid.rs
@@ -0,0 +1,259 @@
+use libc;
+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/extra/git2/src/oid_array.rs b/extra/git2/src/oid_array.rs
new file mode 100644
index 000000000..0d87ce995
--- /dev/null
+++ b/extra/git2/src/oid_array.rs
@@ -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/extra/git2/src/opts.rs b/extra/git2/src/opts.rs
new file mode 100644
index 000000000..e90bea0b1
--- /dev/null
+++ b/extra/git2/src/opts.rs
@@ -0,0 +1,206 @@
+//! 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(())
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn smoke() {
+ strict_hash_verification(false);
+ }
+}
diff --git a/extra/git2/src/packbuilder.rs b/extra/git2/src/packbuilder.rs
new file mode 100644
index 000000000..9b93e7654
--- /dev/null
+++ b/extra/git2/src/packbuilder.rs
@@ -0,0 +1,413 @@
+use libc::{c_int, c_uint, c_void, size_t};
+use std::marker;
+use std::ptr;
+use std::slice;
+use std::str;
+
+use crate::util::Binding;
+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(())
+ }
+
+ /// 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;
+
+ 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_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 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 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/extra/git2/src/panic.rs b/extra/git2/src/panic.rs
new file mode 100644
index 000000000..3e1b208bc
--- /dev/null
+++ b/extra/git2/src/panic.rs
@@ -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/extra/git2/src/patch.rs b/extra/git2/src/patch.rs
new file mode 100644
index 000000000..67b84c0f0
--- /dev/null
+++ b/extra/git2/src/patch.rs
@@ -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/extra/git2/src/pathspec.rs b/extra/git2/src/pathspec.rs
new file mode 100644
index 000000000..48174fcc1
--- /dev/null
+++ b/extra/git2/src/pathspec.rs
@@ -0,0 +1,368 @@
+use libc::size_t;
+use std::iter::{FusedIterator, IntoIterator};
+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/extra/git2/src/proxy_options.rs b/extra/git2/src/proxy_options.rs
new file mode 100644
index 000000000..b19ba3a52
--- /dev/null
+++ b/extra/git2/src/proxy_options.rs
@@ -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/extra/git2/src/push_update.rs b/extra/git2/src/push_update.rs
new file mode 100644
index 000000000..3f74a2506
--- /dev/null
+++ b/extra/git2/src/push_update.rs
@@ -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.
+ pub fn src_refname(&self) -> Option<&str> {
+ str::from_utf8(self.src_refname_bytes()).ok()
+ }
+
+ /// Returns the destination name of the reference as a byte slice.
+ pub fn dst_refname_bytes(&self) -> &[u8] {
+ unsafe { crate::opt_bytes(self, (*self.raw).dst_refname).unwrap() }
+ }
+
+ /// Returns the destination name of the reference.
+ 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/extra/git2/src/rebase.rs b/extra/git2/src/rebase.rs
new file mode 100644
index 000000000..2bf8fe3e8
--- /dev/null
+++ b/extra/git2/src/rebase.rs
@@ -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/extra/git2/src/reference.rs b/extra/git2/src/reference.rs
new file mode 100644
index 000000000..92eb18c63
--- /dev/null
+++ b/extra/git2/src/reference.rs
@@ -0,0 +1,586 @@
+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;
+
+struct Refdb<'repo>(&'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/extra/git2/src/reflog.rs b/extra/git2/src/reflog.rs
new file mode 100644
index 000000000..bbd2140ab
--- /dev/null
+++ b/extra/git2/src/reflog.rs
@@ -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/extra/git2/src/refspec.rs b/extra/git2/src/refspec.rs
new file mode 100644
index 000000000..3f62e991c
--- /dev/null
+++ b/extra/git2/src/refspec.rs
@@ -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/extra/git2/src/remote.rs b/extra/git2/src/remote.rs
new file mode 100644
index 000000000..c8f5a935a
--- /dev/null
+++ b/extra/git2/src/remote.rs
@@ -0,0 +1,1123 @@
+use libc;
+use raw::git_strarray;
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+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, 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_fetchhead: bool,
+ 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>,
+}
+
+/// 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_fetchhead: bool,
+ 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_fetchhead,
+ 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_fetchhead: true,
+ 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_fetchhead = 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: crate::call::convert(&self.update_fetchhead),
+ 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(),
+ }
+ }
+
+ /// 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
+ }
+}
+
+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 _,
+ },
+ }
+ }
+}
+
+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};
+ 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, true, AutotagOption::Unspecified, None)
+ .unwrap();
+ origin
+ .update_tips(None, true, 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/extra/git2/src/remote_callbacks.rs b/extra/git2/src/remote_callbacks.rs
new file mode 100644
index 000000000..1169420bd
--- /dev/null
+++ b/extra/git2/src/remote_callbacks.rs
@@ -0,0 +1,518 @@
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::ffi::{CStr, CString};
+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
+///
+/// Parameters:
+/// * stage
+/// * current
+/// * total
+pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a;
+
+/// Callback used to inform of upcoming updates.
+///
+/// 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
+ 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.
+ 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.
+ /// It provides information about what updates will be performed.
+ 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| {
+ let s = CString::new(e.to_string()).unwrap();
+ raw::git_error_set_str(e.class() as c_int, s.as_ptr());
+ e.raw_code() as c_int
+ })
+ });
+ 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)) => {
+ let s = CString::new(e.message()).unwrap();
+ unsafe {
+ raw::git_error_set_str(e.class() as c_int, s.as_ptr());
+ }
+ e.raw_code() as c_int
+ }
+ 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_code(),
+ }
+ })
+ .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_code(),
+ }
+ })
+ .unwrap_or(-1)
+}
diff --git a/extra/git2/src/repo.rs b/extra/git2/src/repo.rs
new file mode 100644
index 000000000..921e2b30e
--- /dev/null
+++ b/extra/git2/src/repo.rs
@@ -0,0 +1,4226 @@
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::env;
+use std::ffi::{CStr, CString, OsStr};
+use std::iter::IntoIterator;
+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 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).unwrap()),
+ }
+ }
+ }
+
+ /// 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))
+ }
+ }
+
+ /// 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))
+ }
+ }
+
+ /// 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))
+ }
+ }
+
+ /// 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
+ 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 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.
+ 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);
+ }
+
+ /// 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);
+ }
+
+ /// 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());
+ }
+}
diff --git a/extra/git2/src/revert.rs b/extra/git2/src/revert.rs
new file mode 100644
index 000000000..55d702600
--- /dev/null
+++ b/extra/git2/src/revert.rs
@@ -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/extra/git2/src/revspec.rs b/extra/git2/src/revspec.rs
new file mode 100644
index 000000000..d2e08670a
--- /dev/null
+++ b/extra/git2/src/revspec.rs
@@ -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/extra/git2/src/revwalk.rs b/extra/git2/src/revwalk.rs
new file mode 100644
index 000000000..7837f00d6
--- /dev/null
+++ b/extra/git2/src/revwalk.rs
@@ -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/extra/git2/src/signature.rs b/extra/git2/src/signature.rs
new file mode 100644
index 000000000..83fbbf593
--- /dev/null
+++ b/extra/git2/src/signature.rs
@@ -0,0 +1,189 @@
+use libc;
+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/extra/git2/src/stash.rs b/extra/git2/src/stash.rs
new file mode 100644
index 000000000..ea898e46b
--- /dev/null
+++ b/extra/git2/src/stash.rs
@@ -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/extra/git2/src/status.rs b/extra/git2/src/status.rs
new file mode 100644
index 000000000..a5a8cffd3
--- /dev/null
+++ b/extra/git2/src/status.rs
@@ -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/extra/git2/src/string_array.rs b/extra/git2/src/string_array.rs
new file mode 100644
index 000000000..c77ccdab9
--- /dev/null
+++ b/extra/git2/src/string_array.rs
@@ -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/extra/git2/src/submodule.rs b/extra/git2/src/submodule.rs
new file mode 100644
index 000000000..06a635940
--- /dev/null
+++ b/extra/git2/src/submodule.rs
@@ -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/extra/git2/src/tag.rs b/extra/git2/src/tag.rs
new file mode 100644
index 000000000..6986c7c16
--- /dev/null
+++ b/extra/git2/src/tag.rs
@@ -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/extra/git2/src/tagforeach.rs b/extra/git2/src/tagforeach.rs
new file mode 100644
index 000000000..425eea5a4
--- /dev/null
+++ b/extra/git2/src/tagforeach.rs
@@ -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/extra/git2/src/test.rs b/extra/git2/src/test.rs
new file mode 100644
index 000000000..c1ff1de21
--- /dev/null
+++ b/extra/git2/src/test.rs
@@ -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.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/extra/git2/src/time.rs b/extra/git2/src/time.rs
new file mode 100644
index 000000000..46b5bd3f9
--- /dev/null
+++ b/extra/git2/src/time.rs
@@ -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/extra/git2/src/tracing.rs b/extra/git2/src/tracing.rs
new file mode 100644
index 000000000..5acae8a85
--- /dev/null
+++ b/extra/git2/src/tracing.rs
@@ -0,0 +1,85 @@
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use libc::c_char;
+
+use crate::{panic, raw, util::Binding};
+
+/// 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,
+ }
+ }
+}
+
+//TODO: pass raw &[u8] and leave conversion to consumer (breaking API)
+/// Callback type used to pass tracing events to the subscriber.
+/// see `trace_set` to register a subscriber.
+pub type TracingCb = fn(TraceLevel, &str);
+
+static CALLBACK: AtomicUsize = AtomicUsize::new(0);
+
+///
+pub fn trace_set(level: TraceLevel, cb: TracingCb) -> bool {
+ CALLBACK.store(cb as usize, Ordering::SeqCst);
+
+ unsafe {
+ raw::git_trace_set(level.raw(), Some(tracing_cb_c));
+ }
+
+ return true;
+}
+
+extern "C" fn tracing_cb_c(level: raw::git_trace_level_t, msg: *const c_char) {
+ let cb = CALLBACK.load(Ordering::SeqCst);
+ panic::wrap(|| unsafe {
+ let cb: TracingCb = std::mem::transmute(cb);
+ let msg = std::ffi::CStr::from_ptr(msg).to_string_lossy();
+ cb(Binding::from_raw(level), msg.as_ref());
+ });
+}
diff --git a/extra/git2/src/transaction.rs b/extra/git2/src/transaction.rs
new file mode 100644
index 000000000..4f661f1d4
--- /dev/null
+++ b/extra/git2/src/transaction.rs
@@ -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/extra/git2/src/transport.rs b/extra/git2/src/transport.rs
new file mode 100644
index 000000000..74446d0ca
--- /dev/null
+++ b/extra/git2/src/transport.rs
@@ -0,0 +1,429 @@
+//! 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)]
+#[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) => {
+ set_err(&e);
+ return e.raw_code() as c_int;
+ }
+ };
+ *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());
+}
+
+unsafe fn set_err(e: &Error) {
+ let s = CString::new(e.message()).unwrap();
+ raw::git_error_set_str(e.raw_class() 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/extra/git2/src/tree.rs b/extra/git2/src/tree.rs
new file mode 100644
index 000000000..9a38244cf
--- /dev/null
+++ b/extra/git2/src/tree.rs
@@ -0,0 +1,570 @@
+use libc::{self, 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.
+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`](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,
+ };
+ raw::git_tree_walk(
+ self.raw(),
+ mode.into(),
+ Some(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()
+ }
+}
+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(), 1);
+
+ for entry in tree_iter(&tree, &repo) {
+ println!("iter entry {:?}", entry.name());
+ }
+ }
+
+ fn setup_repo(td: &TempDir, repo: &Repository) {
+ let mut index = repo.index().unwrap();
+ File::create(&td.path().join("foo"))
+ .unwrap()
+ .write_all(b"foo")
+ .unwrap();
+ index.add_path(Path::new("foo")).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(), 1);
+ {
+ let e1 = tree.get(0).unwrap();
+ assert!(e1 == tree.get_id(e1.id()).unwrap());
+ assert!(e1 == tree.get_name("foo").unwrap());
+ assert!(e1 == tree.get_name_bytes(b"foo").unwrap());
+ assert!(e1 == tree.get_path(Path::new("foo")).unwrap());
+ assert_eq!(e1.name(), Some("foo"));
+ 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("foo"));
+ ct += 1;
+ 0
+ })
+ .unwrap();
+ assert_eq!(ct, 1);
+
+ let mut ct = 0;
+ tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+ assert_eq!(entry.name(), Some("foo"));
+ ct += 1;
+ TreeWalkResult::Ok
+ })
+ .unwrap();
+ assert_eq!(ct, 1);
+ }
+}
diff --git a/extra/git2/src/treebuilder.rs b/extra/git2/src/treebuilder.rs
new file mode 100644
index 000000000..1548a048c
--- /dev/null
+++ b/extra/git2/src/treebuilder.rs
@@ -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/extra/git2/src/util.rs b/extra/git2/src/util.rs
new file mode 100644
index 000000000..5f735bc00
--- /dev/null
+++ b/extra/git2/src/util.rs
@@ -0,0 +1,342 @@
+use libc::{c_char, c_int, size_t};
+use std::cmp::Ordering;
+use std::ffi::{CString, OsStr, OsString};
+use std::iter::IntoIterator;
+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/extra/git2/src/version.rs b/extra/git2/src/version.rs
new file mode 100644
index 000000000..b5dd4fb12
--- /dev/null
+++ b/extra/git2/src/version.rs
@@ -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/extra/git2/src/worktree.rs b/extra/git2/src/worktree.rs
new file mode 100644
index 000000000..569b639cf
--- /dev/null
+++ b/extra/git2/src/worktree.rs
@@ -0,0 +1,331 @@
+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
+ }
+
+ /// 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/extra/git2/tests/add_extensions.rs b/extra/git2/tests/add_extensions.rs
new file mode 100644
index 000000000..57c0eb976
--- /dev/null
+++ b/extra/git2/tests/add_extensions.rs
@@ -0,0 +1,21 @@
+//! 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() }?;
+
+ assert_eq!(extensions.len(), 3);
+ assert_eq!(extensions.get(0), Some("custom"));
+ // The objectformat extension was added in 1.6
+ assert_eq!(extensions.get(1), Some("noop"));
+ assert_eq!(extensions.get(2), Some("objectformat"));
+
+ Ok(())
+}
diff --git a/extra/git2/tests/get_extensions.rs b/extra/git2/tests/get_extensions.rs
new file mode 100644
index 000000000..d8dd55fe0
--- /dev/null
+++ b/extra/git2/tests/get_extensions.rs
@@ -0,0 +1,16 @@
+//! 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() }?;
+
+ assert_eq!(extensions.len(), 2);
+ assert_eq!(extensions.get(0), Some("noop"));
+ // The objectformat extension was added in 1.6
+ assert_eq!(extensions.get(1), Some("objectformat"));
+
+ Ok(())
+}
diff --git a/extra/git2/tests/global_state.rs b/extra/git2/tests/global_state.rs
new file mode 100644
index 000000000..192acdbd3
--- /dev/null
+++ b/extra/git2/tests/global_state.rs
@@ -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/extra/git2/tests/remove_extensions.rs b/extra/git2/tests/remove_extensions.rs
new file mode 100644
index 000000000..5f632a880
--- /dev/null
+++ b/extra/git2/tests/remove_extensions.rs
@@ -0,0 +1,19 @@
+//! 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", "other"])?;
+ }
+
+ let extensions = unsafe { get_extensions() }?;
+
+ assert_eq!(extensions.len(), 2);
+ assert_eq!(extensions.get(0), Some("custom"));
+ assert_eq!(extensions.get(1), Some("other"));
+
+ Ok(())
+}
diff --git a/extra/libgit2-sys/.cargo-checksum.json b/extra/libgit2-sys/.cargo-checksum.json
new file mode 100644
index 000000000..d5f0c2b7c
--- /dev/null
+++ b/extra/libgit2-sys/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{},"package":"23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"} \ No newline at end of file
diff --git a/extra/libgit2-sys/CHANGELOG.md b/extra/libgit2-sys/CHANGELOG.md
new file mode 100644
index 000000000..5f159825f
--- /dev/null
+++ b/extra/libgit2-sys/CHANGELOG.md
@@ -0,0 +1,170 @@
+# Changelog
+
+## 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/extra/libgit2-sys/Cargo.toml b/extra/libgit2-sys/Cargo.toml
new file mode 100644
index 000000000..a40fcbbd3
--- /dev/null
+++ b/extra/libgit2-sys/Cargo.toml
@@ -0,0 +1,63 @@
+# 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.16.1+1.7.1"
+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/*",
+]
+description = "Native bindings to the libgit2 library"
+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"]
+ssh_key_from_memory = []
+
+[target."cfg(unix)".dependencies.openssl-sys]
+version = "0.9.45"
+optional = true
diff --git a/extra/libgit2-sys/LICENSE-APACHE b/extra/libgit2-sys/LICENSE-APACHE
new file mode 100644
index 000000000..16fe87b06
--- /dev/null
+++ b/extra/libgit2-sys/LICENSE-APACHE
@@ -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/extra/libgit2-sys/LICENSE-MIT b/extra/libgit2-sys/LICENSE-MIT
new file mode 100644
index 000000000..39e0ed660
--- /dev/null
+++ b/extra/libgit2-sys/LICENSE-MIT
@@ -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/extra/libgit2-sys/build.rs b/extra/libgit2-sys/build.rs
new file mode 100644
index 000000000..e7f7fa388
--- /dev/null
+++ b/extra/libgit2-sys/build.rs
@@ -0,0 +1,295 @@
+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.7.1".."1.8.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() {
+ 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;
+ }
+
+ panic!("debian build must never use vendored libgit2!");
+
+ 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 for now
+ cfg.include("libgit2/deps/http-parser")
+ .file("libgit2/deps/http-parser/http_parser.c");
+
+ // 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");
+
+ 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_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/extra/libgit2-sys/debian/patches/disable-vendor.patch b/extra/libgit2-sys/debian/patches/disable-vendor.patch
new file mode 100644
index 000000000..fe20e2315
--- /dev/null
+++ b/extra/libgit2-sys/debian/patches/disable-vendor.patch
@@ -0,0 +1,26 @@
+Index: libgit2-sys/Cargo.toml
+===================================================================
+--- libgit2-sys.orig/Cargo.toml
++++ libgit2-sys/Cargo.toml
+@@ -57,8 +57,6 @@ version = "0.3.15"
+ https = ["openssl-sys"]
+ ssh = ["libssh2-sys"]
+ ssh_key_from_memory = []
+-vendored = []
+-vendored-openssl = ["openssl-sys/vendored"]
+
+ [target."cfg(unix)".dependencies.openssl-sys]
+ version = "0.9.45"
+Index: libgit2-sys/build.rs
+===================================================================
+--- libgit2-sys.orig/build.rs
++++ libgit2-sys/build.rs
+@@ -55,6 +55,8 @@ The build is now aborting. To disable, u
+ return;
+ }
+
++ panic!("debian build must never use vendored libgit2!");
++
+ println!("cargo:rustc-cfg=libgit2_vendored");
+
+ if !Path::new("libgit2/src").exists() {
diff --git a/extra/libgit2-sys/debian/patches/remove-zlib-ng-compat.patch b/extra/libgit2-sys/debian/patches/remove-zlib-ng-compat.patch
new file mode 100644
index 000000000..5e5b44e5e
--- /dev/null
+++ b/extra/libgit2-sys/debian/patches/remove-zlib-ng-compat.patch
@@ -0,0 +1,15 @@
+Index: libgit2-sys/Cargo.toml
+===================================================================
+--- libgit2-sys.orig/Cargo.toml
++++ libgit2-sys/Cargo.toml
+@@ -59,10 +59,6 @@ ssh = ["libssh2-sys"]
+ ssh_key_from_memory = []
+ 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"
diff --git a/extra/libgit2-sys/debian/patches/series b/extra/libgit2-sys/debian/patches/series
new file mode 100644
index 000000000..90136830c
--- /dev/null
+++ b/extra/libgit2-sys/debian/patches/series
@@ -0,0 +1,2 @@
+remove-zlib-ng-compat.patch
+disable-vendor.patch
diff --git a/extra/libgit2-sys/lib.rs b/extra/libgit2-sys/lib.rs
new file mode 100644
index 000000000..6c42d70d2
--- /dev/null
+++ b/extra/libgit2-sys/lib.rs
@@ -0,0 +1,4312 @@
+#![doc(html_root_url = "https://docs.rs/libgit2-sys/0.16")]
+#![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_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>;
+
+#[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,
+}
+
+#[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_int,
+ 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_NONE = 0,
+ GIT_CHECKOUT_SAFE = 1 << 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_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 =
+ Option<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 orig_commit_id: git_oid,
+ pub orig_path: *const c_char,
+ pub orig_start_line_number: usize,
+ pub orig_signature: *mut git_signature,
+ 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 include_depth: c_uint,
+ pub level: git_config_level_t,
+ pub free: Option<extern "C" fn(*mut git_config_entry)>,
+ pub payload: *mut c_void,
+}
+
+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_APP = 6,
+ 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 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_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 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_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_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_fetchead: c_int,
+ 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_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_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)
+}