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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
use std::{collections::BTreeMap, path::PathBuf};
use crate::error::{ErrorExt, ResultExt, bail, message};
use crate::{
Result, Worktree,
bstr::{BStr, ByteSlice},
worktree,
};
/// Interact with individual worktrees and their information.
impl crate::Repository {
/// Return all references checked out or reserved by bisect or rebase in the main and linked worktrees.
///
/// Each key maps to the worktree directories in which it is checked out. Besides branch names,
/// `HEAD` is recorded for every worktree with a readable head, and all symbolic references in
/// the chain from `HEAD` to its referent are included. Bisect and rebase also reserve their original
/// branch while `HEAD` is detached. Bare repositories and worktrees whose head cannot be read are ignored.
///
/// The repository's current reference namespace is used for every inspected worktree.
pub(crate) fn checked_out_branches(&self) -> Result<BTreeMap<gix_ref::FullName, Vec<PathBuf>>> {
self.checked_out_branches_inner(self.namespace())
}
/// Like [`checked_out_branches()`][Self::checked_out_branches()], but ignore the repository's reference namespace.
///
/// Resolve each worktree's `HEAD` and its referents in the default namespace.
/// Use this for worktree creation, which ignores reference namespaces like `git worktree add`.
#[cfg(feature = "worktree-mutation")]
pub(crate) fn checked_out_branches_without_namespace(&self) -> Result<BTreeMap<gix_ref::FullName, Vec<PathBuf>>> {
self.checked_out_branches_inner(None)
}
fn checked_out_branches_inner(
&self,
namespace: Option<&gix_ref::Namespace>,
) -> Result<BTreeMap<gix_ref::FullName, Vec<PathBuf>>> {
let mut map = BTreeMap::new();
for repo in self
.worktrees_including_main()
.or_raise(|| message("Failed to read or iterate worktree directories"))?
{
let mut repo = repo.or_raise(|| message("Could not open a worktree repository"))?;
repo.refs.namespace = namespace.cloned();
insert_head(repo.head().ok(), &mut map)?;
}
Ok(map)
}
/// Return a list of all **linked** worktrees sorted by private git dir path as a lightweight proxy.
///
/// This means the number is `0` even if there is the main worktree, as it is not counted as linked worktree.
/// This also means it will be `1` if there is one linked worktree next to the main worktree.
/// It's worth noting that a *bare* repository may have one or more linked worktrees, but has no *main* worktree,
/// which is the reason why the *possibly* available main worktree isn't listed here.
///
/// Use [`worktrees_including_main()`][Self::worktrees_including_main()] to open the main and linked repositories.
///
/// Note that these need additional processing to become usable, but provide a first glimpse a typical worktree information.
pub fn worktrees(&self) -> Result<Vec<worktree::Proxy<'_>>> {
let mut res = Vec::new();
let iter = match std::fs::read_dir(self.current_dir().join(self.common_dir()).join("worktrees")) {
Ok(iter) => iter,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(res),
Err(err) => bail!(err),
};
for entry in iter {
let entry = entry.or_error()?;
let worktree_git_dir = entry.path();
res.extend(worktree::Proxy::new_if_gitdir_file_exists(self, worktree_git_dir));
}
res.sort_by(|a, b| a.git_dir.cmp(&b.git_dir));
Ok(res)
}
/// Return an iterator over the main repository, then each linked worktree repository in private Git directory order.
///
/// Each repository is yielded once, regardless of which worktree this method is called from. The main repository
/// is included even if it is bare, matching `git worktree list`.
///
/// Linked worktrees are listed immediately and opened during iteration. Opening errors are yielded
/// per entry. All returned repositories have absolute paths. Missing or inaccessible checkout directories are
/// allowed, as with
/// [`Proxy::into_repo_with_possibly_inaccessible_worktree()`][worktree::Proxy::into_repo_with_possibly_inaccessible_worktree()].
pub fn worktrees_including_main(&self) -> Result<impl Iterator<Item = Result<crate::Repository>> + '_> {
Ok(std::iter::once_with(|| self.main_repo()).chain(
self.worktrees()?
.into_iter()
.map(worktree::Proxy::into_repo_with_possibly_inaccessible_worktree),
))
}
/// Return the worktree that [is identified](Worktree::id) by the given `id`, if it exists at
/// `.git/worktrees/<id>` and its `gitdir` file exists.
/// Return `Ok(None)` otherwise, or an error if `id` cannot be represented as a native path.
pub fn worktree_proxy_by_id<'a>(&self, id: impl Into<&'a BStr>) -> Result<Option<worktree::Proxy<'_>>> {
Ok(worktree::Proxy::new_if_gitdir_file_exists(
self,
self.common_dir()
.join("worktrees")
.join(gix_path::from_bstr(id.into())?),
))
}
/// Return the *repository* owning the main worktree, typically from a linked worktree.
///
/// If this repository isn't a linked worktree and thus already "main", return a [clone](Clone::clone)
/// of this in-memory repository handle. The main repository may be bare.
///
/// The returned repository has absolute paths, leaving this handle unchanged. A cloned handle is anchored with
/// [`make_paths_absolute()`][Self::make_paths_absolute()].
pub fn main_repo(&self) -> Result<crate::Repository> {
if self.kind() == crate::repository::Kind::LinkedWorkTree {
let options = self
.options
.clone()
.without_repository_environment_overrides()
.open_path_as_is(true);
crate::ThreadSafeRepository::open_opts(self.current_dir().join(self.common_dir()), options).map(Into::into)
} else {
let mut repo = self.clone();
repo.make_paths_absolute()?;
Ok(repo)
}
}
/// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path.
///
/// Note that this would be `None` if this repository is `bare` and the parent [`Repository`](crate::Repository)
/// was instantiated without registered worktree in the current working dir, even if no `.git` file or directory exists.
/// It's merely based on configuration, see [Worktree::dot_git_exists()] for a way to perform more validation.
pub fn worktree(&self) -> Option<Worktree<'_>> {
self.workdir().map(|path| Worktree { parent: self, path })
}
/// Return true if this repository is bare, or in absence of a known configuration value, if it has no work tree.
///
/// This is not to be confused with the [`worktree()`](crate::Repository::worktree()) method, which may exist if this instance
/// was opened in a worktree that was created separately.
pub fn is_bare(&self) -> bool {
self.config.is_bare.unwrap_or_else(|| self.workdir().is_none())
}
/// If `id` points to a tree, produce a stream that yields one worktree entry after the other. The index of the tree at `id`
/// is returned as well as it is an intermediate byproduct that might be useful to callers.
///
/// The entries will look exactly like they would if one would check them out, with filters applied.
/// The `export-ignore` attribute is used to skip blobs or directories to which it applies.
#[cfg(feature = "worktree-stream")]
pub fn worktree_stream(
&self,
id: impl Into<gix_hash::ObjectId>,
) -> Result<(gix_worktree_stream::Stream, gix_index::File)> {
use gix_odb::HeaderExt;
let id = id.into();
let header = self.objects.header(id)?;
if !header.kind().is_tree() {
bail!(gix_error::validation(format!(
"Needed {id} to be a tree to turn into a workspace stream, got {}",
header.kind()
)));
}
// TODO(perf): potential performance improvements could be to use the index at `HEAD` if possible (`index_from_head_tree…()`)
// TODO(perf): when loading a non-HEAD tree, we effectively traverse the tree twice. This is usually fast though, and sharing
// an object cache between the copies of the ODB handles isn't trivial and needs a lock.
let index = self.index_from_tree(&id)?;
let mut cache = self
.attributes_only(&index, gix_worktree::stack::state::attributes::Source::IdMapping)?
.detach();
let pipeline = gix_filter::Pipeline::new(
self.command_context()?,
self.object_hash(),
crate::filter::Pipeline::options(self)?,
);
let objects = self.objects.clone().into_arc().expect("TBD error handling");
let stream = gix_worktree_stream::from_tree(
id,
objects.clone(),
pipeline,
move |path, mode, attrs| -> std::io::Result<()> {
let entry = cache.at_entry(path, Some(mode.into()), &objects)?;
entry.matching_attributes(attrs);
Ok(())
},
);
Ok((stream, index))
}
/// Produce an archive from the `stream` and write it to `out` according to `options`.
/// Use `blob` to provide progress for each entry written to `out`, and note that it should already be initialized to the amount
/// of expected entries, with `should_interrupt` being queried between each entry to abort if needed, and on each write to `out`.
///
/// ### Performance
///
/// Be sure that `out` is able to handle a lot of write calls. Otherwise wrap it in a [`BufWriter`][std::io::BufWriter].
///
/// ### Additional progress and fine-grained interrupt handling
///
/// For additional progress reporting, wrap `out` into a writer that counts throughput on each write.
/// This can also be used to react to interrupts on each write, instead of only for each entry.
#[cfg(feature = "worktree-archive")]
pub fn worktree_archive(
&self,
mut stream: gix_worktree_stream::Stream,
out: impl std::io::Write + std::io::Seek,
blobs: impl gix_utils::progress::Count,
should_interrupt: &std::sync::atomic::AtomicBool,
options: gix_archive::Options,
) -> Result<()> {
let mut out = gix_utils::interrupt::Write {
inner: out,
should_interrupt,
};
if options.format == gix_archive::Format::InternalTransientNonPersistable {
std::io::copy(&mut stream.into_read(), &mut out).or_raise(|| message("Could not copy stream"))?;
return Ok(());
}
gix_archive::write_stream_seek(
&mut stream,
|stream| {
if should_interrupt.load(std::sync::atomic::Ordering::Relaxed) {
bail!(gix_error::cancelled("Cancelled by user"));
}
let res = stream.next_entry();
blobs.inc();
res
},
out,
options,
)?;
Ok(())
}
}
/// Record the worktree directory under `HEAD`, its symbolic referents, and branches reserved by bisect or rebase.
///
/// Do nothing if `head` is absent or its repository has no worktree, and fail if a symbolic
/// reference in the chain cannot be followed or an operation's branch file cannot be read.
fn insert_head(head: Option<crate::Head<'_>>, out: &mut BTreeMap<gix_ref::FullName, Vec<PathBuf>>) -> Result {
let Some((head, workdir)) = head.and_then(|head| head.repo.workdir().map(|workdir| (head, workdir))) else {
return Ok(());
};
out.entry("HEAD".try_into().expect("valid reference name"))
.or_default()
.push(workdir.to_owned());
let repo = head.repo;
let mut cursor = head.try_into_referent();
while let Some(reference) = cursor {
out.entry(reference.name().to_owned())
.or_default()
.push(workdir.to_owned());
cursor = reference
.follow()
.transpose()
.or_raise(|| message("Failed to follow a symbolic reference"))?;
}
let git_dir = repo.git_dir();
let rebase = if git_dir.join("rebase-apply").is_dir() {
// `git am` uses the same directory but doesn't reserve a branch for rebase.
(!git_dir.join("rebase-apply/applying").is_file()).then_some("rebase-apply/head-name")
} else {
git_dir
.join("rebase-merge")
.is_dir()
.then_some("rebase-merge/head-name")
};
let bisect = git_dir.join("BISECT_LOG").is_file().then_some("BISECT_START");
for path in rebase.into_iter().chain(bisect) {
let contents = match std::fs::read(git_dir.join(path)) {
Ok(contents) => contents,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue,
Err(err) => {
return Err(err.and_raise(message!(
"Failed to read worktree operation state at {:?}",
git_dir.join(path)
)));
}
};
let name = contents.trim_end_with(|c| c == '\n');
let name = if name.starts_with(b"refs/heads/") {
name.to_owned()
} else {
// An operation started from detached HEAD doesn't reserve a branch.
if name.is_empty() || name == b"detached HEAD" || gix_hash::ObjectId::from_hex(name).is_ok() {
continue;
}
[b"refs/heads/".as_slice(), name].concat()
};
let Ok(name) = gix_ref::FullName::try_from(name.as_bstr()) else {
continue;
};
let workdirs = out.entry(name).or_default();
if !workdirs.iter().any(|path| path == workdir) {
workdirs.push(workdir.to_owned());
}
}
Ok(())
}