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
use std::{io::Read, rc::Rc};
use crate::error::{ErrorExt, ResultExt, message};
use crate::{Repository, Result, submodule};
use gix_fs::FileOrSymlink;
impl Repository {
/// Open the `.gitmodules` file as present in the worktree, or return `None` if no such file is available.
/// Symlinked worktree `.gitmodules` files are silently ignored so content outside the repository
/// cannot become active submodule configuration by being linked into the worktree.
/// Note that git configuration is also contributing to the result based on the current snapshot.
/// Only sections accepted by the repository's configuration filter contribute overrides.
///
/// Note that his method will not look in other places, like the index or the `HEAD` tree.
pub fn open_modules_file(&self) -> Result<Option<gix_submodule::File>> {
let path = match self.modules_path() {
Some(path) => path,
None => return Ok(None),
};
let mut file = match gix_fs::open_read_only_no_follow(&path) {
Ok(FileOrSymlink::File(file)) => file,
Ok(FileOrSymlink::Symlink) => return Ok(None),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(err.and_raise(message("Could not open '.gitmodules' file"))),
};
let mut buf = Vec::new();
file.read_to_end(&mut buf)
.or_raise(|| message("Could not read '.gitmodules' file"))?;
Ok(Some(self.modules_from_bytes(&buf, Some(path))?))
}
/// Return a shared [`.gitmodules` file](submodule::File) which is updated automatically if the in-memory snapshot
/// has become stale as the underlying file on disk has changed. The snapshot based on the file on disk is shared across all
/// clones of this repository.
///
/// If a file on disk isn't present, we will try to load it from the index, and finally from the current tree.
/// In the latter two cases, the result will not be cached in this repository instance as we can't detect freshness anymore,
/// so time this method is called a new [modules file](submodule::ModulesSnapshot) will be created.
///
/// Note that git configuration is also contributing to the result based on the current snapshot.
///
// TODO(submodule): make it use an updated snapshot instead once we have `config()`.
pub fn modules(&self) -> Result<Option<submodule::ModulesSnapshot>> {
match self.modules.recent_snapshot(
|| {
self.modules_path()
.and_then(|path| path.metadata().and_then(|m| m.modified()).ok())
},
|| self.open_modules_file(),
)? {
Some(m) => Ok(Some(m)),
None => {
let id = match self.try_index()?.and_then(|index| {
index
.entry_by_path(submodule::MODULES_FILE.into())
.map(|entry| entry.id)
}) {
Some(id) => id,
None => match self
.head()?
.try_peel_to_id()?
.map(|id| -> Result<Option<_>> {
Ok(id
.object()?
.peel_to_commit()?
.tree()?
.find_entry(submodule::MODULES_FILE)
.map(|entry| entry.inner.oid.to_owned()))
})
.transpose()?
.flatten()
{
Some(id) => id,
None => return Ok(None),
},
};
Ok(Some(gix_parallel::OwnShared::new(
self.modules_from_bytes(&self.find_object(id)?.data, None)?.into(),
)))
}
}
}
fn modules_from_bytes(&self, bytes: &[u8], path: Option<std::path::PathBuf>) -> Result<gix_submodule::File> {
let mut overrides = gix_config::File::new(self.config.resolved.meta_owned());
for section in self
.config
.resolved
.sections_by_name_and_filter("submodule", self.filter_config_section())
.into_iter()
.flatten()
{
overrides.push_section(section.to_owned())?;
}
gix_submodule::File::from_bytes(bytes, path, &overrides)
}
/// Return the list of available submodules, or `None` if there is no submodule configuration.
#[doc(alias = "git2")]
pub fn submodules(&self) -> Result<Option<impl Iterator<Item = crate::Submodule<'_>>>> {
let modules = match self.modules()? {
None => return Ok(None),
Some(m) => m,
};
let shared_state = Rc::new(submodule::SharedState::new(self, modules));
Ok(Some(
shared_state
.modules
.names()
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.into_iter()
.map(move |name| crate::Submodule {
state: shared_state.clone(),
name,
}),
))
}
}