Skip to content

Commit d1b7ec6

Browse files
committed
feat: read worktree specific configuration to override the one from the shared repository.
This is intensively used when space checkouts are created, along with Cone mode. Thus it's the basis for properly interpreting sparse checkout options which are set on a per-worktree basis.
2 parents eca6705 + 2d83222 commit d1b7ec6

File tree

7 files changed

+186
-21
lines changed

7 files changed

+186
-21
lines changed

git-discover/src/repository.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub enum Kind {
116116
///
117117
/// Note that this is merely a guess at this point as we didn't read the configuration yet.
118118
Bare,
119-
/// A `git` repository along with a checked out files in a work tree.
119+
/// A `git` repository along with checked out files in a work tree.
120120
WorkTree {
121121
/// If set, this is the git dir associated with this _linked_ worktree.
122122
/// If `None`, the git_dir is the `.git` directory inside the _main_ worktree we represent.

git-repository/src/config/cache/incubate.rs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,20 @@ pub(crate) struct StageOne {
1515
/// Initialization
1616
impl StageOne {
1717
pub fn new(
18+
common_dir: &std::path::Path,
1819
git_dir: &std::path::Path,
1920
git_dir_trust: git_sec::Trust,
2021
lossy: Option<bool>,
2122
lenient: bool,
2223
) -> Result<Self, Error> {
2324
let mut buf = Vec::with_capacity(512);
24-
let config = {
25-
let config_path = git_dir.join("config");
26-
std::io::copy(&mut std::fs::File::open(&config_path)?, &mut buf)?;
27-
28-
git_config::File::from_bytes_owned(
29-
&mut buf,
30-
git_config::file::Metadata::from(git_config::Source::Local)
31-
.at(config_path)
32-
.with(git_dir_trust),
33-
git_config::file::init::Options {
34-
includes: git_config::file::includes::Options::no_follow(),
35-
..util::base_options(lossy)
36-
},
37-
)?
38-
};
25+
let mut config = load_config(
26+
common_dir.join("config"),
27+
&mut buf,
28+
git_config::Source::Local,
29+
git_dir_trust,
30+
lossy,
31+
)?;
3932

4033
let is_bare = util::config_bool(&config, "core.bare", false, lenient)?;
4134
let repo_format_version = config
@@ -57,6 +50,18 @@ impl StageOne {
5750
.transpose()?
5851
.unwrap_or(git_hash::Kind::Sha1);
5952

53+
let extension_worktree = util::config_bool(&config, "extensions.worktreeConfig", false, lenient)?;
54+
if extension_worktree {
55+
let worktree_config = load_config(
56+
git_dir.join("config.worktree"),
57+
&mut buf,
58+
git_config::Source::Worktree,
59+
git_dir_trust,
60+
lossy,
61+
)?;
62+
config.append(worktree_config);
63+
};
64+
6065
let reflog = util::query_refupdates(&config, lenient)?;
6166
Ok(StageOne {
6267
git_dir_config: config,
@@ -68,3 +73,27 @@ impl StageOne {
6873
})
6974
}
7075
}
76+
77+
fn load_config(
78+
config_path: std::path::PathBuf,
79+
buf: &mut Vec<u8>,
80+
source: git_config::Source,
81+
git_dir_trust: git_sec::Trust,
82+
lossy: Option<bool>,
83+
) -> Result<git_config::File<'static>, Error> {
84+
buf.clear();
85+
std::io::copy(&mut std::fs::File::open(&config_path)?, buf)?;
86+
87+
let config = git_config::File::from_bytes_owned(
88+
buf,
89+
git_config::file::Metadata::from(source)
90+
.at(config_path)
91+
.with(git_dir_trust),
92+
git_config::file::init::Options {
93+
includes: git_config::file::includes::Options::no_follow(),
94+
..util::base_options(lossy)
95+
},
96+
)?;
97+
98+
Ok(config)
99+
}

git-repository/src/open/repository.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,13 @@ impl ThreadSafeRepository {
133133
.map(|cd| git_dir.join(cd));
134134
let common_dir_ref = common_dir.as_deref().unwrap_or(&git_dir);
135135

136-
let repo_config = config::cache::StageOne::new(common_dir_ref, git_dir_trust, lossy_config, lenient_config)?;
136+
let repo_config = config::cache::StageOne::new(
137+
common_dir_ref,
138+
git_dir.as_ref(),
139+
git_dir_trust,
140+
lossy_config,
141+
lenient_config,
142+
)?;
137143
let mut refs = {
138144
let reflog = repo_config.reflog.unwrap_or(git_ref::store::WriteReflog::Disable);
139145
let object_hash = repo_config.object_hash;

git-repository/src/repository/permissions.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ pub struct Permissions {
99
pub config: Config,
1010
}
1111

12-
/// Configure security relevant options when loading a git configuration.
12+
/// Configure from which sources git configuration may be loaded.
13+
///
14+
/// Note that configuration from inside of the repository is always loaded as it's definitely required for correctness.
1315
#[derive(Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Debug, Hash)]
1416
pub struct Config {
1517
/// 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 {
2931
/// Whether to use the user configuration.
3032
/// This is usually `~/.gitconfig` on unix.
3133
pub user: bool,
32-
// TODO: figure out how this really applies and provide more information here.
33-
// Whether to use worktree configuration from `config.worktree`.
34-
// pub worktree: bool,
3534
/// Whether to use the configuration from environment variables.
3635
pub env: bool,
3736
/// Whether to follow include files are encountered in loaded configuration,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/make_worktree_repo.tar.xz
2+
/make_worktree_repo_with_configs.tar.xz
23
/make_remote_repos.tar.xz
34
/make_fetch_repos.tar.xz
45
/make_core_worktree_repo.tar.xz
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
set -eu -o pipefail
4+
5+
git init repo
6+
7+
(cd repo
8+
touch a b c
9+
git add .
10+
git commit -m initial
11+
12+
git worktree add ../wt-1
13+
git worktree add ../wt-2
14+
15+
git config extensions.worktreeConfig true
16+
git config --worktree worktree.setting "set in the main worktree"
17+
18+
git config shared.setting "set in the shared config"
19+
git config override.setting "set in the shared config"
20+
)
21+
22+
(cd wt-1
23+
git config --worktree worktree.setting "set in wt-1"
24+
)
25+
26+
(cd wt-2
27+
git config --worktree worktree.setting "set in wt-2"
28+
git config --worktree override.setting "override in wt-2"
29+
)

git-repository/tests/repository/open.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,104 @@ mod with_overrides {
225225
Cow::Borrowed(s.into())
226226
}
227227
}
228+
229+
mod worktree {
230+
use git_repository::open;
231+
232+
#[test]
233+
fn with_worktree_configs() -> git_testtools::Result {
234+
let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
235+
let fixture_dir = git_testtools::scripted_fixture_repo_read_only("make_worktree_repo_with_configs.sh")?;
236+
let worktree_base = manifest_dir.join(&fixture_dir).join("repo/.git/worktrees");
237+
238+
{
239+
let base = open(fixture_dir.join("repo"))?;
240+
let base_config = base.config_snapshot();
241+
242+
assert_eq!(
243+
base.work_dir(),
244+
Some(fixture_dir.join("repo").as_path()),
245+
"the main worktree"
246+
);
247+
assert_eq!(base.git_dir(), fixture_dir.join("repo/.git"), "git dir and…");
248+
assert_eq!(
249+
base.common_dir(),
250+
fixture_dir.join("repo/.git"),
251+
"…common dir are the same"
252+
);
253+
254+
assert_eq!(
255+
base_config.string("worktree.setting").expect("exists").as_ref(),
256+
"set in the main worktree"
257+
);
258+
assert_eq!(
259+
base_config.string("shared.setting").expect("exists").as_ref(),
260+
"set in the shared config"
261+
);
262+
assert_eq!(
263+
base_config.string("override.setting").expect("exists").as_ref(),
264+
"set in the shared config"
265+
);
266+
}
267+
268+
{
269+
let wt1 = open(fixture_dir.join("wt-1"))?;
270+
let wt1_config = wt1.config_snapshot();
271+
assert_eq!(
272+
wt1.work_dir(),
273+
Some(fixture_dir.join("wt-1").as_path()),
274+
"a linked worktree in its own location"
275+
);
276+
assert_eq!(
277+
wt1.git_dir(),
278+
worktree_base.join("wt-1"),
279+
"whose git-dir is within the common dir"
280+
);
281+
assert_eq!(
282+
wt1.common_dir(),
283+
worktree_base.join("wt-1/../.."),
284+
"the common dir is the `git-dir` of the repository with the main worktree"
285+
);
286+
287+
assert_eq!(
288+
wt1_config.string("worktree.setting").expect("exists").as_ref(),
289+
"set in wt-1"
290+
);
291+
assert_eq!(
292+
wt1_config.string("shared.setting").expect("exists").as_ref(),
293+
"set in the shared config"
294+
);
295+
assert_eq!(
296+
wt1_config.string("override.setting").expect("exists").as_ref(),
297+
"set in the shared config"
298+
);
299+
}
300+
301+
{
302+
let wt2 = open(fixture_dir.join("wt-2"))?;
303+
let wt2_config = wt2.config_snapshot();
304+
assert_eq!(
305+
wt2.work_dir(),
306+
Some(fixture_dir.join("wt-2").as_path()),
307+
"another linked worktree as sibling to wt-1"
308+
);
309+
assert_eq!(wt2.git_dir(), worktree_base.join("wt-2"));
310+
assert_eq!(wt2.common_dir(), worktree_base.join("wt-2/../.."));
311+
312+
assert_eq!(
313+
wt2_config.string("worktree.setting").expect("exists").as_ref(),
314+
"set in wt-2"
315+
);
316+
assert_eq!(
317+
wt2_config.string("shared.setting").expect("exists").as_ref(),
318+
"set in the shared config"
319+
);
320+
assert_eq!(
321+
wt2_config.string("override.setting").expect("exists").as_ref(),
322+
"override in wt-2"
323+
);
324+
}
325+
326+
Ok(())
327+
}
328+
}

0 commit comments

Comments
 (0)