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
2525 * **Objects larger than 32 bits cannot be loaded on 32 bit systems**
2626 * in-memory representations objects cannot handle objects greater than the amount of addressable memory.
2727 * 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.
2832
2933 ### `gix-url`
3034
gix/src/config/cache/init.rs+9−0
@@ -347,6 +347,15 @@ fn apply_environment_overrides( · 346 unmodified lines
347347 },
348348 ],
349349 ),
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+ ),
350359 (
351360 "gitoxide",
352361 Some(Cow::Borrowed("author".into())),
gix/src/config/tree/sections/gitoxide.rs+28−4
@@ -1,3 +1,4 @@
1+use crate::config;
12 use crate::config::tree::{keys, Gitoxide, Key, Section};
23
34 impl Gitoxide {
@@ -5,6 +6,8 @@ impl Gitoxide { · 1 unmodified line
56 pub const ALLOW: Allow = Allow;
67 /// The `gitoxide.author` section.
78 pub const AUTHOR: Author = Author;
9+ /// The `gitoxide.core` section.
10+ pub const CORE: Core = Core;
811 /// The `gitoxide.commit` section.
912 pub const COMMIT: Commit = Commit;
1013 /// The `gitoxide.committer` section.
@@ -39,6 +42,7 @@ impl Section for Gitoxide { · 28 unmodified lines
3942 &[
4043 &Self::ALLOW,
4144 &Self::AUTHOR,
45+ &Self::CORE,
4246 &Self::COMMIT,
4347 &Self::COMMITTER,
4448 &Self::HTTP,
@@ -56,6 +60,29 @@ mod subsections { · 11 unmodified lines
5660 Tree,
5761 };
5862
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+
5986 /// The `Http` sub-section.
6087 #[derive(Copy, Clone, Default)]
6188 pub struct Http;
@@ -341,6 +368,7 @@ mod subsections { · 279 unmodified lines
341368 }
342369 }
343370 }
371+pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Ssh, User};
344372
345373 pub mod validate {
346374 use std::error::Error;
@@ -357,7 +385,3 @@ pub mod validate { · 10 unmodified lines
357385 }
358386 }
359387 }
360−
361−pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User};
362−
363−use crate::config;
gix/src/diff.rs+17−0
@@ -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+}
gix/src/id.rs+0−3
@@ -90,9 +90,6 @@ impl<'repo> Id<'repo> { · 89 unmodified lines
9090 impl<'repo> Id<'repo> {
9191 /// Obtain a platform for traversing ancestors of this commit.
9292 ///
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.
9693 pub fn ancestors(&self) -> revision::walk::Platform<'repo> {
9794 revision::walk::Platform::new(Some(self.inner), self.repo)
9895 }
gix/src/lib.rs+10−69
@@ -98,12 +98,7 @@ pub mod interrupt; · 97 unmodified lines
9898
9999 mod ext;
100100 ///
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;
107102
108103 ///
109104 pub mod path;
@@ -133,31 +128,10 @@ mod repository; · 23 unmodified lines
133128 pub mod tag;
134129
135130 ///
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;
141132
142133 ///
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;
161135
162136 /// See [ThreadSafeRepository::discover()], but returns a [`Repository`] instead.
163137 #[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
238212 }
239213
240214 ///
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+
251217 ///
252−pub mod permissions {
253− pub use crate::repository::permissions::{Config, Environment};
254−}
218+pub mod permissions;
255219 pub use repository::permissions::Permissions;
256220
257221 ///
@@ -278,33 +242,10 @@ pub mod remote; · 20 unmodified lines
278242 pub mod init;
279243
280244 /// 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;
308249
309250 ///
310251 pub mod discover;
gix/src/open/repository.rs+1−0
@@ -267,6 +267,7 @@ impl ThreadSafeRepository { · 266 unmodified lines
267267 // used when spawning new repositories off this one when following worktrees
268268 linked_worktree_options: options,
269269 index: gix_features::fs::MutableSnapshot::new().into(),
270+ shallow_commits: gix_features::fs::MutableSnapshot::new().into(),
270271 })
271272 }
272273 }
gix/src/permission.rs+8−0
@@ -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+}
gix/src/permissions.rs+1−0
@@ -0,0 +1 @@
1+pub use crate::repository::permissions::{Config, Environment};
gix/src/prelude.rs+4−0
@@ -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::*;
gix/src/progress.rs+3−0
@@ -0,0 +1,3 @@
1+#[cfg(feature = "progress-tree")]
2+pub use gix_features::progress::prodash::tree;
3+pub use gix_features::progress::*;
gix/src/repository/impls.rs+4−0
@@ -8,6 +8,7 @@ impl Clone for crate::Repository { · 7 unmodified lines
88 self.config.clone(),
99 self.options.clone(),
1010 self.index.clone(),
11+ self.shallow_commits.clone(),
1112 )
1213 }
1314 }
@@ -40,6 +41,7 @@ impl From<&crate::ThreadSafeRepository> for crate::Repository { · 26 unmodified lines
4041 repo.config.clone(),
4142 repo.linked_worktree_options.clone(),
4243 repo.index.clone(),
44+ repo.shallow_commits.clone(),
4345 )
4446 }
4547 }
@@ -54,6 +56,7 @@ impl From<crate::ThreadSafeRepository> for crate::Repository { · 8 unmodified lines
5456 repo.config,
5557 repo.linked_worktree_options,
5658 repo.index,
59+ repo.shallow_commits,
5760 )
5861 }
5962 }
@@ -68,6 +71,7 @@ impl From<crate::Repository> for crate::ThreadSafeRepository { · 8 unmodified lines
6871 config: r.config,
6972 linked_worktree_options: r.options,
7073 index: r.index,
74+ shallow_commits: r.shallow_commits,
7175 }
7276 }
7377 }
gix/src/repository/init.rs+3−0
@@ -1,6 +1,7 @@
11 use std::cell::RefCell;
22
33 impl crate::Repository {
4+ #[allow(clippy::too_many_arguments)]
45 pub(crate) fn from_refs_and_objects(
56 refs: crate::RefStore,
67 objects: crate::OdbHandle,
@@ -9,6 +10,7 @@ impl crate::Repository { · 2 unmodified lines
910 config: crate::config::Cache,
1011 linked_worktree_options: crate::open::Options,
1112 index: crate::worktree::IndexStorage,
13+ shallow_commits: crate::shallow::CommitsStorage,
1214 ) -> Self {
1315 let objects = setup_objects(objects, &config);
1416 crate::Repository {
@@ -20,6 +22,7 @@ impl crate::Repository { · 5 unmodified lines
2022 config,
2123 options: linked_worktree_options,
2224 index,
25+ shallow_commits,
2326 }
2427 }
2528
gix/src/repository/mod.rs+1−0
@@ -30,6 +30,7 @@ pub(crate) mod permissions; · 29 unmodified lines
3030 mod reference;
3131 mod remote;
3232 mod revision;
33+mod shallow;
3334 mod snapshots;
3435 mod state;
3536 mod thread_safe;
gix/src/repository/shallow.rs+59−0
@@ -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+}
gix/src/revision/spec/parse/types.rs+2−0
@@ -177,6 +177,8 @@ pub enum Error { · 176 unmodified lines
177177 },
178178 #[error(transparent)]
179179 Traverse(#[from] gix_traverse::commit::ancestors::Error),
180+ #[error(transparent)]
181+ Walk(#[from] crate::revision::walk::Error),
180182 #[error("Spec does not contain a single object id")]
181183 SingleNotFound,
182184 }
gix/src/revision/walk.rs+35−42
@@ -3,6 +3,16 @@ use gix_odb::FindExt; · 2 unmodified lines
33
44 use crate::{revision, Repository};
55
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+
616 /// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed,
717 /// returned by [`Repository::rev_walk()`].
818 pub struct Platform<'repo> {
@@ -46,7 +56,7 @@ impl<'repo> Platform<'repo> { · 37 unmodified lines
4656 ///
4757 /// It's highly recommended to set an [`object cache`][Repository::object_cache_size()] on the parent repo
4858 /// 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> {
5060 let Platform {
5161 repo,
5262 tips,
@@ -56,16 +66,36 @@ impl<'repo> Platform<'repo> { · 3 unmodified lines
5666 Ok(revision::Walk {
5767 repo,
5868 inner: Box::new(
59− gix_traverse::commit::Ancestors::new(
69+ gix_traverse::commit::Ancestors::filtered(
6070 tips,
6171 gix_traverse::commit::ancestors::State::default(),
6272 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+ },
6395 )
6496 .sorting(sorting)?
6597 .parents(parents),
6698 ),
67− is_shallow: None,
68− error_on_missing_commit: false,
6999 })
70100 }
71101 }
@@ -78,50 +108,13 @@ pub(crate) mod iter { · 6 unmodified lines
78108 pub(crate) repo: &'repo crate::Repository,
79109 pub(crate) inner:
80110 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− }
103111 }
104112
105113 impl<'repo> Iterator for Walk<'repo> {
106114 type Item = Result<Id<'repo>, gix_traverse::commit::ancestors::Error>;
107115
108116 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)))
125118 }
126119 }
127120 }
gix/src/shallow.rs+18−0
@@ -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+}
gix/src/state.rs+25−0
@@ -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+}
gix/src/types.rs+2−0
@@ -152,6 +152,7 @@ pub struct Repository { · 151 unmodified lines
152152 /// Particularly useful when following linked worktrees and instantiating new equally configured worktree repositories.
153153 pub(crate) options: crate::open::Options,
154154 pub(crate) index: crate::worktree::IndexStorage,
155+ pub(crate) shallow_commits: crate::shallow::CommitsStorage,
155156 }
156157
157158 /// 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
175176 pub(crate) linked_worktree_options: crate::open::Options,
176177 /// The index of this instances worktree.
177178 pub(crate) index: crate::worktree::IndexStorage,
179+ pub(crate) shallow_commits: crate::shallow::CommitsStorage,
178180 }
179181
180182 /// 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 @@
11 /make_worktree_repo.tar.xz
22 /make_worktree_repo_with_configs.tar.xz
33 /make_remote_repos.tar.xz
4+/make_complex_shallow_repo.tar.xz
45 /make_fetch_repos.tar.xz
56 /make_core_worktree_repo.tar.xz
67 /make_signatures_repo.tar.xz
gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz+3−0
@@ -0,0 +1,3 @@
1+version https://git-lfs.github.com/spec/v1
2+oid sha256:c56c269562ef67b1f8bd46640e6ad9d196cbc9c7c609300ffd6a8da3bc501852
3+size 12632
gix/tests/fixtures/make_complex_shallow_repo.sh+7−0
@@ -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
gix/tests/fixtures/make_shallow_repo.sh+28−0
@@ -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
gix/tests/repository/mod.rs+1−0
@@ -5,6 +5,7 @@ mod object; · 4 unmodified lines
55 mod open;
66 mod reference;
77 mod remote;
8+mod shallow;
89 mod state;
910 mod worktree;
1011
gix/tests/repository/open.rs+14−1
@@ -194,7 +194,8 @@ mod with_overrides { · 193 unmodified lines
194194 .set("GIT_SSL_VERSION", "tlsv1.3")
195195 .set("GIT_SSH_VARIANT", "ssh-variant-env")
196196 .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");
198199 let mut opts = gix::open::Options::isolated()
199200 .cli_overrides([
200201 "http.userAgent=agent-from-cli",
@@ -206,6 +207,7 @@ mod with_overrides { · 5 unmodified lines
206207 "core.sshCommand=ssh-command-cli",
207208 "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-cli",
208209 "gitoxide.http.proxyAuthMethod=proxy-auth-method-cli",
210+ "gitoxide.core.shallowFile=shallow-file-cli",
209211 ])
210212 .config_overrides([
211213 "http.userAgent=agent-from-api",
@@ -217,6 +219,7 @@ mod with_overrides { · 5 unmodified lines
217219 "core.sshCommand=ssh-command-api",
218220 "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-api",
219221 "gitoxide.http.proxyAuthMethod=proxy-auth-method-api",
222+ "gitoxide.core.shallowFile=shallow-file-api",
220223 ]);
221224 opts.permissions.env.git_prefix = Permission::Allow;
222225 opts.permissions.env.http_transport = Permission::Allow;
@@ -229,6 +232,16 @@ mod with_overrides { · 6 unmodified lines
229232 "config always refers to the local one for safety"
230233 );
231234 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+ );
232245 assert_eq!(
233246 config.strings_by_key("http.userAgent").expect("at least one value"),
234247 [
gix/tests/repository/shallow.rs+99−0
@@ -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+}