Unified · Split
| @@ -25,6 +25,10 @@ This file is for tracking features that are less well implemented or less powerf · 24 unmodified lines | |||
| 25 | * **Objects larger than 32 bits cannot be loaded on 32 bit systems** | 25 | * **Objects larger than 32 bits cannot be loaded on 32 bit systems** |
| 26 | * in-memory representations objects cannot handle objects greater than the amount of addressable memory. | 26 | * in-memory representations objects cannot handle objects greater than the amount of addressable memory. |
| 27 | * This will not affect git LFS though. | 27 | * This will not affect git LFS though. |
| 28 | |||
| 29 | ### `gix` | ||
| 30 | |||
| 31 | * object replacements are read once upon opening the repository from their refs and changes to these won't be picked up. | ||
| 28 | 32 | ||
| 29 | ### `gix-url` | 33 | ### `gix-url` |
| 30 | 34 | ||
| @@ -347,6 +347,15 @@ fn apply_environment_overrides( · 346 unmodified lines | |||
| 347 | }, | 347 | }, |
| 348 | ], | 348 | ], |
| 349 | ), | 349 | ), |
| 350 | ( | ||
| 351 | "gitoxide", | ||
| 352 | Some(Cow::Borrowed("core".into())), | ||
| 353 | git_prefix, | ||
| 354 | &[{ | ||
| 355 | let key = &gitoxide::Core::SHALLOW_FILE; | ||
| 356 | (env(key), key.name) | ||
| 357 | }], | ||
| 358 | ), | ||
| 350 | ( | 359 | ( |
| 351 | "gitoxide", | 360 | "gitoxide", |
| 352 | Some(Cow::Borrowed("author".into())), | 361 | Some(Cow::Borrowed("author".into())), |
| @@ -1,3 +1,4 @@ | |||
| 1 | use crate::config; | ||
| 1 | use crate::config::tree::{keys, Gitoxide, Key, Section}; | 2 | use crate::config::tree::{keys, Gitoxide, Key, Section}; |
| 2 | 3 | ||
| 3 | impl Gitoxide { | 4 | impl Gitoxide { |
| @@ -5,6 +6,8 @@ impl Gitoxide { · 1 unmodified line | |||
| 5 | pub const ALLOW: Allow = Allow; | 6 | pub const ALLOW: Allow = Allow; |
| 6 | /// The `gitoxide.author` section. | 7 | /// The `gitoxide.author` section. |
| 7 | pub const AUTHOR: Author = Author; | 8 | pub const AUTHOR: Author = Author; |
| 9 | /// The `gitoxide.core` section. | ||
| 10 | pub const CORE: Core = Core; | ||
| 8 | /// The `gitoxide.commit` section. | 11 | /// The `gitoxide.commit` section. |
| 9 | pub const COMMIT: Commit = Commit; | 12 | pub const COMMIT: Commit = Commit; |
| 10 | /// The `gitoxide.committer` section. | 13 | /// The `gitoxide.committer` section. |
| @@ -39,6 +42,7 @@ impl Section for Gitoxide { · 28 unmodified lines | |||
| 39 | &[ | 42 | &[ |
| 40 | &Self::ALLOW, | 43 | &Self::ALLOW, |
| 41 | &Self::AUTHOR, | 44 | &Self::AUTHOR, |
| 45 | &Self::CORE, | ||
| 42 | &Self::COMMIT, | 46 | &Self::COMMIT, |
| 43 | &Self::COMMITTER, | 47 | &Self::COMMITTER, |
| 44 | &Self::HTTP, | 48 | &Self::HTTP, |
| @@ -56,6 +60,29 @@ mod subsections { · 11 unmodified lines | |||
| 56 | Tree, | 60 | Tree, |
| 57 | }; | 61 | }; |
| 58 | 62 | ||
| 63 | /// The `Core` sub-section. | ||
| 64 | #[derive(Copy, Clone, Default)] | ||
| 65 | pub struct Core; | ||
| 66 | |||
| 67 | impl Core { | ||
| 68 | /// The `gitoxide.core.shallowFile` key. | ||
| 69 | pub const SHALLOW_FILE: keys::Path = keys::Path::new_path("shallowFile", &Gitoxide::CORE) | ||
| 70 | .with_environment_override("GIT_SHALLOW_FILE") | ||
| 71 | .with_deviation( | ||
| 72 | "relative file paths will always be made relative to the git-common-dir, whereas `git` keeps them as is.", | ||
| 73 | ); | ||
| 74 | } | ||
| 75 | |||
| 76 | impl Section for Core { | ||
| 77 | fn name(&self) -> &str { | ||
| 78 | "core" | ||
| 79 | } | ||
| 80 | |||
| 81 | fn keys(&self) -> &[&dyn Key] { | ||
| 82 | &[&Self::SHALLOW_FILE] | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 59 | /// The `Http` sub-section. | 86 | /// The `Http` sub-section. |
| 60 | #[derive(Copy, Clone, Default)] | 87 | #[derive(Copy, Clone, Default)] |
| 61 | pub struct Http; | 88 | pub struct Http; |
| @@ -341,6 +368,7 @@ mod subsections { · 279 unmodified lines | |||
| 341 | } | 368 | } |
| 342 | } | 369 | } |
| 343 | } | 370 | } |
| 371 | pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Ssh, User}; | ||
| 344 | 372 | ||
| 345 | pub mod validate { | 373 | pub mod validate { |
| 346 | use std::error::Error; | 374 | use std::error::Error; |
| @@ -357,7 +385,3 @@ pub mod validate { · 10 unmodified lines | |||
| 357 | } | 385 | } |
| 358 | } | 386 | } |
| 359 | } | 387 | } |
| 360 | |||
| 361 | pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User}; | ||
| 362 | |||
| 363 | use crate::config; | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | pub use gix_diff::*; | ||
| 2 | |||
| 3 | /// | ||
| 4 | pub mod rename { | ||
| 5 | /// Determine how to do rename tracking. | ||
| 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 7 | pub enum Tracking { | ||
| 8 | /// Do not track renames at all, the fastest option. | ||
| 9 | Disabled, | ||
| 10 | /// Track renames. | ||
| 11 | Renames, | ||
| 12 | /// Track renames and copies. | ||
| 13 | /// | ||
| 14 | /// This is the most expensive option. | ||
| 15 | RenamesAndCopies, | ||
| 16 | } | ||
| 17 | } | ||
| @@ -90,9 +90,6 @@ impl<'repo> Id<'repo> { · 89 unmodified lines | |||
| 90 | impl<'repo> Id<'repo> { | 90 | impl<'repo> Id<'repo> { |
| 91 | /// Obtain a platform for traversing ancestors of this commit. | 91 | /// Obtain a platform for traversing ancestors of this commit. |
| 92 | /// | 92 | /// |
| 93 | /// Note that unless [`error_on_missing_commit()`][revision::Walk::error_on_missing_commit()] is enabled, which be default it is not, | ||
| 94 | /// one will always see an empty iteration even if this id is not a commit, instead of an error. | ||
| 95 | /// If this is undesirable, it's best to check for the correct object type before creating an iterator. | ||
| 96 | pub fn ancestors(&self) -> revision::walk::Platform<'repo> { | 93 | pub fn ancestors(&self) -> revision::walk::Platform<'repo> { |
| 97 | revision::walk::Platform::new(Some(self.inner), self.repo) | 94 | revision::walk::Platform::new(Some(self.inner), self.repo) |
| 98 | } | 95 | } |
| @@ -98,12 +98,7 @@ pub mod interrupt; · 97 unmodified lines | |||
| 98 | 98 | ||
| 99 | mod ext; | 99 | mod ext; |
| 100 | /// | 100 | /// |
| 101 | pub mod prelude { | 101 | pub mod prelude; |
| 102 | pub use gix_features::parallel::reduce::Finalize; | ||
| 103 | pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; | ||
| 104 | |||
| 105 | pub use crate::ext::*; | ||
| 106 | } | ||
| 107 | 102 | ||
| 108 | /// | 103 | /// |
| 109 | pub mod path; | 104 | pub mod path; |
| @@ -133,31 +128,10 @@ mod repository; · 23 unmodified lines | |||
| 133 | pub mod tag; | 128 | pub mod tag; |
| 134 | 129 | ||
| 135 | /// | 130 | /// |
| 136 | pub mod progress { | 131 | pub mod progress; |
| 137 | #[cfg(feature = "progress-tree")] | ||
| 138 | pub use gix_features::progress::prodash::tree; | ||
| 139 | pub use gix_features::progress::*; | ||
| 140 | } | ||
| 141 | 132 | ||
| 142 | /// | 133 | /// |
| 143 | pub mod diff { | 134 | pub mod diff; |
| 144 | pub use gix_diff::*; | ||
| 145 | /// | ||
| 146 | pub mod rename { | ||
| 147 | /// Determine how to do rename tracking. | ||
| 148 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 149 | pub enum Tracking { | ||
| 150 | /// Do not track renames at all, the fastest option. | ||
| 151 | Disabled, | ||
| 152 | /// Track renames. | ||
| 153 | Renames, | ||
| 154 | /// Track renames and copies. | ||
| 155 | /// | ||
| 156 | /// This is the most expensive option. | ||
| 157 | RenamesAndCopies, | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | 135 | ||
| 162 | /// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead. | 136 | /// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead. |
| 163 | #[allow(clippy::result_large_err)] | 137 | #[allow(clippy::result_large_err)] |
| @@ -238,20 +212,10 @@ pub fn open_opts(directory: impl Into<std::path::PathBuf>, options: open::Option · 74 unmodified lines | |||
| 238 | } | 212 | } |
| 239 | 213 | ||
| 240 | /// | 214 | /// |
| 241 | pub mod permission { | 215 | pub mod permission; |
| 242 | /// | 216 | |
| 243 | pub mod env_var { | ||
| 244 | /// | ||
| 245 | pub mod resource { | ||
| 246 | /// | ||
| 247 | pub type Error = gix_sec::permission::Error<std::path::PathBuf>; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | /// | 217 | /// |
| 252 | pub mod permissions { | 218 | pub mod permissions; |
| 253 | pub use crate::repository::permissions::{Config, Environment}; | ||
| 254 | } | ||
| 255 | pub use repository::permissions::Permissions; | 219 | pub use repository::permissions::Permissions; |
| 256 | 220 | ||
| 257 | /// | 221 | /// |
| @@ -278,33 +242,10 @@ pub mod remote; · 20 unmodified lines | |||
| 278 | pub mod init; | 242 | pub mod init; |
| 279 | 243 | ||
| 280 | /// Not to be confused with 'status'. | 244 | /// Not to be confused with 'status'. |
| 281 | pub mod state { | 245 | pub mod state; |
| 282 | /// Tell what operation is currently in progress. | 246 | |
| 283 | #[derive(Debug, PartialEq, Eq)] | 247 | /// |
| 284 | pub enum InProgress { | 248 | pub mod shallow; |
| 285 | /// A mailbox is being applied. | ||
| 286 | ApplyMailbox, | ||
| 287 | /// A rebase is happening while a mailbox is being applied. | ||
| 288 | // TODO: test | ||
| 289 | ApplyMailboxRebase, | ||
| 290 | /// A git bisect operation has not yet been concluded. | ||
| 291 | Bisect, | ||
| 292 | /// A cherry pick operation. | ||
| 293 | CherryPick, | ||
| 294 | /// A cherry pick with multiple commits pending. | ||
| 295 | CherryPickSequence, | ||
| 296 | /// A merge operation. | ||
| 297 | Merge, | ||
| 298 | /// A rebase operation. | ||
| 299 | Rebase, | ||
| 300 | /// An interactive rebase operation. | ||
| 301 | RebaseInteractive, | ||
| 302 | /// A revert operation. | ||
| 303 | Revert, | ||
| 304 | /// A revert operation with multiple commits pending. | ||
| 305 | RevertSequence, | ||
| 306 | } | ||
| 307 | } | ||
| 308 | 249 | ||
| 309 | /// | 250 | /// |
| 310 | pub mod discover; | 251 | pub mod discover; |
| @@ -267,6 +267,7 @@ impl ThreadSafeRepository { · 266 unmodified lines | |||
| 267 | // used when spawning new repositories off this one when following worktrees | 267 | // used when spawning new repositories off this one when following worktrees |
| 268 | linked_worktree_options: options, | 268 | linked_worktree_options: options, |
| 269 | index: gix_features::fs::MutableSnapshot::new().into(), | 269 | index: gix_features::fs::MutableSnapshot::new().into(), |
| 270 | shallow_commits: gix_features::fs::MutableSnapshot::new().into(), | ||
| 270 | }) | 271 | }) |
| 271 | } | 272 | } |
| 272 | } | 273 | } |
| @@ -0,0 +1,8 @@ | |||
| 1 | /// | ||
| 2 | pub mod env_var { | ||
| 3 | /// | ||
| 4 | pub mod resource { | ||
| 5 | /// | ||
| 6 | pub type Error = gix_sec::permission::Error<std::path::PathBuf>; | ||
| 7 | } | ||
| 8 | } | ||
| @@ -0,0 +1 @@ | |||
| 1 | pub use crate::repository::permissions::{Config, Environment}; | ||
| @@ -0,0 +1,4 @@ | |||
| 1 | pub use gix_features::parallel::reduce::Finalize; | ||
| 2 | pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; | ||
| 3 | |||
| 4 | pub use crate::ext::*; | ||
| @@ -0,0 +1,3 @@ | |||
| 1 | #[cfg(feature = "progress-tree")] | ||
| 2 | pub use gix_features::progress::prodash::tree; | ||
| 3 | pub use gix_features::progress::*; | ||
| @@ -8,6 +8,7 @@ impl Clone for crate::Repository { · 7 unmodified lines | |||
| 8 | self.config.clone(), | 8 | self.config.clone(), |
| 9 | self.options.clone(), | 9 | self.options.clone(), |
| 10 | self.index.clone(), | 10 | self.index.clone(), |
| 11 | self.shallow_commits.clone(), | ||
| 11 | ) | 12 | ) |
| 12 | } | 13 | } |
| 13 | } | 14 | } |
| @@ -40,6 +41,7 @@ impl From<&crate::ThreadSafeRepository> for crate::Repository { · 26 unmodified lines | |||
| 40 | repo.config.clone(), | 41 | repo.config.clone(), |
| 41 | repo.linked_worktree_options.clone(), | 42 | repo.linked_worktree_options.clone(), |
| 42 | repo.index.clone(), | 43 | repo.index.clone(), |
| 44 | repo.shallow_commits.clone(), | ||
| 43 | ) | 45 | ) |
| 44 | } | 46 | } |
| 45 | } | 47 | } |
| @@ -54,6 +56,7 @@ impl From<crate::ThreadSafeRepository> for crate::Repository { · 8 unmodified lines | |||
| 54 | repo.config, | 56 | repo.config, |
| 55 | repo.linked_worktree_options, | 57 | repo.linked_worktree_options, |
| 56 | repo.index, | 58 | repo.index, |
| 59 | repo.shallow_commits, | ||
| 57 | ) | 60 | ) |
| 58 | } | 61 | } |
| 59 | } | 62 | } |
| @@ -68,6 +71,7 @@ impl From<crate::Repository> for crate::ThreadSafeRepository { · 8 unmodified lines | |||
| 68 | config: r.config, | 71 | config: r.config, |
| 69 | linked_worktree_options: r.options, | 72 | linked_worktree_options: r.options, |
| 70 | index: r.index, | 73 | index: r.index, |
| 74 | shallow_commits: r.shallow_commits, | ||
| 71 | } | 75 | } |
| 72 | } | 76 | } |
| 73 | } | 77 | } |
| @@ -1,6 +1,7 @@ | |||
| 1 | use std::cell::RefCell; | 1 | use std::cell::RefCell; |
| 2 | 2 | ||
| 3 | impl crate::Repository { | 3 | impl crate::Repository { |
| 4 | #[allow(clippy::too_many_arguments)] | ||
| 4 | pub(crate) fn from_refs_and_objects( | 5 | pub(crate) fn from_refs_and_objects( |
| 5 | refs: crate::RefStore, | 6 | refs: crate::RefStore, |
| 6 | objects: crate::OdbHandle, | 7 | objects: crate::OdbHandle, |
| @@ -9,6 +10,7 @@ impl crate::Repository { · 2 unmodified lines | |||
| 9 | config: crate::config::Cache, | 10 | config: crate::config::Cache, |
| 10 | linked_worktree_options: crate::open::Options, | 11 | linked_worktree_options: crate::open::Options, |
| 11 | index: crate::worktree::IndexStorage, | 12 | index: crate::worktree::IndexStorage, |
| 13 | shallow_commits: crate::shallow::CommitsStorage, | ||
| 12 | ) -> Self { | 14 | ) -> Self { |
| 13 | let objects = setup_objects(objects, &config); | 15 | let objects = setup_objects(objects, &config); |
| 14 | crate::Repository { | 16 | crate::Repository { |
| @@ -20,6 +22,7 @@ impl crate::Repository { · 5 unmodified lines | |||
| 20 | config, | 22 | config, |
| 21 | options: linked_worktree_options, | 23 | options: linked_worktree_options, |
| 22 | index, | 24 | index, |
| 25 | shallow_commits, | ||
| 23 | } | 26 | } |
| 24 | } | 27 | } |
| 25 | 28 | ||
| @@ -30,6 +30,7 @@ pub(crate) mod permissions; · 29 unmodified lines | |||
| 30 | mod reference; | 30 | mod reference; |
| 31 | mod remote; | 31 | mod remote; |
| 32 | mod revision; | 32 | mod revision; |
| 33 | mod shallow; | ||
| 33 | mod snapshots; | 34 | mod snapshots; |
| 34 | mod state; | 35 | mod state; |
| 35 | mod thread_safe; | 36 | mod thread_safe; |
| @@ -0,0 +1,59 @@ | |||
| 1 | use crate::bstr::ByteSlice; | ||
| 2 | use crate::config::tree::{gitoxide, Key}; | ||
| 3 | use crate::Repository; | ||
| 4 | use std::borrow::Cow; | ||
| 5 | use std::path::PathBuf; | ||
| 6 | |||
| 7 | impl Repository { | ||
| 8 | /// Return `true` if the repository is a shallow clone, i.e. contains history only up to a certain depth. | ||
| 9 | pub fn is_shallow(&self) -> bool { | ||
| 10 | self.shallow_file() | ||
| 11 | .metadata() | ||
| 12 | .map_or(false, |m| m.is_file() && m.len() > 0) | ||
| 13 | } | ||
| 14 | |||
| 15 | /// Return a shared list of shallow commits which is updated automatically if the in-memory snapshot has become stale as the underlying file | ||
| 16 | /// on disk has changed. | ||
| 17 | /// | ||
| 18 | /// The shared list is shared across all clones of this repository. | ||
| 19 | pub fn shallow_commits(&self) -> Result<Option<crate::shallow::Commits>, crate::shallow::open::Error> { | ||
| 20 | self.shallow_commits.recent_snapshot( | ||
| 21 | || self.shallow_file().metadata().ok().and_then(|m| m.modified().ok()), | ||
| 22 | || { | ||
| 23 | let buf = match std::fs::read(self.shallow_file()) { | ||
| 24 | Ok(buf) => buf, | ||
| 25 | Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None), | ||
| 26 | Err(err) => return Err(err.into()), | ||
| 27 | }; | ||
| 28 | |||
| 29 | let mut commits = buf | ||
| 30 | .lines() | ||
| 31 | .map(gix_hash::ObjectId::from_hex) | ||
| 32 | .collect::<Result<Vec<_>, _>>()?; | ||
| 33 | |||
| 34 | commits.sort(); | ||
| 35 | if commits.is_empty() { | ||
| 36 | Ok(None) | ||
| 37 | } else { | ||
| 38 | Ok(Some(commits)) | ||
| 39 | } | ||
| 40 | }, | ||
| 41 | ) | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Return the path to the `shallow` file which contains hashes, one per line, that describe commits that don't have their | ||
| 45 | /// parents within this repository. | ||
| 46 | /// | ||
| 47 | /// Note that it may not exist if the repository isn't actually shallow. | ||
| 48 | pub fn shallow_file(&self) -> PathBuf { | ||
| 49 | let shallow_name = self | ||
| 50 | .config | ||
| 51 | .resolved | ||
| 52 | .string_filter_by_key( | ||
| 53 | gitoxide::Core::SHALLOW_FILE.logical_name().as_str(), | ||
| 54 | &mut self.filter_config_section(), | ||
| 55 | ) | ||
| 56 | .unwrap_or_else(|| Cow::Borrowed("shallow".into())); | ||
| 57 | self.common_dir().join(gix_path::from_bstr(shallow_name)) | ||
| 58 | } | ||
| 59 | } | ||
| @@ -177,6 +177,8 @@ pub enum Error { · 176 unmodified lines | |||
| 177 | }, | 177 | }, |
| 178 | #[error(transparent)] | 178 | #[error(transparent)] |
| 179 | Traverse(#[from] gix_traverse::commit::ancestors::Error), | 179 | Traverse(#[from] gix_traverse::commit::ancestors::Error), |
| 180 | #[error(transparent)] | ||
| 181 | Walk(#[from] crate::revision::walk::Error), | ||
| 180 | #[error("Spec does not contain a single object id")] | 182 | #[error("Spec does not contain a single object id")] |
| 181 | SingleNotFound, | 183 | SingleNotFound, |
| 182 | } | 184 | } |
| @@ -3,6 +3,16 @@ use gix_odb::FindExt; · 2 unmodified lines | |||
| 3 | 3 | ||
| 4 | use crate::{revision, Repository}; | 4 | use crate::{revision, Repository}; |
| 5 | 5 | ||
| 6 | /// The error returned by [`Platform::all()`]. | ||
| 7 | #[derive(Debug, thiserror::Error)] | ||
| 8 | #[allow(missing_docs)] | ||
| 9 | pub enum Error { | ||
| 10 | #[error(transparent)] | ||
| 11 | AncestorIter(#[from] gix_traverse::commit::ancestors::Error), | ||
| 12 | #[error(transparent)] | ||
| 13 | ShallowCommits(#[from] crate::shallow::open::Error), | ||
| 14 | } | ||
| 15 | |||
| 6 | /// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed, | 16 | /// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed, |
| 7 | /// returned by [`Repository::rev_walk()`]. | 17 | /// returned by [`Repository::rev_walk()`]. |
| 8 | pub struct Platform<'repo> { | 18 | pub struct Platform<'repo> { |
| @@ -46,7 +56,7 @@ impl<'repo> Platform<'repo> { · 37 unmodified lines | |||
| 46 | /// | 56 | /// |
| 47 | /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo | 57 | /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo |
| 48 | /// to greatly speed up performance if the returned id is supposed to be looked up right after. | 58 | /// to greatly speed up performance if the returned id is supposed to be looked up right after. |
| 49 | pub fn all(self) -> Result<revision::Walk<'repo>, gix_traverse::commit::ancestors::Error> { | 59 | pub fn all(self) -> Result<revision::Walk<'repo>, Error> { |
| 50 | let Platform { | 60 | let Platform { |
| 51 | repo, | 61 | repo, |
| 52 | tips, | 62 | tips, |
| @@ -56,16 +66,36 @@ impl<'repo> Platform<'repo> { · 3 unmodified lines | |||
| 56 | Ok(revision::Walk { | 66 | Ok(revision::Walk { |
| 57 | repo, | 67 | repo, |
| 58 | inner: Box::new( | 68 | inner: Box::new( |
| 59 | gix_traverse::commit::Ancestors::new( | 69 | gix_traverse::commit::Ancestors::filtered( |
| 60 | tips, | 70 | tips, |
| 61 | gix_traverse::commit::ancestors::State::default(), | 71 | gix_traverse::commit::ancestors::State::default(), |
| 62 | move |oid, buf| repo.objects.find_commit_iter(oid, buf), | 72 | move |oid, buf| repo.objects.find_commit_iter(oid, buf), |
| 73 | { | ||
| 74 | let shallow_commits = repo.shallow_commits()?; | ||
| 75 | let mut grafted_parents_to_skip = Vec::new(); | ||
| 76 | let mut buf = Vec::new(); | ||
| 77 | move |id| match shallow_commits.as_ref() { | ||
| 78 | Some(commits) => { | ||
| 79 | let id = id.to_owned(); | ||
| 80 | if let Ok(idx) = grafted_parents_to_skip.binary_search(&id) { | ||
| 81 | grafted_parents_to_skip.remove(idx); | ||
| 82 | return false; | ||
| 83 | }; | ||
| 84 | if commits.binary_search(&id).is_ok() { | ||
| 85 | if let Ok(commit) = repo.objects.find_commit_iter(id, &mut buf) { | ||
| 86 | grafted_parents_to_skip.extend(commit.parent_ids()); | ||
| 87 | grafted_parents_to_skip.sort(); | ||
| 88 | } | ||
| 89 | }; | ||
| 90 | true | ||
| 91 | } | ||
| 92 | None => true, | ||
| 93 | } | ||
| 94 | }, | ||
| 63 | ) | 95 | ) |
| 64 | .sorting(sorting)? | 96 | .sorting(sorting)? |
| 65 | .parents(parents), | 97 | .parents(parents), |
| 66 | ), | 98 | ), |
| 67 | is_shallow: None, | ||
| 68 | error_on_missing_commit: false, | ||
| 69 | }) | 99 | }) |
| 70 | } | 100 | } |
| 71 | } | 101 | } |
| @@ -78,50 +108,13 @@ pub(crate) mod iter { · 6 unmodified lines | |||
| 78 | pub(crate) repo: &'repo crate::Repository, | 108 | pub(crate) repo: &'repo crate::Repository, |
| 79 | pub(crate) inner: | 109 | pub(crate) inner: |
| 80 | Box<dyn Iterator<Item = Result<gix_hash::ObjectId, gix_traverse::commit::ancestors::Error>> + 'repo>, | 110 | Box<dyn Iterator<Item = Result<gix_hash::ObjectId, gix_traverse::commit::ancestors::Error>> + 'repo>, |
| 81 | pub(crate) error_on_missing_commit: bool, | ||
| 82 | // TODO: tests | ||
| 83 | /// After iteration this flag is true if the iteration was stopped prematurely due to missing parent commits. | ||
| 84 | /// Note that this flag won't be `Some` if any iteration error occurs, which is the case if | ||
| 85 | /// [`error_on_missing_commit()`][Walk::error_on_missing_commit()] was called. | ||
| 86 | /// | ||
| 87 | /// This happens if a repository is a shallow clone. | ||
| 88 | /// Note that this value is `None` as long as the iteration isn't complete. | ||
| 89 | pub is_shallow: Option<bool>, | ||
| 90 | } | ||
| 91 | |||
| 92 | impl<'repo> Walk<'repo> { | ||
| 93 | // TODO: tests | ||
| 94 | /// Once invoked, the iteration will return an error if a commit cannot be found in the object database. This typically happens | ||
| 95 | /// when operating on a shallow clone and thus is non-critical by default. | ||
| 96 | /// | ||
| 97 | /// Check the [`is_shallow`][Walk::is_shallow] field once the iteration ended otherwise to learn if a shallow commit graph | ||
| 98 | /// was encountered. | ||
| 99 | pub fn error_on_missing_commit(mut self) -> Self { | ||
| 100 | self.error_on_missing_commit = true; | ||
| 101 | self | ||
| 102 | } | ||
| 103 | } | 111 | } |
| 104 | 112 | ||
| 105 | impl<'repo> Iterator for Walk<'repo> { | 113 | impl<'repo> Iterator for Walk<'repo> { |
| 106 | type Item = Result<Id<'repo>, gix_traverse::commit::ancestors::Error>; | 114 | type Item = Result<Id<'repo>, gix_traverse::commit::ancestors::Error>; |
| 107 | 115 | ||
| 108 | fn next(&mut self) -> Option<Self::Item> { | 116 | fn next(&mut self) -> Option<Self::Item> { |
| 109 | match self.inner.next() { | 117 | self.inner.next().map(|res| res.map(|id| id.attach(self.repo))) |
| 110 | None => { | ||
| 111 | self.is_shallow = Some(false); | ||
| 112 | None | ||
| 113 | } | ||
| 114 | Some(Ok(oid)) => Some(Ok(oid.attach(self.repo))), | ||
| 115 | Some(Err(err @ gix_traverse::commit::ancestors::Error::FindExisting { .. })) => { | ||
| 116 | if self.error_on_missing_commit { | ||
| 117 | Some(Err(err)) | ||
| 118 | } else { | ||
| 119 | self.is_shallow = Some(true); | ||
| 120 | None | ||
| 121 | } | ||
| 122 | } | ||
| 123 | Some(Err(err)) => Some(Err(err)), | ||
| 124 | } | ||
| 125 | } | 118 | } |
| 126 | } | 119 | } |
| 127 | } | 120 | } |
| @@ -0,0 +1,18 @@ | |||
| 1 | pub(crate) type CommitsStorage = | ||
| 2 | gix_features::threading::OwnShared<gix_features::fs::MutableSnapshot<Vec<gix_hash::ObjectId>>>; | ||
| 3 | /// A lazily loaded and auto-updated list of commits which are at the shallow boundary (behind which there are no commits available), | ||
| 4 | /// sorted to allow bisecting. | ||
| 5 | pub type Commits = gix_features::fs::SharedSnapshot<Vec<gix_hash::ObjectId>>; | ||
| 6 | |||
| 7 | /// | ||
| 8 | pub mod open { | ||
| 9 | /// The error returned by [`Repository::shallow_commits()`][crate::Repository::shallow_commits()]. | ||
| 10 | #[derive(Debug, thiserror::Error)] | ||
| 11 | #[allow(missing_docs)] | ||
| 12 | pub enum Error { | ||
| 13 | #[error("Could not open shallow file for reading")] | ||
| 14 | Io(#[from] std::io::Error), | ||
| 15 | #[error("Could not decode a line in shallow file as hex-encoded object hash")] | ||
| 16 | DecodeHash(#[from] gix_hash::decode::Error), | ||
| 17 | } | ||
| 18 | } | ||
| @@ -0,0 +1,25 @@ | |||
| 1 | /// Tell what operation is currently in progress. | ||
| 2 | #[derive(Debug, PartialEq, Eq)] | ||
| 3 | pub enum InProgress { | ||
| 4 | /// A mailbox is being applied. | ||
| 5 | ApplyMailbox, | ||
| 6 | /// A rebase is happening while a mailbox is being applied. | ||
| 7 | // TODO: test | ||
| 8 | ApplyMailboxRebase, | ||
| 9 | /// A git bisect operation has not yet been concluded. | ||
| 10 | Bisect, | ||
| 11 | /// A cherry pick operation. | ||
| 12 | CherryPick, | ||
| 13 | /// A cherry pick with multiple commits pending. | ||
| 14 | CherryPickSequence, | ||
| 15 | /// A merge operation. | ||
| 16 | Merge, | ||
| 17 | /// A rebase operation. | ||
| 18 | Rebase, | ||
| 19 | /// An interactive rebase operation. | ||
| 20 | RebaseInteractive, | ||
| 21 | /// A revert operation. | ||
| 22 | Revert, | ||
| 23 | /// A revert operation with multiple commits pending. | ||
| 24 | RevertSequence, | ||
| 25 | } | ||
| @@ -152,6 +152,7 @@ pub struct Repository { · 151 unmodified lines | |||
| 152 | /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. | 152 | /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. |
| 153 | pub(crate) options: crate::open::Options, | 153 | pub(crate) options: crate::open::Options, |
| 154 | pub(crate) index: crate::worktree::IndexStorage, | 154 | pub(crate) index: crate::worktree::IndexStorage, |
| 155 | pub(crate) shallow_commits: crate::shallow::CommitsStorage, | ||
| 155 | } | 156 | } |
| 156 | 157 | ||
| 157 | /// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_ | 158 | /// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_ |
| @@ -175,6 +176,7 @@ pub struct ThreadSafeRepository { · 17 unmodified lines | |||
| 175 | pub(crate) linked_worktree_options: crate::open::Options, | 176 | pub(crate) linked_worktree_options: crate::open::Options, |
| 176 | /// The index of this instances worktree. | 177 | /// The index of this instances worktree. |
| 177 | pub(crate) index: crate::worktree::IndexStorage, | 178 | pub(crate) index: crate::worktree::IndexStorage, |
| 179 | pub(crate) shallow_commits: crate::shallow::CommitsStorage, | ||
| 178 | } | 180 | } |
| 179 | 181 | ||
| 180 | /// A remote which represents a way to interact with hosts for remote clones of the parent repository. | 182 | /// A remote which represents a way to interact with hosts for remote clones of the parent repository. |
| @@ -1,6 +1,7 @@ | |||
| 1 | /make_worktree_repo.tar.xz | 1 | /make_worktree_repo.tar.xz |
| 2 | /make_worktree_repo_with_configs.tar.xz | 2 | /make_worktree_repo_with_configs.tar.xz |
| 3 | /make_remote_repos.tar.xz | 3 | /make_remote_repos.tar.xz |
| 4 | /make_complex_shallow_repo.tar.xz | ||
| 4 | /make_fetch_repos.tar.xz | 5 | /make_fetch_repos.tar.xz |
| 5 | /make_core_worktree_repo.tar.xz | 6 | /make_core_worktree_repo.tar.xz |
| 6 | /make_signatures_repo.tar.xz | 7 | /make_signatures_repo.tar.xz |
| @@ -0,0 +1,3 @@ | |||
| 1 | version https://git-lfs.github.com/spec/v1 | ||
| 2 | oid sha256:c56c269562ef67b1f8bd46640e6ad9d196cbc9c7c609300ffd6a8da3bc501852 | ||
| 3 | size 12632 | ||
| @@ -0,0 +1,7 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | set -eu -o pipefail | ||
| 3 | |||
| 4 | remote="${1:?First argument is the complex repo to clone from}" | ||
| 5 | |||
| 6 | git clone --depth 3 file://"$remote" shallow | ||
| 7 | git clone --depth 3 --bare file://"$remote" shallow.git | ||
| @@ -0,0 +1,28 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | set -eu -o pipefail | ||
| 3 | |||
| 4 | mkdir base | ||
| 5 | (cd base | ||
| 6 | git init -q | ||
| 7 | |||
| 8 | git checkout -b main | ||
| 9 | touch a && git add a | ||
| 10 | git commit -q -m c1 | ||
| 11 | echo 1 >> a | ||
| 12 | git commit -q -am c2 | ||
| 13 | echo 1 >> a | ||
| 14 | git commit -q -am c3 | ||
| 15 | ) | ||
| 16 | |||
| 17 | mkdir empty | ||
| 18 | (cd empty | ||
| 19 | git init -q | ||
| 20 | |||
| 21 | git checkout -b main | ||
| 22 | touch a && git add a | ||
| 23 | git commit -q -m c1 | ||
| 24 | touch .git/shallow | ||
| 25 | ) | ||
| 26 | |||
| 27 | git clone --depth 1 --bare file://$PWD/base shallow.git | ||
| 28 | git clone --depth 1 file://$PWD/base shallow | ||
| @@ -5,6 +5,7 @@ mod object; · 4 unmodified lines | |||
| 5 | mod open; | 5 | mod open; |
| 6 | mod reference; | 6 | mod reference; |
| 7 | mod remote; | 7 | mod remote; |
| 8 | mod shallow; | ||
| 8 | mod state; | 9 | mod state; |
| 9 | mod worktree; | 10 | mod worktree; |
| 10 | 11 | ||
| @@ -194,7 +194,8 @@ mod with_overrides { · 193 unmodified lines | |||
| 194 | .set("GIT_SSL_VERSION", "tlsv1.3") | 194 | .set("GIT_SSL_VERSION", "tlsv1.3") |
| 195 | .set("GIT_SSH_VARIANT", "ssh-variant-env") | 195 | .set("GIT_SSH_VARIANT", "ssh-variant-env") |
| 196 | .set("GIT_SSH_COMMAND", "ssh-command-env") | 196 | .set("GIT_SSH_COMMAND", "ssh-command-env") |
| 197 | .set("GIT_SSH", "ssh-command-fallback-env"); | 197 | .set("GIT_SSH", "ssh-command-fallback-env") |
| 198 | .set("GIT_SHALLOW_FILE", "shallow-file-env"); | ||
| 198 | let mut opts = gix::open::Options::isolated() | 199 | let mut opts = gix::open::Options::isolated() |
| 199 | .cli_overrides([ | 200 | .cli_overrides([ |
| 200 | "http.userAgent=agent-from-cli", | 201 | "http.userAgent=agent-from-cli", |
| @@ -206,6 +207,7 @@ mod with_overrides { · 5 unmodified lines | |||
| 206 | "core.sshCommand=ssh-command-cli", | 207 | "core.sshCommand=ssh-command-cli", |
| 207 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-cli", | 208 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-cli", |
| 208 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-cli", | 209 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-cli", |
| 210 | "gitoxide.core.shallowFile=shallow-file-cli", | ||
| 209 | ]) | 211 | ]) |
| 210 | .config_overrides([ | 212 | .config_overrides([ |
| 211 | "http.userAgent=agent-from-api", | 213 | "http.userAgent=agent-from-api", |
| @@ -217,6 +219,7 @@ mod with_overrides { · 5 unmodified lines | |||
| 217 | "core.sshCommand=ssh-command-api", | 219 | "core.sshCommand=ssh-command-api", |
| 218 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-api", | 220 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-api", |
| 219 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-api", | 221 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-api", |
| 222 | "gitoxide.core.shallowFile=shallow-file-api", | ||
| 220 | ]); | 223 | ]); |
| 221 | opts.permissions.env.git_prefix = Permission::Allow; | 224 | opts.permissions.env.git_prefix = Permission::Allow; |
| 222 | opts.permissions.env.http_transport = Permission::Allow; | 225 | opts.permissions.env.http_transport = Permission::Allow; |
| @@ -229,6 +232,16 @@ mod with_overrides { · 6 unmodified lines | |||
| 229 | "config always refers to the local one for safety" | 232 | "config always refers to the local one for safety" |
| 230 | ); | 233 | ); |
| 231 | let config = repo.config_snapshot(); | 234 | let config = repo.config_snapshot(); |
| 235 | assert_eq!( | ||
| 236 | config | ||
| 237 | .strings_by_key("gitoxide.core.shallowFile") | ||
| 238 | .expect("at least one value"), | ||
| 239 | [ | ||
| 240 | cow_bstr("shallow-file-cli"), | ||
| 241 | cow_bstr("shallow-file-api"), | ||
| 242 | cow_bstr("shallow-file-env") | ||
| 243 | ] | ||
| 244 | ); | ||
| 232 | assert_eq!( | 245 | assert_eq!( |
| 233 | config.strings_by_key("http.userAgent").expect("at least one value"), | 246 | config.strings_by_key("http.userAgent").expect("at least one value"), |
| 234 | [ | 247 | [ |
| @@ -0,0 +1,99 @@ | |||
| 1 | use crate::util::{hex_to_id, named_subrepo_opts}; | ||
| 2 | |||
| 3 | #[test] | ||
| 4 | fn no() -> crate::Result { | ||
| 5 | for name in ["base", "empty"] { | ||
| 6 | let repo = named_subrepo_opts("make_shallow_repo.sh", name, crate::restricted())?; | ||
| 7 | assert!(!repo.is_shallow()); | ||
| 8 | assert!(repo.shallow_commits()?.is_none()); | ||
| 9 | let commits: Vec<_> = repo.head_id()?.ancestors().all()?.collect::<Result<_, _>>()?; | ||
| 10 | let expected = if name == "base" { | ||
| 11 | vec![ | ||
| 12 | hex_to_id("30887839de28edf7ab66c860e5c58b4d445f6b12"), | ||
| 13 | hex_to_id("d8523dfd5a7aa16562fa1c3e1d3b4a4494f97876"), | ||
| 14 | hex_to_id("05dc291f5376cde200316cb0b74b00cfebc79ea4"), | ||
| 15 | ] | ||
| 16 | } else { | ||
| 17 | vec![hex_to_id("05dc291f5376cde200316cb0b74b00cfebc79ea4")] | ||
| 18 | }; | ||
| 19 | assert_eq!(commits, expected); | ||
| 20 | } | ||
| 21 | Ok(()) | ||
| 22 | } | ||
| 23 | |||
| 24 | #[test] | ||
| 25 | fn yes() -> crate::Result { | ||
| 26 | for name in ["shallow.git", "shallow"] { | ||
| 27 | let repo = named_subrepo_opts("make_shallow_repo.sh", name, crate::restricted())?; | ||
| 28 | assert!(repo.is_shallow()); | ||
| 29 | assert_eq!( | ||
| 30 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 31 | [hex_to_id("30887839de28edf7ab66c860e5c58b4d445f6b12")] | ||
| 32 | ); | ||
| 33 | } | ||
| 34 | Ok(()) | ||
| 35 | } | ||
| 36 | |||
| 37 | mod traverse { | ||
| 38 | use crate::util::{hex_to_id, named_subrepo_opts}; | ||
| 39 | use gix_traverse::commit::Sorting; | ||
| 40 | |||
| 41 | #[test] | ||
| 42 | fn boundary_is_detected_triggering_no_error() -> crate::Result { | ||
| 43 | for name in ["shallow.git", "shallow"] { | ||
| 44 | let repo = named_subrepo_opts("make_shallow_repo.sh", name, crate::restricted())?; | ||
| 45 | let commits: Vec<_> = repo.head_id()?.ancestors().all()?.collect::<Result<_, _>>()?; | ||
| 46 | assert_eq!(commits, [hex_to_id("30887839de28edf7ab66c860e5c58b4d445f6b12")]); | ||
| 47 | } | ||
| 48 | Ok(()) | ||
| 49 | } | ||
| 50 | |||
| 51 | #[test] | ||
| 52 | fn complex_graphs_can_be_iterated_despite_multiple_shallow_boundaries() -> crate::Result { | ||
| 53 | let base = gix_path::realpath(gix_testtools::scripted_fixture_read_only("make_remote_repos.sh")?.join("base"))?; | ||
| 54 | let shallow_base = gix_testtools::scripted_fixture_read_only_with_args( | ||
| 55 | "make_complex_shallow_repo.sh", | ||
| 56 | Some(base.to_string_lossy()), | ||
| 57 | )?; | ||
| 58 | for name in ["shallow.git", "shallow"] { | ||
| 59 | let repo = gix::open_opts(shallow_base.join(name), crate::restricted())?; | ||
| 60 | assert_eq!( | ||
| 61 | repo.shallow_commits()?.expect("present").as_slice(), | ||
| 62 | [ | ||
| 63 | hex_to_id("27e71576a6335294aa6073ab767f8b36bdba81d0"), | ||
| 64 | hex_to_id("82024b2ef7858273337471cbd1ca1cedbdfd5616"), | ||
| 65 | hex_to_id("b5152869aedeb21e55696bb81de71ea1bb880c85"), | ||
| 66 | ] | ||
| 67 | ); | ||
| 68 | let commits: Vec<_> = repo | ||
| 69 | .head_id()? | ||
| 70 | .ancestors() | ||
| 71 | .sorting(Sorting::ByCommitTimeNewestFirst) | ||
| 72 | .all()? | ||
| 73 | .collect::<Result<_, _>>()?; | ||
| 74 | assert_eq!( | ||
| 75 | commits, | ||
| 76 | [ | ||
| 77 | "f99771fe6a1b535783af3163eba95a927aae21d5", | ||
| 78 | "2d9d136fb0765f2e24c44a0f91984318d580d03b", | ||
| 79 | "dfd0954dabef3b64f458321ef15571cc1a46d552", | ||
| 80 | "b5152869aedeb21e55696bb81de71ea1bb880c85", | ||
| 81 | "27e71576a6335294aa6073ab767f8b36bdba81d0", | ||
| 82 | "82024b2ef7858273337471cbd1ca1cedbdfd5616", | ||
| 83 | ] | ||
| 84 | .into_iter() | ||
| 85 | .map(hex_to_id) | ||
| 86 | .collect::<Vec<_>>() | ||
| 87 | ); | ||
| 88 | |||
| 89 | // should be | ||
| 90 | // * f99771f - (HEAD -> main, origin/main, origin/HEAD) A (18 years ago) <A U Thor> | ||
| 91 | // | * 2d9d136 - C (18 years ago) <A U Thor> | ||
| 92 | // *-. | dfd0954 - (tag: b-tag) B (18 years ago) <A U Thor> | ||
| 93 | // | | * 27e7157 - (grafted) F (18 years ago) <A U Thor> | ||
| 94 | // | * b515286 - (grafted) E (18 years ago) <A U Thor> | ||
| 95 | // * 82024b2 - (grafted) D (18 years ago) <A U Thor> | ||
| 96 | } | ||
| 97 | Ok(()) | ||
| 98 | } | ||
| 99 | } | ||