gix/lib.rs
1//! This crate provides the [`Repository`] abstraction which serves as a hub into all the functionality of git.
2//!
3//! It's powerful and won't sacrifice performance while still increasing convenience compared to using the sub-crates
4//! individually. Sometimes it may hide complexity under the assumption that the performance difference doesn't matter
5//! for all but the fewest tools out there, which would be using the underlying crates directly or file an issue.
6//!
7//! ## Example
8//!
9//! This is merely an introduction, for more see the respective [`Repository`] methods.
10//! ```
11//! # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
12//! # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }
13//! # let repo_dir = doctest::basic_repo_dir()?;
14//! # let repo = doctest::open_repo(repo_dir)?;
15//! let head = repo.head_commit()?;
16//!
17//! assert_eq!(repo.head_name()?.expect("born").shorten(), "main");
18//! assert_eq!(head.decode()?.message, "c2\n");
19//! assert_eq!(repo.head_tree_id()?, head.tree_id()?);
20//! # Ok(()) }
21//! ```
22//!
23//! ### The Trust Model
24//!
25//! It is very simple - based on the ownership of the repository compared to the user of the current process [Trust](sec::Trust)
26//! is assigned. This can be [overridden](open::Options::with()) as well. Further, git configuration files track their trust level
27//! per section based on and sensitive values like paths to executables or certain values will be skipped if they are from a source
28//! that isn't [fully](sec::Trust::Full) trusted.
29//!
30//! That way, data can safely be obtained without risking to execute untrusted executables.
31//!
32//! Note that it's possible to let `gix` act like `git` or `git2` by setting the [open::Options::bail_if_untrusted()] option.
33//!
34//! ### The prelude and extensions
35//!
36//! With `use git_repository::prelude::*` you should be ready to go as it pulls in various extension traits to make functionality
37//! available on objects that may use it.
38//!
39//! The method signatures are still complex and may require various arguments for configuration and cache control.
40//!
41//! Most extensions to existing objects provide an `obj_with_extension.attach(&repo).an_easier_version_of_a_method()` for simpler
42//! call signatures.
43//!
44//! ### `ThreadSafe` Mode
45//!
46//! By default, the [`Repository`] isn't `Sync` and thus can't be used in certain contexts which require the `Sync` trait.
47//!
48//! To help with this, convert it with [`Repository::into_sync()`] into a [`ThreadSafeRepository`].
49//!
50//! ### Object-Access Performance
51//!
52//! Accessing objects quickly is the bread-and-butter of working with git, right after accessing references. Hence it's vital
53//! to understand which cache levels exist and how to leverage them.
54//!
55//! When accessing an object, the first cache that's queried is a memory-capped LRU object cache, mapping their id to data and kind.
56//! It has to be specifically enabled on a [`Repository`].
57//! On miss, the object is looked up and if a pack is hit, there is a small fixed-size cache for delta-base objects.
58//!
59//! In scenarios where the same objects are accessed multiple times, the object cache can be useful and is to be configured specifically
60//! using the [`Repository::object_cache_size()`] method.
61//!
62//! Use the `cache-efficiency-debug` cargo feature to learn how efficient the cache actually is - it's easy to end up with lowered
63//! performance if the cache is not hit in 50% of the time.
64//!
65//! ### Terminology
66//!
67//! #### `WorkingTree` and `WorkTree`
68//!
69//! When reading the documentation of the canonical gix-worktree program one gets the impression work tree and working tree are used
70//! interchangeably. We use the term _work tree_ only and try to do so consistently as its shorter and assumed to be the same.
71//!
72//! ### Plumbing Crates
73//!
74//! To make using _sub-crates_ and their types easier, these are re-exported into the root of this crate. Here we list how to access nested plumbing
75//! crates which are otherwise harder to discover:
76//!
77//! **`git_repository::`**
78//! * [`odb`]
79//! * [`pack`][odb::pack]
80//! * [`protocol`]
81//! * [`transport`][protocol::transport]
82//! * [`packetline`][protocol::transport::packetline]
83//!
84//! ### `libgit2` API to `gix`
85//!
86//! This doc-aliases are used to help finding methods under a possibly changed name. Just search in the docs.
87//! Entering `git2` into the search field will also surface all methods with such annotations.
88//!
89//! What follows is a list of methods you might be missing, along with workarounds if available.
90//! * [`git2::Repository::open_bare()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.open_bare) ➡ ❌ - use [`open()`] and discard if it is not bare.
91//! * [`git2::build::CheckoutBuilder::disable_filters()`](https://docs.rs/git2/*/git2/build/struct.CheckoutBuilder.html#method.disable_filters) ➡ ❌ *(filters are always applied during checkouts)*
92//! * [`git2::Repository::submodule_status()`](https://docs.rs/git2/*/git2/struct.Repository.html#method.submodule_status) ➡ [`Submodule::state()`] - status provides more information and conveniences though, and an actual worktree status isn't performed.
93//!
94//! #### Integrity checks
95//!
96//! `git2` by default performs integrity checks via [`strict_hash_verification()`](https://docs.rs/git2/latest/git2/opts/fn.strict_hash_verification.html) and
97//! [`strict_object_creation`](https://docs.rs/git2/latest/git2/opts/fn.strict_object_creation.html) which `gitoxide` *currently* **does not have**.
98//!
99//! ### Feature Flags
100#![cfg_attr(
101 all(doc, feature = "document-features"),
102 doc = ::document_features::document_features!()
103)]
104#![cfg_attr(all(doc, feature = "document-features"), feature(doc_cfg))]
105#![deny(missing_docs, unsafe_code)]
106#![allow(clippy::result_large_err)]
107
108// Re-exports to make this a potential one-stop shop crate avoiding people from having to reference various crates themselves.
109// This also means that their major version changes affect our major version, but that's alright as we directly expose their
110// APIs/instances anyway.
111pub use gix_actor as actor;
112#[cfg(feature = "attributes")]
113pub use gix_attributes as attrs;
114#[cfg(feature = "blame")]
115pub use gix_blame as blame;
116#[cfg(feature = "command")]
117pub use gix_command as command;
118pub use gix_commitgraph as commitgraph;
119#[cfg(feature = "credentials")]
120pub use gix_credentials as credentials;
121pub use gix_date as date;
122#[cfg(feature = "dirwalk")]
123pub use gix_dir as dir;
124pub use gix_error as error;
125pub use gix_features as features;
126use gix_features::threading::OwnShared;
127pub use gix_features::{
128 parallel,
129 progress::{Count, DynNestedProgress, NestedProgress, Progress},
130 threading,
131};
132pub use gix_fs as fs;
133pub use gix_glob as glob;
134pub use gix_hash as hash;
135pub use gix_hashtable as hashtable;
136#[cfg(feature = "excludes")]
137pub use gix_ignore as ignore;
138#[doc(inline)]
139#[cfg(feature = "index")]
140pub use gix_index as index;
141pub use gix_lock as lock;
142#[cfg(feature = "credentials")]
143pub use gix_negotiate as negotiate;
144pub use gix_object as objs;
145pub use gix_object::bstr;
146pub use gix_odb as odb;
147#[cfg(feature = "credentials")]
148pub use gix_prompt as prompt;
149pub use gix_protocol as protocol;
150pub use gix_quote as quote;
151pub use gix_ref as refs;
152pub use gix_refspec as refspec;
153pub use gix_revwalk as revwalk;
154pub use gix_sec as sec;
155pub use gix_tempfile as tempfile;
156pub use gix_trace as trace;
157pub use gix_traverse as traverse;
158pub use gix_url as url;
159#[doc(inline)]
160pub use gix_url::Url;
161pub use gix_utils as utils;
162pub use gix_validate as validate;
163pub use gix_zlib as zlib;
164pub use hash::{ObjectId, oid};
165
166pub use gix_error::{Error, Exn};
167
168pub mod interrupt;
169
170mod ext;
171///
172pub mod prelude;
173
174#[cfg(feature = "excludes")]
175mod attribute_stack;
176
177///
178pub mod path;
179
180/// The standard type for a store to handle git references.
181pub type RefStore = gix_ref::file::Store;
182/// A handle for finding objects in an object database, abstracting away caches for thread-local use.
183pub type OdbHandle = gix_odb::memory::Proxy<gix_odb::Handle>;
184/// A handle for finding objects in an object database, abstracting away caches for moving across threads.
185pub type OdbHandleArc = gix_odb::memory::Proxy<gix_odb::HandleArc>;
186
187/// A way to access git configuration
188pub(crate) type Config = OwnShared<gix_config::File>;
189
190mod types;
191#[cfg(any(feature = "excludes", feature = "attributes"))]
192pub use types::AttributeStack;
193pub use types::{
194 Blob, Commit, Head, Id, Object, ObjectDetached, Reference, Remote, Repository, Tag, ThreadSafeRepository, Tree,
195 Worktree,
196};
197#[cfg(feature = "attributes")]
198pub use types::{Pathspec, PathspecDetached, Submodule};
199
200///
201pub mod clone;
202pub mod commit;
203///
204#[cfg(feature = "dirwalk")]
205pub mod dirwalk;
206pub mod head;
207pub mod id;
208pub mod object;
209#[cfg(feature = "attributes")]
210pub mod pathspec;
211pub mod reference;
212pub mod repository;
213#[cfg(feature = "attributes")]
214pub mod submodule;
215pub mod tag;
216#[cfg(any(feature = "dirwalk", feature = "status"))]
217pub(crate) mod util;
218
219///
220pub mod progress;
221///
222pub mod push;
223
224///
225pub mod diff;
226
227///
228#[cfg(feature = "merge")]
229pub mod merge;
230
231/// Try to open a git repository in `directory` and search upwards through its parents until one is found,
232/// using default trust options which matters in case the found repository isn't owned by the current user.
233///
234/// For details, see [`ThreadSafeRepository::discover()`].
235///
236/// # Note
237///
238/// **The discovered repository might not be suitable for any operation that requires authentication with remotes**
239/// as it doesn't see the relevant git configuration.
240///
241/// To achieve that, one has to [enable `git_binary` configuration](https://github.com/GitoxideLabs/gitoxide/blob/9723e1addf52cc336d59322de039ea0537cdca36/src/plumbing/main.rs#L86)
242/// in the open-options and use [`ThreadSafeRepository::discover_opts()`] instead. Alternatively, it might be well-known
243/// that the tool is going to run in a neatly configured environment without relying on bundled configuration.
244///
245/// # Examples
246///
247/// ```
248/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
249/// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }
250/// # let repo_dir = doctest::basic_repo_dir()?;
251/// let repo = doctest::discover_repo(repo_dir.join("some/very/deeply/nested/subdir"))?;
252///
253/// assert_eq!(repo.kind(), gix::repository::Kind::Common);
254/// assert_eq!(repo.head_name()?.expect("born").shorten(), "main");
255/// assert!(repo.workdir_path("this").expect("non-bare").is_file());
256/// # Ok(()) }
257/// ```
258#[expect(
259 clippy::result_large_err,
260 reason = "will be removed once `gix-error` is used consistently"
261)]
262pub fn discover(directory: impl AsRef<std::path::Path>) -> Result<Repository, discover::Error> {
263 ThreadSafeRepository::discover(directory).map(Into::into)
264}
265
266/// Try to open a git repository in `directory` and search upwards through its parents until one is found,
267/// using `open_options` regardless of the trust level of the discovered repository.
268/// The detected trust level is retained, so repositories with reduced trust still restrict their behavior accordingly.
269#[expect(
270 clippy::result_large_err,
271 reason = "will be removed once `gix-error` is used consistently"
272)]
273pub fn discover_opts(
274 directory: impl AsRef<std::path::Path>,
275 options: discover::upwards::Options<'_>,
276 open_options: open::Options,
277) -> Result<Repository, discover::Error> {
278 ThreadSafeRepository::discover_opts(
279 directory,
280 options,
281 sec::trust::Mapping {
282 full: open_options.clone(),
283 reduced: open_options,
284 },
285 )
286 .map(Into::into)
287}
288
289/// Try to discover a git repository directly from the environment.
290///
291/// For details, see [`ThreadSafeRepository::discover_with_environment_overrides_opts()`].
292#[expect(
293 clippy::result_large_err,
294 reason = "will be removed once `gix-error` is used consistently"
295)]
296pub fn discover_with_environment_overrides(
297 directory: impl AsRef<std::path::Path>,
298) -> Result<Repository, discover::Error> {
299 ThreadSafeRepository::discover_with_environment_overrides(directory).map(Into::into)
300}
301
302/// Try to open a git repository directly from the environment.
303///
304/// See [`ThreadSafeRepository::open_with_environment_overrides()`].
305#[expect(
306 clippy::result_large_err,
307 reason = "will be removed once `gix-error` is used consistently"
308)]
309pub fn open_with_environment_overrides(directory: impl Into<std::path::PathBuf>) -> Result<Repository, open::Error> {
310 ThreadSafeRepository::open_with_environment_overrides(directory, Default::default()).map(Into::into)
311}
312
313/// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead.
314///
315/// # Examples
316///
317/// ```
318/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
319/// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }
320/// # let dir = doctest::tempdir()?;
321/// let repo = gix::init(dir.path())?;
322///
323/// assert!(repo.git_dir().is_dir());
324/// assert!(repo.head_name()?.is_some());
325/// assert!(repo.head()?.is_unborn());
326/// # Ok(()) }
327/// ```
328#[expect(
329 clippy::result_large_err,
330 reason = "will be removed once `gix-error` is used consistently"
331)]
332pub fn init(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> {
333 ThreadSafeRepository::init(directory, create::Kind::WithWorktree, create::Options::default()).map(Into::into)
334}
335
336/// See [`ThreadSafeRepository::init()`], but returns a [`Repository`] instead.
337#[expect(
338 clippy::result_large_err,
339 reason = "will be removed once `gix-error` is used consistently"
340)]
341pub fn init_bare(directory: impl AsRef<std::path::Path>) -> Result<Repository, init::Error> {
342 ThreadSafeRepository::init(directory, create::Kind::Bare, create::Options::default()).map(Into::into)
343}
344
345/// Create a platform for configuring a bare clone from `url` to the local `path`, using default options for opening it (but
346/// amended with using configuration from the git installation to ensure all authentication options are honored).
347///
348/// See [`clone::PrepareFetch::new()`] for a function to take full control over all options.
349#[expect(
350 clippy::result_large_err,
351 reason = "will be removed once `gix-error` is used consistently"
352)]
353pub fn prepare_clone_bare<Url, E>(
354 url: Url,
355 path: impl AsRef<std::path::Path>,
356) -> Result<clone::PrepareFetch, clone::Error>
357where
358 Url: std::convert::TryInto<gix_url::Url, Error = E>,
359 gix_url::parse::Error: From<E>,
360{
361 clone::PrepareFetch::new(
362 url,
363 path,
364 create::Kind::Bare,
365 create::Options::default(),
366 open_opts_with_git_binary_config(),
367 )
368}
369
370/// Create a platform for configuring a clone with main working tree from `url` to the local `path`, using default options for opening it
371/// (but amended with using configuration from the git installation to ensure all authentication options are honored).
372///
373/// See [`clone::PrepareFetch::new()`] for a function to take full control over all options.
374#[expect(
375 clippy::result_large_err,
376 reason = "will be removed once `gix-error` is used consistently"
377)]
378pub fn prepare_clone<Url, E>(url: Url, path: impl AsRef<std::path::Path>) -> Result<clone::PrepareFetch, clone::Error>
379where
380 Url: std::convert::TryInto<gix_url::Url, Error = E>,
381 gix_url::parse::Error: From<E>,
382{
383 clone::PrepareFetch::new(
384 url,
385 path,
386 create::Kind::WithWorktree,
387 create::Options::default(),
388 open_opts_with_git_binary_config(),
389 )
390}
391
392fn open_opts_with_git_binary_config() -> open::Options {
393 use gix_sec::trust::DefaultForLevel;
394 let mut opts = open::Options::default_for_level(gix_sec::Trust::Full);
395 opts.permissions.config.git_binary = true;
396 opts
397}
398
399/// See [`ThreadSafeRepository::open()`], but returns a [`Repository`] instead.
400///
401/// # Examples
402///
403/// ```
404/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
405/// # mod doctest { include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/doctest.rs")); }
406/// # let repo_dir = doctest::basic_repo_dir()?;
407/// let repo = doctest::open_repo(repo_dir)?;
408///
409/// assert_eq!(repo.head_name()?.expect("born").shorten(), "main");
410/// assert_eq!(repo.head_commit()?.decode()?.message, "c2\n");
411/// # Ok(()) }
412/// ```
413#[expect(
414 clippy::result_large_err,
415 reason = "will be removed once `gix-error` is used consistently"
416)]
417#[doc(alias = "git2")]
418pub fn open(directory: impl Into<std::path::PathBuf>) -> Result<Repository, open::Error> {
419 ThreadSafeRepository::open(directory).map(Into::into)
420}
421
422/// See [`ThreadSafeRepository::open_opts()`], but returns a [`Repository`] instead.
423#[expect(
424 clippy::result_large_err,
425 reason = "will be removed once `gix-error` is used consistently"
426)]
427#[doc(alias = "open_ext", alias = "git2")]
428pub fn open_opts(directory: impl Into<std::path::PathBuf>, options: open::Options) -> Result<Repository, open::Error> {
429 ThreadSafeRepository::open_opts(directory, options).map(Into::into)
430}
431
432/// Load configuration available without an existing repository, using the configuration-related portions of `options`.
433///
434/// `git_dir` supplies context for `includeIf.gitdir` conditions and does not have to exist. Without it, these
435/// conditions aren't matched. Repository-local and branch-dependent configuration isn't available at this stage.
436pub fn config(git_dir: Option<&std::path::Path>, options: &open::Options) -> Result<config::File, config::Error> {
437 let environment = options.permissions.env;
438 let git_install_dir = path::install_dir().ok();
439 let home = gix_path::env::home_dir().and_then(|home| environment.home.check_opt(home));
440 config::cache::load(
441 None,
442 &mut Vec::new(),
443 git_dir,
444 None,
445 git_install_dir.as_deref(),
446 home.as_deref(),
447 environment,
448 options.permissions.config,
449 options.lossy_config,
450 options.lenient_config,
451 &options.api_config_overrides,
452 &options.cli_config_overrides,
453 options.use_repository_local_environment,
454 )
455}
456
457///
458pub mod create;
459
460///
461pub mod open;
462
463///
464pub mod config;
465
466///
467#[cfg(feature = "mailmap")]
468pub mod mailmap;
469
470///
471#[cfg(feature = "notes")]
472pub mod note;
473
474///
475pub mod worktree;
476
477pub mod revision;
478
479#[cfg(feature = "attributes")]
480pub mod filter;
481
482///
483pub mod remote;
484
485///
486pub mod init;
487
488/// Not to be confused with 'status'.
489pub mod state;
490
491///
492#[cfg(feature = "status")]
493pub mod status;
494
495///
496pub mod shallow;
497
498///
499pub mod discover;
500
501pub mod env;
502
503#[cfg(feature = "attributes")]
504fn is_dir_to_mode(is_dir: bool) -> gix_index::entry::Mode {
505 if is_dir {
506 gix_index::entry::Mode::DIR
507 } else {
508 gix_index::entry::Mode::FILE
509 }
510}