Unified · Split
| @@ -25,6 +25,10 @@ This file is for tracking features that are less well implemented or less powerf · 24 unmodified lines | ||
| 25 | 25 | * **Objects larger than 32 bits cannot be loaded on 32 bit systems** |
| 26 | 26 | * in-memory representations objects cannot handle objects greater than the amount of addressable memory. |
| 27 | 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 | 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 | 360 | "gitoxide", |
| 352 | 361 | Some(Cow::Borrowed("author".into())), |
| @@ -1,3 +1,4 @@ | ||
| 1 | +use crate::config; | |
| 1 | 2 | use crate::config::tree::{keys, Gitoxide, Key, Section}; |
| 2 | 3 | |
| 3 | 4 | impl Gitoxide { |
| @@ -5,6 +6,8 @@ impl Gitoxide { · 1 unmodified line | ||
| 5 | 6 | pub const ALLOW: Allow = Allow; |
| 6 | 7 | /// The `gitoxide.author` section. |
| 7 | 8 | pub const AUTHOR: Author = Author; |
| 9 | + /// The `gitoxide.core` section. | |
| 10 | + pub const CORE: Core = Core; | |
| 8 | 11 | /// The `gitoxide.commit` section. |
| 9 | 12 | pub const COMMIT: Commit = Commit; |
| 10 | 13 | /// The `gitoxide.committer` section. |
| @@ -39,6 +42,7 @@ impl Section for Gitoxide { · 28 unmodified lines | ||
| 39 | 42 | &[ |
| 40 | 43 | &Self::ALLOW, |
| 41 | 44 | &Self::AUTHOR, |
| 45 | + &Self::CORE, | |
| 42 | 46 | &Self::COMMIT, |
| 43 | 47 | &Self::COMMITTER, |
| 44 | 48 | &Self::HTTP, |
| @@ -56,6 +60,29 @@ mod subsections { · 11 unmodified lines | ||
| 56 | 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 | 86 | /// The `Http` sub-section. |
| 60 | 87 | #[derive(Copy, Clone, Default)] |
| 61 | 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 | 373 | pub mod validate { |
| 346 | 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 | 90 | impl<'repo> Id<'repo> { |
| 91 | 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 | 93 | pub fn ancestors(&self) -> revision::walk::Platform<'repo> { |
| 97 | 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 | 99 | mod ext; |
| 100 | 100 | /// |
| 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 | −} | |
| 101 | +pub mod prelude; | |
| 107 | 102 | |
| 108 | 103 | /// |
| 109 | 104 | pub mod path; |
| @@ -133,31 +128,10 @@ mod repository; · 23 unmodified lines | ||
| 133 | 128 | pub mod tag; |
| 134 | 129 | |
| 135 | 130 | /// |
| 136 | −pub mod progress { | |
| 137 | − #[cfg(feature = "progress-tree")] | |
| 138 | − pub use gix_features::progress::prodash::tree; | |
| 139 | − pub use gix_features::progress::*; | |
| 140 | −} | |
| 131 | +pub mod progress; | |
| 141 | 132 | |
| 142 | 133 | /// |
| 143 | −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 | −} | |
| 134 | +pub mod diff; | |
| 161 | 135 | |
| 162 | 136 | /// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead. |
| 163 | 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 { | |
| 242 | − /// | |
| 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 | −} | |
| 215 | +pub mod permission; | |
| 216 | + | |
| 251 | 217 | /// |
| 252 | −pub mod permissions { | |
| 253 | − pub use crate::repository::permissions::{Config, Environment}; | |
| 254 | −} | |
| 218 | +pub mod permissions; | |
| 255 | 219 | pub use repository::permissions::Permissions; |
| 256 | 220 | |
| 257 | 221 | /// |
| @@ -278,33 +242,10 @@ pub mod remote; · 20 unmodified lines | ||
| 278 | 242 | pub mod init; |
| 279 | 243 | |
| 280 | 244 | /// Not to be confused with 'status'. |
| 281 | −pub mod state { | |
| 282 | − /// Tell what operation is currently in progress. | |
| 283 | − #[derive(Debug, PartialEq, Eq)] | |
| 284 | − pub enum InProgress { | |
| 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 | −} | |
| 245 | +pub mod state; | |
| 246 | + | |
| 247 | +/// | |
| 248 | +pub mod shallow; | |
| 308 | 249 | |
| 309 | 250 | /// |
| 310 | 251 | pub mod discover; |
| @@ -267,6 +267,7 @@ impl ThreadSafeRepository { · 266 unmodified lines | ||
| 267 | 267 | // used when spawning new repositories off this one when following worktrees |
| 268 | 268 | linked_worktree_options: options, |
| 269 | 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 | 8 | self.config.clone(), |
| 9 | 9 | self.options.clone(), |
| 10 | 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 | 41 | repo.config.clone(), |
| 41 | 42 | repo.linked_worktree_options.clone(), |
| 42 | 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 | 56 | repo.config, |
| 55 | 57 | repo.linked_worktree_options, |
| 56 | 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 | 71 | config: r.config, |
| 69 | 72 | linked_worktree_options: r.options, |
| 70 | 73 | index: r.index, |
| 74 | + shallow_commits: r.shallow_commits, | |
| 71 | 75 | } |
| 72 | 76 | } |
| 73 | 77 | } |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | use std::cell::RefCell; |
| 2 | 2 | |
| 3 | 3 | impl crate::Repository { |
| 4 | + #[allow(clippy::too_many_arguments)] | |
| 4 | 5 | pub(crate) fn from_refs_and_objects( |
| 5 | 6 | refs: crate::RefStore, |
| 6 | 7 | objects: crate::OdbHandle, |
| @@ -9,6 +10,7 @@ impl crate::Repository { · 2 unmodified lines | ||
| 9 | 10 | config: crate::config::Cache, |
| 10 | 11 | linked_worktree_options: crate::open::Options, |
| 11 | 12 | index: crate::worktree::IndexStorage, |
| 13 | + shallow_commits: crate::shallow::CommitsStorage, | |
| 12 | 14 | ) -> Self { |
| 13 | 15 | let objects = setup_objects(objects, &config); |
| 14 | 16 | crate::Repository { |
| @@ -20,6 +22,7 @@ impl crate::Repository { · 5 unmodified lines | ||
| 20 | 22 | config, |
| 21 | 23 | options: linked_worktree_options, |
| 22 | 24 | index, |
| 25 | + shallow_commits, | |
| 23 | 26 | } |
| 24 | 27 | } |
| 25 | 28 | |
| @@ -30,6 +30,7 @@ pub(crate) mod permissions; · 29 unmodified lines | ||
| 30 | 30 | mod reference; |
| 31 | 31 | mod remote; |
| 32 | 32 | mod revision; |
| 33 | +mod shallow; | |
| 33 | 34 | mod snapshots; |
| 34 | 35 | mod state; |
| 35 | 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 | 178 | #[error(transparent)] |
| 179 | 179 | Traverse(#[from] gix_traverse::commit::ancestors::Error), |
| 180 | + #[error(transparent)] | |
| 181 | + Walk(#[from] crate::revision::walk::Error), | |
| 180 | 182 | #[error("Spec does not contain a single object id")] |
| 181 | 183 | SingleNotFound, |
| 182 | 184 | } |
| @@ -3,6 +3,16 @@ use gix_odb::FindExt; · 2 unmodified lines | ||
| 3 | 3 | |
| 4 | 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 | 16 | /// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed, |
| 7 | 17 | /// returned by [`Repository::rev_walk()`]. |
| 8 | 18 | pub struct Platform<'repo> { |
| @@ -46,7 +56,7 @@ impl<'repo> Platform<'repo> { · 37 unmodified lines | ||
| 46 | 56 | /// |
| 47 | 57 | /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo |
| 48 | 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 | 60 | let Platform { |
| 51 | 61 | repo, |
| 52 | 62 | tips, |
| @@ -56,16 +66,36 @@ impl<'repo> Platform<'repo> { · 3 unmodified lines | ||
| 56 | 66 | Ok(revision::Walk { |
| 57 | 67 | repo, |
| 58 | 68 | inner: Box::new( |
| 59 | − gix_traverse::commit::Ancestors::new( | |
| 69 | + gix_traverse::commit::Ancestors::filtered( | |
| 60 | 70 | tips, |
| 61 | 71 | gix_traverse::commit::ancestors::State::default(), |
| 62 | 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 | 96 | .sorting(sorting)? |
| 65 | 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 | 108 | pub(crate) repo: &'repo crate::Repository, |
| 79 | 109 | pub(crate) inner: |
| 80 | 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 | 113 | impl<'repo> Iterator for Walk<'repo> { |
| 106 | 114 | type Item = Result<Id<'repo>, gix_traverse::commit::ancestors::Error>; |
| 107 | 115 | |
| 108 | 116 | fn next(&mut self) -> Option<Self::Item> { |
| 109 | − match self.inner.next() { | |
| 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 | − } | |
| 117 | + self.inner.next().map(|res| res.map(|id| id.attach(self.repo))) | |
| 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 | 152 | /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories. |
| 153 | 153 | pub(crate) options: crate::open::Options, |
| 154 | 154 | pub(crate) index: crate::worktree::IndexStorage, |
| 155 | + pub(crate) shallow_commits: crate::shallow::CommitsStorage, | |
| 155 | 156 | } |
| 156 | 157 | |
| 157 | 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 | 176 | pub(crate) linked_worktree_options: crate::open::Options, |
| 176 | 177 | /// The index of this instances worktree. |
| 177 | 178 | pub(crate) index: crate::worktree::IndexStorage, |
| 179 | + pub(crate) shallow_commits: crate::shallow::CommitsStorage, | |
| 178 | 180 | } |
| 179 | 181 | |
| 180 | 182 | /// A remote which represents a way to interact with hosts for remote clones of the parent repository. |
| @@ -1,6 +1,7 @@ | ||
| 1 | 1 | /make_worktree_repo.tar.xz |
| 2 | 2 | /make_worktree_repo_with_configs.tar.xz |
| 3 | 3 | /make_remote_repos.tar.xz |
| 4 | +/make_complex_shallow_repo.tar.xz | |
| 4 | 5 | /make_fetch_repos.tar.xz |
| 5 | 6 | /make_core_worktree_repo.tar.xz |
| 6 | 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 | 5 | mod open; |
| 6 | 6 | mod reference; |
| 7 | 7 | mod remote; |
| 8 | +mod shallow; | |
| 8 | 9 | mod state; |
| 9 | 10 | mod worktree; |
| 10 | 11 | |
| @@ -194,7 +194,8 @@ mod with_overrides { · 193 unmodified lines | ||
| 194 | 194 | .set("GIT_SSL_VERSION", "tlsv1.3") |
| 195 | 195 | .set("GIT_SSH_VARIANT", "ssh-variant-env") |
| 196 | 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 | 199 | let mut opts = gix::open::Options::isolated() |
| 199 | 200 | .cli_overrides([ |
| 200 | 201 | "http.userAgent=agent-from-cli", |
| @@ -206,6 +207,7 @@ mod with_overrides { · 5 unmodified lines | ||
| 206 | 207 | "core.sshCommand=ssh-command-cli", |
| 207 | 208 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-cli", |
| 208 | 209 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-cli", |
| 210 | + "gitoxide.core.shallowFile=shallow-file-cli", | |
| 209 | 211 | ]) |
| 210 | 212 | .config_overrides([ |
| 211 | 213 | "http.userAgent=agent-from-api", |
| @@ -217,6 +219,7 @@ mod with_overrides { · 5 unmodified lines | ||
| 217 | 219 | "core.sshCommand=ssh-command-api", |
| 218 | 220 | "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-api", |
| 219 | 221 | "gitoxide.http.proxyAuthMethod=proxy-auth-method-api", |
| 222 | + "gitoxide.core.shallowFile=shallow-file-api", | |
| 220 | 223 | ]); |
| 221 | 224 | opts.permissions.env.git_prefix = Permission::Allow; |
| 222 | 225 | opts.permissions.env.http_transport = Permission::Allow; |
| @@ -229,6 +232,16 @@ mod with_overrides { · 6 unmodified lines | ||
| 229 | 232 | "config always refers to the local one for safety" |
| 230 | 233 | ); |
| 231 | 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 | 245 | assert_eq!( |
| 233 | 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 | +} | |