Skip to content

git-repository loads worktree configs #635

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Dec 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion git-discover/src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
59 changes: 44 additions & 15 deletions git-repository/src/config/cache/incubate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool>,
lenient: bool,
) -> Result<Self, Error> {
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
Expand All @@ -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,
Expand All @@ -68,3 +73,27 @@ impl StageOne {
})
}
}

fn load_config(
config_path: std::path::PathBuf,
buf: &mut Vec<u8>,
source: git_config::Source,
git_dir_trust: git_sec::Trust,
lossy: Option<bool>,
) -> Result<git_config::File<'static>, 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)
}
8 changes: 7 additions & 1 deletion git-repository/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions git-repository/src/repository/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions git-repository/tests/fixtures/make_worktree_repo_with_configs.sh
Original file line number Diff line number Diff line change
@@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting values so they describe their purpose and show up in the tests like this works really well! Thank you.

)
101 changes: 101 additions & 0 deletions git-repository/tests/repository/open.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}