History

Merge branch 'shallow-support'

Sebastian Thiel committed 3 years ago

Commit 6d88fd22 parents 93e75fe 1046ea227 files · +387 −119Browse files

Unified · Split

27 files
  1. SHORTCOMINGS.md +4 −0
  2. gix/src/config/cache/init.rs +9 −0
  3. gix/src/config/tree/sections/gitoxide.rs +28 −4
  4. gix/src/diff.rs +17 −0
  5. gix/src/id.rs +0 −3
  6. gix/src/lib.rs +10 −69
  7. gix/src/open/repository.rs +1 −0
  8. gix/src/permission.rs +8 −0
  9. gix/src/permissions.rs +1 −0
  10. gix/src/prelude.rs +4 −0
  11. gix/src/progress.rs +3 −0
  12. gix/src/repository/impls.rs +4 −0
  13. gix/src/repository/init.rs +3 −0
  14. gix/src/repository/mod.rs +1 −0
  15. gix/src/repository/shallow.rs +59 −0
  16. gix/src/revision/spec/parse/types.rs +2 −0
  17. gix/src/revision/walk.rs +35 −42
  18. gix/src/shallow.rs +18 −0
  19. gix/src/state.rs +25 −0
  20. gix/src/types.rs +2 −0
  21. gix/tests/fixtures/generated-archives/.gitignore +1 −0
  22. gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz +3 −0
  23. gix/tests/fixtures/make_complex_shallow_repo.sh +7 −0
  24. gix/tests/fixtures/make_shallow_repo.sh +28 −0
  25. gix/tests/repository/mod.rs +1 −0
  26. gix/tests/repository/open.rs +14 −1
  27. gix/tests/repository/shallow.rs +99 −0
SHORTCOMINGS.md+4−0
@@ -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
gix/src/config/cache/init.rs+9−0
@@ -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())),
gix/src/config/tree/sections/gitoxide.rs+28−4
@@ -1,3 +1,4 @@
1use crate::config;
1use crate::config::tree::{keys, Gitoxide, Key, Section};2use crate::config::tree::{keys, Gitoxide, Key, Section};
2 3
3impl Gitoxide {4impl 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}
371pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Ssh, User};
344 372
345pub mod validate {373pub 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
361pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User};
362
363use crate::config;
gix/src/diff.rs+17−0
@@ -0,0 +1,17 @@
1pub use gix_diff::*;
2
3///
4pub 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}
gix/src/id.rs+0−3
@@ -90,9 +90,6 @@ impl<'repo> Id<'repo> { · 89 unmodified lines
90impl<'repo> Id<'repo> {90impl<'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 }
gix/src/lib.rs+10−69
@@ -98,12 +98,7 @@ pub mod interrupt; · 97 unmodified lines
98 98
99mod ext;99mod ext;
100///100///
101pub mod prelude {101pub 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///
109pub mod path;104pub mod path;
@@ -133,31 +128,10 @@ mod repository; · 23 unmodified lines
133pub mod tag;128pub mod tag;
134 129
135///130///
136pub mod progress {131pub 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///
143pub mod diff {134pub 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///
241pub mod permission {215pub 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///
252pub mod permissions {218pub mod permissions;
253 pub use crate::repository::permissions::{Config, Environment};
254}
255pub use repository::permissions::Permissions;219pub use repository::permissions::Permissions;
256 220
257///221///
@@ -278,33 +242,10 @@ pub mod remote; · 20 unmodified lines
278pub mod init;242pub mod init;
279 243
280/// Not to be confused with 'status'.244/// Not to be confused with 'status'.
281pub mod state {245pub mod state;
282 /// Tell what operation is currently in progress.246
283 #[derive(Debug, PartialEq, Eq)]247///
284 pub enum InProgress {248pub 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///
310pub mod discover;251pub mod discover;
gix/src/open/repository.rs+1−0
@@ -267,6 +267,7 @@ impl ThreadSafeRepository { · 266 unmodified lines
267 // used when spawning new repositories off this one when following worktrees267 // 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}
gix/src/permission.rs+8−0
@@ -0,0 +1,8 @@
1///
2pub mod env_var {
3 ///
4 pub mod resource {
5 ///
6 pub type Error = gix_sec::permission::Error<std::path::PathBuf>;
7 }
8}
gix/src/permissions.rs+1−0
@@ -0,0 +1 @@
1pub use crate::repository::permissions::{Config, Environment};
gix/src/prelude.rs+4−0
@@ -0,0 +1,4 @@
1pub use gix_features::parallel::reduce::Finalize;
2pub use gix_odb::{Find, FindExt, Header, HeaderExt, Write};
3
4pub use crate::ext::*;
gix/src/progress.rs+3−0
@@ -0,0 +1,3 @@
1#[cfg(feature = "progress-tree")]
2pub use gix_features::progress::prodash::tree;
3pub use gix_features::progress::*;
gix/src/repository/impls.rs+4−0
@@ -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}
gix/src/repository/init.rs+3−0
@@ -1,6 +1,7 @@
1use std::cell::RefCell;1use std::cell::RefCell;
2 2
3impl crate::Repository {3impl 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
gix/src/repository/mod.rs+1−0
@@ -30,6 +30,7 @@ pub(crate) mod permissions; · 29 unmodified lines
30mod reference;30mod reference;
31mod remote;31mod remote;
32mod revision;32mod revision;
33mod shallow;
33mod snapshots;34mod snapshots;
34mod state;35mod state;
35mod thread_safe;36mod thread_safe;
gix/src/repository/shallow.rs+59−0
@@ -0,0 +1,59 @@
1use crate::bstr::ByteSlice;
2use crate::config::tree::{gitoxide, Key};
3use crate::Repository;
4use std::borrow::Cow;
5use std::path::PathBuf;
6
7impl 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}
gix/src/revision/spec/parse/types.rs+2−0
@@ -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}
gix/src/revision/walk.rs+35−42
@@ -3,6 +3,16 @@ use gix_odb::FindExt; · 2 unmodified lines
3 3
4use crate::{revision, Repository};4use crate::{revision, Repository};
5 5
6/// The error returned by [`Platform::all()`].
7#[derive(Debug, thiserror::Error)]
8#[allow(missing_docs)]
9pub 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()`].
8pub struct Platform<'repo> {18pub 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 repo57 /// 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}
gix/src/shallow.rs+18−0
@@ -0,0 +1,18 @@
1pub(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.
5pub type Commits = gix_features::fs::SharedSnapshot<Vec<gix_hash::ObjectId>>;
6
7///
8pub 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}
gix/src/state.rs+25−0
@@ -0,0 +1,25 @@
1/// Tell what operation is currently in progress.
2#[derive(Debug, PartialEq, Eq)]
3pub 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}
gix/src/types.rs+2−0
@@ -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.
gix/tests/fixtures/generated-archives/.gitignore+1−0
@@ -1,6 +1,7 @@
1/make_worktree_repo.tar.xz1/make_worktree_repo.tar.xz
2/make_worktree_repo_with_configs.tar.xz2/make_worktree_repo_with_configs.tar.xz
3/make_remote_repos.tar.xz3/make_remote_repos.tar.xz
4/make_complex_shallow_repo.tar.xz
4/make_fetch_repos.tar.xz5/make_fetch_repos.tar.xz
5/make_core_worktree_repo.tar.xz6/make_core_worktree_repo.tar.xz
6/make_signatures_repo.tar.xz7/make_signatures_repo.tar.xz
gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz+3−0
@@ -0,0 +1,3 @@
1version https://git-lfs.github.com/spec/v1
2oid sha256:c56c269562ef67b1f8bd46640e6ad9d196cbc9c7c609300ffd6a8da3bc501852
3size 12632
gix/tests/fixtures/make_complex_shallow_repo.sh+7−0
@@ -0,0 +1,7 @@
1#!/bin/bash
2set -eu -o pipefail
3
4remote="${1:?First argument is the complex repo to clone from}"
5
6git clone --depth 3 file://"$remote" shallow
7git clone --depth 3 --bare file://"$remote" shallow.git
gix/tests/fixtures/make_shallow_repo.sh+28−0
@@ -0,0 +1,28 @@
1#!/bin/bash
2set -eu -o pipefail
3
4mkdir 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
17mkdir 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
27git clone --depth 1 --bare file://$PWD/base shallow.git
28git clone --depth 1 file://$PWD/base shallow
gix/tests/repository/mod.rs+1−0
@@ -5,6 +5,7 @@ mod object; · 4 unmodified lines
5mod open;5mod open;
6mod reference;6mod reference;
7mod remote;7mod remote;
8mod shallow;
8mod state;9mod state;
9mod worktree;10mod worktree;
10 11
gix/tests/repository/open.rs+14−1
@@ -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 [
gix/tests/repository/shallow.rs+99−0
@@ -0,0 +1,99 @@
1use crate::util::{hex_to_id, named_subrepo_opts};
2
3#[test]
4fn 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]
25fn 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
37mod 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}