diff --git a/git-discover/src/repository.rs b/git-discover/src/repository.rs index 25c0730d304..0d6cf060bb1 100644 --- a/git-discover/src/repository.rs +++ b/git-discover/src/repository.rs @@ -116,7 +116,7 @@ pub enum Kind { /// /// Note that this is merely a guess at this point as we didn't read the configuration yet. Bare, - /// A `git` repository along with a checked out files in a work tree. + /// A `git` repository along with checked out files in a work tree. WorkTree { /// If set, this is the git dir associated with this _linked_ worktree. /// If `None`, the git_dir is the `.git` directory inside the _main_ worktree we represent. diff --git a/git-repository/src/config/cache/incubate.rs b/git-repository/src/config/cache/incubate.rs index 233b4c0f3e6..7861b643f2d 100644 --- a/git-repository/src/config/cache/incubate.rs +++ b/git-repository/src/config/cache/incubate.rs @@ -15,27 +15,20 @@ pub(crate) struct StageOne { /// Initialization impl StageOne { pub fn new( + common_dir: &std::path::Path, git_dir: &std::path::Path, git_dir_trust: git_sec::Trust, lossy: Option, lenient: bool, ) -> Result { let mut buf = Vec::with_capacity(512); - let config = { - let config_path = git_dir.join("config"); - std::io::copy(&mut std::fs::File::open(&config_path)?, &mut buf)?; - - git_config::File::from_bytes_owned( - &mut buf, - git_config::file::Metadata::from(git_config::Source::Local) - .at(config_path) - .with(git_dir_trust), - git_config::file::init::Options { - includes: git_config::file::includes::Options::no_follow(), - ..util::base_options(lossy) - }, - )? - }; + let mut config = load_config( + common_dir.join("config"), + &mut buf, + git_config::Source::Local, + git_dir_trust, + lossy, + )?; let is_bare = util::config_bool(&config, "core.bare", false, lenient)?; let repo_format_version = config @@ -57,6 +50,18 @@ impl StageOne { .transpose()? .unwrap_or(git_hash::Kind::Sha1); + let extension_worktree = util::config_bool(&config, "extensions.worktreeConfig", false, lenient)?; + if extension_worktree { + let worktree_config = load_config( + git_dir.join("config.worktree"), + &mut buf, + git_config::Source::Worktree, + git_dir_trust, + lossy, + )?; + config.append(worktree_config); + }; + let reflog = util::query_refupdates(&config, lenient)?; Ok(StageOne { git_dir_config: config, @@ -68,3 +73,27 @@ impl StageOne { }) } } + +fn load_config( + config_path: std::path::PathBuf, + buf: &mut Vec, + source: git_config::Source, + git_dir_trust: git_sec::Trust, + lossy: Option, +) -> Result, Error> { + buf.clear(); + std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?; + + let config = git_config::File::from_bytes_owned( + buf, + git_config::file::Metadata::from(source) + .at(config_path) + .with(git_dir_trust), + git_config::file::init::Options { + includes: git_config::file::includes::Options::no_follow(), + ..util::base_options(lossy) + }, + )?; + + Ok(config) +} diff --git a/git-repository/src/open/repository.rs b/git-repository/src/open/repository.rs index b3a14f92571..5953a54f440 100644 --- a/git-repository/src/open/repository.rs +++ b/git-repository/src/open/repository.rs @@ -133,7 +133,13 @@ impl ThreadSafeRepository { .map(|cd| git_dir.join(cd)); let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir); - let repo_config = config::cache::StageOne::new(common_dir_ref, git_dir_trust, lossy_config, lenient_config)?; + let repo_config = config::cache::StageOne::new( + common_dir_ref, + git_dir.as_ref(), + git_dir_trust, + lossy_config, + lenient_config, + )?; let mut refs = { let reflog = repo_config.reflog.unwrap_or(git_ref::store::WriteReflog::Disable); let object_hash = repo_config.object_hash; diff --git a/git-repository/src/repository/permissions.rs b/git-repository/src/repository/permissions.rs index aab98c6799b..b1d51364c46 100644 --- a/git-repository/src/repository/permissions.rs +++ b/git-repository/src/repository/permissions.rs @@ -9,7 +9,9 @@ pub struct Permissions { pub config: Config, } -/// Configure security relevant options when loading a git configuration. +/// Configure from which sources git configuration may be loaded. +/// +/// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness. #[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)] pub struct Config { /// The git binary may come with configuration as part of its configuration, and if this is true (default false) @@ -29,9 +31,6 @@ pub struct Config { /// Whether to use the user configuration. /// This is usually `~/.gitconfig` on unix. pub user: bool, - // TODO: figure out how this really applies and provide more information here. - // Whether to use worktree configuration from `config.worktree`. - // pub worktree: bool, /// Whether to use the configuration from environment variables. pub env: bool, /// Whether to follow include files are encountered in loaded configuration, diff --git a/git-repository/tests/fixtures/generated-archives/.gitignore b/git-repository/tests/fixtures/generated-archives/.gitignore index 9f4f7db5774..8895a24ab87 100644 --- a/git-repository/tests/fixtures/generated-archives/.gitignore +++ b/git-repository/tests/fixtures/generated-archives/.gitignore @@ -1,4 +1,5 @@ /make_worktree_repo.tar.xz +/make_worktree_repo_with_configs.tar.xz /make_remote_repos.tar.xz /make_fetch_repos.tar.xz /make_core_worktree_repo.tar.xz diff --git a/git-repository/tests/fixtures/make_worktree_repo_with_configs.sh b/git-repository/tests/fixtures/make_worktree_repo_with_configs.sh new file mode 100644 index 00000000000..f1c046d7987 --- /dev/null +++ b/git-repository/tests/fixtures/make_worktree_repo_with_configs.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -eu -o pipefail + +git init repo + +(cd repo + touch a b c + git add . + git commit -m initial + + git worktree add ../wt-1 + git worktree add ../wt-2 + + git config extensions.worktreeConfig true + git config --worktree worktree.setting "set in the main worktree" + + git config shared.setting "set in the shared config" + git config override.setting "set in the shared config" +) + +(cd wt-1 + git config --worktree worktree.setting "set in wt-1" +) + +(cd wt-2 + git config --worktree worktree.setting "set in wt-2" + git config --worktree override.setting "override in wt-2" +) diff --git a/git-repository/tests/repository/open.rs b/git-repository/tests/repository/open.rs index a0fdc013027..3768f74c5e3 100644 --- a/git-repository/tests/repository/open.rs +++ b/git-repository/tests/repository/open.rs @@ -225,3 +225,104 @@ mod with_overrides { Cow::Borrowed(s.into()) } } + +mod worktree { + use git_repository::open; + + #[test] + fn with_worktree_configs() -> git_testtools::Result { + let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); + let fixture_dir = git_testtools::scripted_fixture_repo_read_only("make_worktree_repo_with_configs.sh")?; + let worktree_base = manifest_dir.join(&fixture_dir).join("repo/.git/worktrees"); + + { + let base = open(fixture_dir.join("repo"))?; + let base_config = base.config_snapshot(); + + assert_eq!( + base.work_dir(), + Some(fixture_dir.join("repo").as_path()), + "the main worktree" + ); + assert_eq!(base.git_dir(), fixture_dir.join("repo/.git"), "git dir and…"); + assert_eq!( + base.common_dir(), + fixture_dir.join("repo/.git"), + "…common dir are the same" + ); + + assert_eq!( + base_config.string("worktree.setting").expect("exists").as_ref(), + "set in the main worktree" + ); + assert_eq!( + base_config.string("shared.setting").expect("exists").as_ref(), + "set in the shared config" + ); + assert_eq!( + base_config.string("override.setting").expect("exists").as_ref(), + "set in the shared config" + ); + } + + { + let wt1 = open(fixture_dir.join("wt-1"))?; + let wt1_config = wt1.config_snapshot(); + assert_eq!( + wt1.work_dir(), + Some(fixture_dir.join("wt-1").as_path()), + "a linked worktree in its own location" + ); + assert_eq!( + wt1.git_dir(), + worktree_base.join("wt-1"), + "whose git-dir is within the common dir" + ); + assert_eq!( + wt1.common_dir(), + worktree_base.join("wt-1/../.."), + "the common dir is the `git-dir` of the repository with the main worktree" + ); + + assert_eq!( + wt1_config.string("worktree.setting").expect("exists").as_ref(), + "set in wt-1" + ); + assert_eq!( + wt1_config.string("shared.setting").expect("exists").as_ref(), + "set in the shared config" + ); + assert_eq!( + wt1_config.string("override.setting").expect("exists").as_ref(), + "set in the shared config" + ); + } + + { + let wt2 = open(fixture_dir.join("wt-2"))?; + let wt2_config = wt2.config_snapshot(); + assert_eq!( + wt2.work_dir(), + Some(fixture_dir.join("wt-2").as_path()), + "another linked worktree as sibling to wt-1" + ); + assert_eq!(wt2.git_dir(), worktree_base.join("wt-2")); + assert_eq!(wt2.common_dir(), worktree_base.join("wt-2/../..")); + + assert_eq!( + wt2_config.string("worktree.setting").expect("exists").as_ref(), + "set in wt-2" + ); + assert_eq!( + wt2_config.string("shared.setting").expect("exists").as_ref(), + "set in the shared config" + ); + assert_eq!( + wt2_config.string("override.setting").expect("exists").as_ref(), + "override in wt-2" + ); + } + + Ok(()) + } +}