Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gix/src/discover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl ThreadSafeRepository {
options.git_dir_trust = trust.into();
// Note that we will adjust the `current_dir` later so it matches the value of `core.precomposeUnicode`.
options.current_dir = Some(gix_fs::current_dir(false).map_err(upwards::Error::CurrentDir)?);
Self::open_from_paths(git_dir, worktree_dir, options).map_err(Into::into)
Self::open_from_paths(git_dir, worktree_dir, options, None).map_err(Into::into)
}

/// Try to open a git repository directly from the environment.
Expand Down
2 changes: 1 addition & 1 deletion gix/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl ThreadSafeRepository {
open_options.git_dir_trust = Some(gix_sec::Trust::Full);
// The repo will use `core.precomposeUnicode` to adjust the value as needed.
open_options.current_dir = gix_fs::current_dir(false)?.into();
let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options)?;
let repo = ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, open_options, None)?;

let branch_name = repo
.config
Expand Down
14 changes: 9 additions & 5 deletions gix/src/open/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ impl ThreadSafeRepository {
options.git_dir_trust = gix_sec::Trust::from_path_ownership(&git_dir)?.into();
}
options.current_dir = Some(cwd);
ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options)
ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options, None)
}

/// Try to open a git repository in `fallback_directory` (can be worktree or `.git` directory) only if there is no override
Expand Down Expand Up @@ -154,13 +154,14 @@ impl ThreadSafeRepository {
let mut options = trust_map.into_value_by_level(git_dir_trust);
options.git_dir_trust = git_dir_trust.into();
options.current_dir = Some(cwd);
ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options)
ThreadSafeRepository::open_from_paths(git_dir, worktree_dir, options, None)
}

pub(crate) fn open_from_paths(
mut git_dir: PathBuf,
mut worktree_dir: Option<PathBuf>,
mut options: Options,
known_common_dir: Option<PathBuf>,
) -> Result<Self, Error> {
let _span = gix_trace::detail!("open_from_paths()");
options.open_path_as_is = false;
Expand All @@ -185,9 +186,12 @@ impl ThreadSafeRepository {
} = options;
let git_dir_trust = git_dir_trust.as_mut().expect("trust must be determined by now");

let mut common_dir = gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
.transpose()?
.map(|cd| git_dir.join(cd));
let mut common_dir = match known_common_dir {
Some(common_dir) => Some(common_dir),
None => gix_discover::path::from_plain_file(git_dir.join("commondir").as_ref())
.transpose()?
.map(|cd| git_dir.join(cd)),
};
let repo_config = config::cache::StageOne::new(
common_dir.as_deref().unwrap_or(&git_dir),
git_dir.as_ref(),
Expand Down
49 changes: 49 additions & 0 deletions gix/src/remote/connection/fetch/update_refs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,55 @@ mod update {
Ok(())
}

#[test]
fn incomplete_linked_worktrees_without_a_head_are_ignored() -> Result {
let (repo, _tmp) = repo_rw("two-origins");
let git_dir = repo.common_dir().join("worktrees/incomplete");
std::fs::create_dir_all(&git_dir)?;
std::fs::write(git_dir.join("gitdir"), b"missing/.git\n")?;

let (mappings, specs) = mapping_from_spec("refs/heads/main:refs/remotes/origin/incomplete", &repo);
let update = || {
fetch::refs::update(
&repo,
prefixed("action"),
&mappings,
&specs,
&[],
fetch::Tags::None,
fetch::DryRun::Yes,
fetch::WritePackedRefs::Never,
)
};
let expected = vec![fetch::refs::Update {
mode: fetch::refs::update::Mode::New,
type_change: None,
edit_index: Some(0),
}];

assert_eq!(
update()?.updates,
expected,
"the incomplete worktree contributes no checked-out branch"
);

std::fs::write(git_dir.join("locked"), b"still in use\n")?;
assert_eq!(
update()?.updates,
expected,
"locking does not make an unreadable head protect a branch"
);

std::fs::remove_file(git_dir.join("locked"))?;
std::fs::write(git_dir.join("commondir"), b"missing\n")?;
assert_eq!(
update()?.updates,
expected,
"the parent repository supplies the authoritative common directory"
);
Ok(())
}

#[test]
fn unborn_remote_branches_can_be_created_locally_if_they_are_new() -> Result {
let repo = named_repo("unborn");
Expand Down
33 changes: 24 additions & 9 deletions gix/src/worktree/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,20 @@ impl<'repo> Proxy<'repo> {
}

impl Proxy<'_> {
/// Read the location of the checkout, the base of the work tree.
/// Note that the location might not exist.
pub fn base(&self) -> std::io::Result<PathBuf> {
fn dot_git(&self) -> std::io::Result<PathBuf> {
let git_dir = self.git_dir.join("gitdir");
let base_dot_git = gix_discover::path::from_plain_file_relative_to_file(&git_dir).ok_or_else(|| {
gix_discover::path::from_plain_file_relative_to_file(&git_dir).ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Required file '{}' does not exist", git_dir.display()),
)
})??;
})?
}

Ok(gix_discover::path::without_dot_git_dir(base_dot_git))
/// Read the location of the checkout, the base of the work tree.
/// Note that the location might not exist.
pub fn base(&self) -> std::io::Result<PathBuf> {
Ok(gix_discover::path::without_dot_git_dir(self.dot_git()?))
}

/// The git directory for the work tree, typically contained within the parent git dir.
Expand All @@ -70,7 +72,18 @@ impl Proxy<'_> {

/// Return true if the worktree cannot be pruned, moved or deleted, which is useful if it is located on an external storage device.
pub fn is_locked(&self) -> bool {
self.git_dir.join("locked").is_file()
self.git_dir.join("locked").symlink_metadata().is_ok()
}

/// Return true if this worktree can be pruned without an expiry grace period.
///
/// Locked worktrees are never prunable. Otherwise, an unreadable `gitdir` file or missing target
/// makes the worktree prunable.
pub fn is_prunable(&self) -> bool {
!self.is_locked()
&& self
.dot_git()
.map_or(true, |dot_git| dot_git.symlink_metadata().is_err())
}

/// Provide a reason for the locking of this worktree, if it is locked at all.
Expand All @@ -92,7 +105,8 @@ impl Proxy<'_> {
pub fn into_repo_with_possibly_inaccessible_worktree(self) -> Result<Repository, crate::open::Error> {
let base = self.base().ok();
let options = self.parent.options.clone().without_repository_environment_overrides();
let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base, options)?;
let common_dir = self.parent.common_dir().to_owned();
let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base, options, Some(common_dir))?;
Ok(repo.into())
}

Expand All @@ -106,7 +120,8 @@ impl Proxy<'_> {
return Err(into_repo::Error::MissingWorktree { base });
}
let options = self.parent.options.clone().without_repository_environment_overrides();
let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base.into(), options)?;
let common_dir = self.parent.common_dir().to_owned();
let repo = ThreadSafeRepository::open_from_paths(self.git_dir, base.into(), options, Some(common_dir))?;
Ok(repo.into())
}
}
11 changes: 8 additions & 3 deletions gix/tests/gix/repository/worktree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,11 @@ fn run_assertions(main_repo: gix::Repository, should_be_bare: bool) {
assert_eq!(proxy_lock_reason, expected.locked);
let proxy_is_locked = actual.is_locked();
assert_eq!(proxy_is_locked, proxy_lock_reason.is_some());
assert_eq!(
actual.is_prunable(),
expected.prunable.is_some(),
"prunability matches `git worktree list --porcelain`"
);
// TODO: check id of expected worktree, but need access to .gitdir from worktree base
let proxy_id = actual.id().to_owned();
assert_eq!(
Expand Down Expand Up @@ -485,15 +490,15 @@ fn run_assertions(main_repo: gix::Repository, should_be_bare: bool) {
let proxy_by_id = repo
.worktree_proxy_by_id(actual.id())
.expect("can get the proxy from a linked repo as well");
assert_ne!(
assert_eq!(
proxy_by_id.git_dir(),
actual.git_dir(),
"The git directories might not look the same"
"The git directories are the same"
);
assert_eq!(
gix_path::realpath(proxy_by_id.git_dir()).ok(),
gix_path::realpath(actual.git_dir()).ok(),
"…but they are the same effectively"
"the git directories are effectively the same"
);
}
}
Loading