From 2b677cff85a8984e05270f16aa3294af4510bd3f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 5 Mar 2023 10:11:40 +0100 Subject: [PATCH] feat: `Repository::is_shallow()` to test if a repository is shallow. --- gix/src/config/cache/init.rs | 9 ++++++ gix/src/config/tree/sections/gitoxide.rs | 32 ++++++++++++++++--- gix/src/repository/location.rs | 16 ++++++++++ gix/src/repository/state.rs | 7 ++++ .../make_shallow_repo.tar.xz | 3 ++ gix/tests/fixtures/make_shallow_repo.sh | 28 ++++++++++++++++ gix/tests/repository/open.rs | 15 ++++++++- gix/tests/repository/state.rs | 22 +++++++++++++ 8 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz create mode 100644 gix/tests/fixtures/make_shallow_repo.sh diff --git a/gix/src/config/cache/init.rs b/gix/src/config/cache/init.rs index dc76f78bbac..2aa4e4192cb 100644 --- a/gix/src/config/cache/init.rs +++ b/gix/src/config/cache/init.rs @@ -347,6 +347,15 @@ fn apply_environment_overrides( }, ], ), + ( + "gitoxide", + Some(Cow::Borrowed("core".into())), + git_prefix, + &[{ + let key = &gitoxide::Core::SHALLOW_FILE; + (env(key), key.name) + }], + ), ( "gitoxide", Some(Cow::Borrowed("author".into())), diff --git a/gix/src/config/tree/sections/gitoxide.rs b/gix/src/config/tree/sections/gitoxide.rs index 8c3defd0b68..1622139dedc 100644 --- a/gix/src/config/tree/sections/gitoxide.rs +++ b/gix/src/config/tree/sections/gitoxide.rs @@ -1,3 +1,4 @@ +use crate::config; use crate::config::tree::{keys, Gitoxide, Key, Section}; impl Gitoxide { @@ -5,6 +6,8 @@ impl Gitoxide { pub const ALLOW: Allow = Allow; /// The `gitoxide.author` section. pub const AUTHOR: Author = Author; + /// The `gitoxide.core` section. + pub const CORE: Core = Core; /// The `gitoxide.commit` section. pub const COMMIT: Commit = Commit; /// The `gitoxide.committer` section. @@ -39,6 +42,7 @@ impl Section for Gitoxide { &[ &Self::ALLOW, &Self::AUTHOR, + &Self::CORE, &Self::COMMIT, &Self::COMMITTER, &Self::HTTP, @@ -56,6 +60,29 @@ mod subsections { Tree, }; + /// The `Core` sub-section. + #[derive(Copy, Clone, Default)] + pub struct Core; + + impl Core { + /// The `gitoxide.core.shallowFile` key. + pub const SHALLOW_FILE: keys::Path = keys::Path::new_path("shallowFile", &Gitoxide::CORE) + .with_environment_override("GIT_SHALLOW_FILE") + .with_deviation( + "relative file paths will always be made relative to the git-common-dir, whereas `git` keeps them as is.", + ); + } + + impl Section for Core { + fn name(&self) -> &str { + "core" + } + + fn keys(&self) -> &[&dyn Key] { + &[&Self::SHALLOW_FILE] + } + } + /// The `Http` sub-section. #[derive(Copy, Clone, Default)] pub struct Http; @@ -341,6 +368,7 @@ mod subsections { } } } +pub use subsections::{Allow, Author, Commit, Committer, Core, Http, Https, Objects, Ssh, User}; pub mod validate { use std::error::Error; @@ -357,7 +385,3 @@ pub mod validate { } } } - -pub use subsections::{Allow, Author, Commit, Committer, Http, Https, Objects, Ssh, User}; - -use crate::config; diff --git a/gix/src/repository/location.rs b/gix/src/repository/location.rs index 0bb8ea2535d..f64641f80d8 100644 --- a/gix/src/repository/location.rs +++ b/gix/src/repository/location.rs @@ -1,5 +1,7 @@ +use std::borrow::Cow; use std::path::PathBuf; +use crate::config::tree::{gitoxide, Key}; use gix_path::realpath::MAX_SYMLINKS; impl crate::Repository { @@ -41,6 +43,20 @@ impl crate::Repository { crate::path::install_dir() } + /// Return the path to the `shallow` file which contains hashes, one per line, that describe commits that don't have their + /// parents within this repository. + pub fn shallow_file(&self) -> PathBuf { + let shallow_name = self + .config + .resolved + .string_filter_by_key( + gitoxide::Core::SHALLOW_FILE.logical_name().as_str(), + &mut self.filter_config_section(), + ) + .unwrap_or(Cow::Borrowed("shallow".into())); + self.common_dir().join(gix_path::from_bstr(shallow_name)) + } + /// Returns the relative path which is the components between the working tree and the current working dir (CWD). /// Note that there may be `None` if there is no work tree, even though the `PathBuf` will be empty /// if the CWD is at the root of the work tree. diff --git a/gix/src/repository/state.rs b/gix/src/repository/state.rs index 4034fe349db..9b8342886c6 100644 --- a/gix/src/repository/state.rs +++ b/gix/src/repository/state.rs @@ -41,4 +41,11 @@ impl crate::Repository { None } } + + /// Return `true` if the repository is a shallow clone, i.e. contains history only up to a certain depth. + pub fn is_shallow(&self) -> bool { + self.shallow_file() + .symlink_metadata() + .map_or(false, |m| m.is_file() && m.len() > 0) + } } diff --git a/gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz b/gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz new file mode 100644 index 00000000000..d2da451c8b1 --- /dev/null +++ b/gix/tests/fixtures/generated-archives/make_shallow_repo.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c56c269562ef67b1f8bd46640e6ad9d196cbc9c7c609300ffd6a8da3bc501852 +size 12632 diff --git a/gix/tests/fixtures/make_shallow_repo.sh b/gix/tests/fixtures/make_shallow_repo.sh new file mode 100644 index 00000000000..236d5e00d1c --- /dev/null +++ b/gix/tests/fixtures/make_shallow_repo.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -eu -o pipefail + +mkdir base +(cd base + git init -q + + git checkout -b main + touch a && git add a + git commit -q -m c1 + echo 1 >> a + git commit -q -am c2 + echo 1 >> a + git commit -q -am c3 +) + +mkdir empty +(cd empty + git init -q + + git checkout -b main + touch a && git add a + git commit -q -m c1 + touch .git/shallow +) + +git clone --depth 1 --bare file://$PWD/base shallow.git +git clone --depth 1 file://$PWD/base shallow diff --git a/gix/tests/repository/open.rs b/gix/tests/repository/open.rs index b0da3a2d022..f58ab5c9e5e 100644 --- a/gix/tests/repository/open.rs +++ b/gix/tests/repository/open.rs @@ -194,7 +194,8 @@ mod with_overrides { .set("GIT_SSL_VERSION", "tlsv1.3") .set("GIT_SSH_VARIANT", "ssh-variant-env") .set("GIT_SSH_COMMAND", "ssh-command-env") - .set("GIT_SSH", "ssh-command-fallback-env"); + .set("GIT_SSH", "ssh-command-fallback-env") + .set("GIT_SHALLOW_FILE", "shallow-file-env"); let mut opts = gix::open::Options::isolated() .cli_overrides([ "http.userAgent=agent-from-cli", @@ -206,6 +207,7 @@ mod with_overrides { "core.sshCommand=ssh-command-cli", "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-cli", "gitoxide.http.proxyAuthMethod=proxy-auth-method-cli", + "gitoxide.core.shallowFile=shallow-file-cli", ]) .config_overrides([ "http.userAgent=agent-from-api", @@ -217,6 +219,7 @@ mod with_overrides { "core.sshCommand=ssh-command-api", "gitoxide.ssh.commandWithoutShellFallback=ssh-command-fallback-api", "gitoxide.http.proxyAuthMethod=proxy-auth-method-api", + "gitoxide.core.shallowFile=shallow-file-api", ]); opts.permissions.env.git_prefix = Permission::Allow; opts.permissions.env.http_transport = Permission::Allow; @@ -229,6 +232,16 @@ mod with_overrides { "config always refers to the local one for safety" ); let config = repo.config_snapshot(); + assert_eq!( + config + .strings_by_key("gitoxide.core.shallowFile") + .expect("at least one value"), + [ + cow_bstr("shallow-file-cli"), + cow_bstr("shallow-file-api"), + cow_bstr("shallow-file-env") + ] + ); assert_eq!( config.strings_by_key("http.userAgent").expect("at least one value"), [ diff --git a/gix/tests/repository/state.rs b/gix/tests/repository/state.rs index 400a361d2ab..dcbeab12647 100644 --- a/gix/tests/repository/state.rs +++ b/gix/tests/repository/state.rs @@ -77,3 +77,25 @@ fn revert_sequence() -> Result { Ok(()) } + +mod shallow { + use crate::util::named_subrepo_opts; + + #[test] + fn without() -> crate::Result { + for name in ["base", "empty"] { + let repo = named_subrepo_opts("make_shallow_repo.sh", name, gix::open::Options::isolated())?; + assert!(!repo.is_shallow()); + } + Ok(()) + } + + #[test] + fn with() -> crate::Result { + for name in ["shallow.git", "shallow"] { + let repo = named_subrepo_opts("make_shallow_repo.sh", name, gix::open::Options::isolated())?; + assert!(repo.is_shallow()); + } + Ok(()) + } +}