1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! Explicit storage selection and command worktree setup, independent of ambient environment.
use super::*;
impl RepositoryLocation {
/// Selects an exact private Git directory with optional common and object storage overrides.
///
/// Every supplied path must be nonempty and absolute. The caller anchors command-environment
/// paths against its captured working directory, rejecting empty values before anchoring.
/// An explicit common directory bypasses `commondir` entirely, even if that file is malformed.
/// Its explicit presence remains meaningful when it names the private directory itself.
/// Private and common directories are canonicalized; the private logical alias remains in
/// conditional-include context. Object storage is checked later by metadata or full opening.
///
/// This does not select an index, consult environment variables, establish trust, or freeze
/// filesystem identity. [`Self::read_metadata_for_command`] supplies command worktree setup;
/// ordinary metadata/full opening retains its existing linked-backlink validation.
///
/// # Errors
///
/// Rejects relative/empty supplied paths and reports private/common selection errors as in
/// [`Self::at_git_dir`]. The object path need not exist until metadata is read.
pub fn at_git_dir_with_storage(
git_dir: impl AsRef<Path>,
common_dir: Option<&Path>,
object_dir: Option<&Path>,
) -> Result<Self, OpenError> {
let git_dir = git_dir.as_ref();
require_absolute(git_dir)?;
if let Some(path) = common_dir {
require_absolute(path)?;
}
if let Some(path) = object_dir {
require_absolute(path)?;
}
Self::resolve_storage(git_dir, true, common_dir, object_dir)
}
/// Reads selected metadata with explicit-command worktree setup instead of backlink inference.
///
/// `cwd` and a supplied `worktree_override` must be nonempty absolute paths. An override wins
/// over configured bare/worktree settings, even when the checkout does not exist. Otherwise
/// the direct layout configuration selects bare or a worktree path (relative to the private
/// Git directory), falling back to `cwd`. A source checkout and its backlink are not required.
///
/// An explicitly supplied common directory suppresses its `core.bare` and `core.worktree`,
/// including when private/common paths are equal. Enabled private `config.worktree` settings
/// still apply. An on-disk `commondir` relationship also suppresses common layout settings.
/// The effective configuration retains these values and their origins for policy consumers.
///
/// Conflicting configured bare/worktree settings are represented by
/// [`RepositoryMetadata::has_conflicting_worktree_config`], so the caller can report a warning.
/// Normal metadata opening still rejects that conflict and validates linked backlinks.
/// No environment or process working directory is read. Relative caller config-file paths
/// must also be anchored before supplying `inputs`.
///
/// # Errors
///
/// Reports metadata/configuration errors as in [`Self::read_metadata_with_config`]. Command
/// setup currently requires an explicit direct `core.repositoryFormatVersion`; repositories
/// omitting it have different Git bootstrap rules and return [`OpenError::Unsupported`].
/// Configured worktree interpolation remains unsupported. Supplied storage is not mutated.
///
/// # Examples
///
/// ```no_run
/// use girt::RepositoryLocation;
/// use girt::config::ConfigInputs;
/// let cwd = std::env::current_dir()?;
/// let location = RepositoryLocation::at_git_dir_with_storage(cwd.join(".git"), None, None)?;
/// let metadata = location.read_metadata_for_command(&ConfigInputs::default(), &cwd, None)?;
/// if metadata.has_conflicting_worktree_config() {
/// eprintln!("core.bare and core.worktree conflict");
/// }
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn read_metadata_for_command(
&self,
inputs: &ConfigInputs,
cwd: &Path,
worktree_override: Option<&Path>,
) -> Result<RepositoryMetadata, OpenError> {
require_absolute(cwd)?;
for file in &inputs.files {
require_absolute(&file.path)?;
}
for path in inputs
.context
.home
.iter()
.chain(inputs.context.prefix.iter())
{
require_absolute(path)?;
}
for (_, path) in &inputs.context.user_homes {
require_absolute(path)?;
}
if let Some(path) = worktree_override {
require_absolute(path)?;
}
RepositoryMetadata::read_location(
self,
inputs,
crate::refs::reftable::StackLimits::default(),
Some(CommandWorktree {
cwd,
worktree_override,
}),
IncludePlacement::InPlace,
)
}
}
impl RepositoryMetadata {
/// Whether command setup observed both `core.bare=true` and a configured worktree path.
///
/// Bare takes precedence unless an explicit worktree override is supplied. The caller owns
/// reporting a warning; no diagnostic is printed by metadata reading. Ordinary metadata
/// opening rejects the conflict instead of returning it.
pub fn has_conflicting_worktree_config(&self) -> bool {
self.worktree_config_conflict
}
}
pub(super) struct CommandWorktree<'a> {
cwd: &'a Path,
worktree_override: Option<&'a Path>,
}
impl CommandWorktree<'_> {
pub(super) fn resolve(
self,
git_dir: &Path,
config: &Config,
source: &Path,
) -> Result<(Option<PathBuf>, bool, bool), OpenError> {
if let Some(path) = self.worktree_override {
return Ok((Some(available_path(path)?), false, false));
}
let bare = boolean(config, source, "bare")? == Some(true);
let configured = config.value("core", None, "worktree");
if bare {
return Ok((None, true, configured.is_some()));
}
let worktree = if let Some(value) = configured {
let value = value
.filter(|v| !v.is_empty())
.ok_or_else(|| malformed(source, "empty core.worktree"))?;
if value.starts_with(b"~") || value.starts_with(b"%(") {
return Err(unsupported(source, "core.worktree path interpolation"));
}
available_path(&git_dir.join(path_bytes(source, value)?))?
} else {
available_path(self.cwd)?
};
Ok((Some(worktree), false, false))
}
}
fn require_absolute(path: &Path) -> Result<(), OpenError> {
if path.as_os_str().is_empty() || !path.is_absolute() {
return Err(malformed(
path,
"explicit selection requires a nonempty absolute path",
));
}
Ok(())
}
#[cfg(test)]
mod tests;