Skip to content

Commit 0ed0164

Browse files
committed
feat: add Repository::set_workdir().
Force this repository instance to use the given worktree directory.
1 parent 94dac4b commit 0ed0164

File tree

5 files changed

+93
-4
lines changed

5 files changed

+93
-4
lines changed

gitoxide-core/src/repository/merge_base.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ pub fn merge_base(
1616
let first_id = repo.rev_parse_single(first.as_str())?;
1717
let other_ids: Vec<_> = others
1818
.iter()
19-
.cloned()
2019
.map(|other| repo.rev_parse_single(other.as_str()).map(gix::Id::detach))
2120
.collect::<Result<_, _>>()?;
2221

gix/src/repository/location.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,36 @@ impl crate::Repository {
5858
self.work_tree.as_deref()
5959
}
6060

61+
/// Forcefully set the given `workdir` to be the worktree of this repository, *in memory*,
62+
/// no matter if it had one or not, or unset it with `None`.
63+
/// Return the previous working directory if one existed.
64+
///
65+
/// Fail if the `workdir`, if not `None`, isn't accessible or isn't a directory.
66+
/// No change is performed on error.
67+
///
68+
/// ### About Worktrees
69+
///
70+
/// * When setting a main worktree to a linked worktree directory, this repository instance
71+
/// will still claim that it is the [main worktree](crate::Worktree::is_main()) as that depends
72+
/// on the `git_dir`, not the worktree dir.
73+
/// * When setting a linked worktree to a main worktree directory, this repository instance
74+
/// will still claim that it is *not* a [main worktree](crate::Worktree::is_main()) as that depends
75+
/// on the `git_dir`, not the worktree dir.
76+
#[doc(alias = "git2")]
77+
pub fn set_workdir(&mut self, workdir: impl Into<Option<PathBuf>>) -> Result<Option<PathBuf>, std::io::Error> {
78+
let workdir = workdir.into();
79+
Ok(match workdir {
80+
None => self.work_tree.take(),
81+
Some(new_workdir) => {
82+
_ = std::fs::read_dir(&new_workdir)?;
83+
84+
let old = self.work_tree.take();
85+
self.work_tree = Some(new_workdir);
86+
old
87+
}
88+
})
89+
}
90+
6191
/// Return the work tree containing all checked out files, if there is one.
6292
pub fn workdir(&self) -> Option<&std::path::Path> {
6393
self.work_tree.as_deref()

gix/src/repository/worktree.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ impl crate::Repository {
3030
res.sort_by(|a, b| a.git_dir.cmp(&b.git_dir));
3131
Ok(res)
3232
}
33+
3334
/// Return the repository owning the main worktree, typically from a linked worktree.
3435
///
3536
/// Note that it might be the one that is currently open if this repository doesn't point to a linked worktree.
@@ -41,7 +42,7 @@ impl crate::Repository {
4142

4243
/// Return the currently set worktree if there is one, acting as platform providing a validated worktree base path.
4344
///
44-
/// Note that there would be `None` if this repository is `bare` and the parent [`Repository`][crate::Repository] was instantiated without
45+
/// Note that there would be `None` if this repository is `bare` and the parent [`Repository`](crate::Repository) was instantiated without
4546
/// registered worktree in the current working dir, even if no `.git` file or directory exists.
4647
/// It's merely based on configuration, see [Worktree::dot_git_exists()] for a way to perform more validation.
4748
pub fn worktree(&self) -> Option<Worktree<'_>> {
@@ -50,7 +51,7 @@ impl crate::Repository {
5051

5152
/// Return true if this repository is bare, and has no main work tree.
5253
///
53-
/// This is not to be confused with the [`worktree()`][crate::Repository::worktree()] worktree, which may exists if this instance
54+
/// This is not to be confused with the [`worktree()`](crate::Repository::worktree()) method, which may exist if this instance
5455
/// was opened in a worktree that was created separately.
5556
pub fn is_bare(&self) -> bool {
5657
self.config.is_bare && self.workdir().is_none()

gix/tests/gix/repository/open.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ fn non_bare_non_git_repo_without_worktree() -> crate::Result {
154154

155155
#[test]
156156
fn none_bare_repo_without_index() -> crate::Result {
157-
let repo = named_subrepo_opts(
157+
let mut repo = named_subrepo_opts(
158158
"make_basic_repo.sh",
159159
"non-bare-repo-without-index",
160160
gix::open::Options::isolated(),
@@ -175,6 +175,28 @@ fn none_bare_repo_without_index() -> crate::Result {
175175
.is_ok(),
176176
"this is a minimal path"
177177
);
178+
179+
let old = repo.set_workdir(None).expect("should never fail");
180+
assert_eq!(
181+
old.as_ref().and_then(|wd| wd.file_name()?.to_str()),
182+
Some("non-bare-repo-without-index")
183+
);
184+
assert!(repo.workdir().is_none(), "the workdir was unset");
185+
assert!(repo.worktree().is_none(), "the worktree was unset");
186+
assert!(
187+
!repo.is_bare(),
188+
"this is based on `core.bare`, not on the lack of worktree"
189+
);
190+
191+
assert_eq!(
192+
repo.set_workdir(old.clone()).expect("does not fail as it exists"),
193+
None,
194+
"nothing was set before"
195+
);
196+
assert_eq!(repo.workdir(), old.as_deref());
197+
198+
let worktree = repo.worktree().expect("should be present after setting");
199+
assert!(worktree.is_main(), "it's still the main worktree");
178200
Ok(())
179201
}
180202

gix/tests/gix/repository/worktree.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,43 @@ fn from_nonbare_parent_repo() {
254254
run_assertions(repo, false /* bare */);
255255
}
256256

257+
#[test]
258+
fn from_nonbare_parent_repo_set_workdir() -> gix_testtools::Result {
259+
if gix_testtools::should_skip_as_git_version_is_smaller_than(2, 31, 0) {
260+
return Ok(());
261+
}
262+
263+
let dir = gix_testtools::scripted_fixture_read_only("make_worktree_repo.sh").unwrap();
264+
let mut repo = gix::open(dir.join("repo")).unwrap();
265+
266+
assert!(repo.worktree().is_some_and(|wt| wt.is_main()), "we have main worktree");
267+
268+
let worktrees = repo.worktrees()?;
269+
assert_eq!(worktrees.len(), 6);
270+
271+
let linked_wt_dir = worktrees.first().unwrap().base().expect("this linked worktree exists");
272+
repo.set_workdir(linked_wt_dir).expect("works as the dir exists");
273+
274+
assert!(
275+
repo.worktree().is_some_and(|wt| wt.is_main()),
276+
"it's still the main worktree as that depends on the git_dir"
277+
);
278+
279+
let mut wt_repo = repo.worktrees()?.first().unwrap().clone().into_repo()?;
280+
assert!(
281+
wt_repo.worktree().is_some_and(|wt| !wt.is_main()),
282+
"linked worktrees are never main"
283+
);
284+
285+
wt_repo.set_workdir(Some(repo.workdir().unwrap().to_owned()))?;
286+
assert!(
287+
wt_repo.worktree().is_some_and(|wt| !wt.is_main()),
288+
"it's still the linked worktree as that depends on the git_dir"
289+
);
290+
291+
Ok(())
292+
}
293+
257294
fn run_assertions(main_repo: gix::Repository, should_be_bare: bool) {
258295
assert_eq!(main_repo.is_bare(), should_be_bare);
259296
let mut baseline = Baseline::collect(

0 commit comments

Comments
 (0)