fix: follow a link anywhere under the store base
This commit is contained in:
@@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result, anyhow, bail};
|
||||
|
||||
use self::store::{Leads, Repo, ignored, leads, resolve, symlink_metadata_opt, tracked};
|
||||
use self::store::{Leads, Repo, held, ignored, leads, resolve, symlink_metadata_opt, tracked};
|
||||
use crate::cli::link as cli;
|
||||
use crate::ctx::Ctx;
|
||||
use crate::fsops::suffixed;
|
||||
@@ -104,7 +104,6 @@ pub fn restore(ctx: &Ctx, args: &cli::Restore) -> Result<()> {
|
||||
fn restore_one(ctx: &Ctx, repo: &Repo, path: &Path, report: &Report) -> Result<()> {
|
||||
let src = resolve(path)?;
|
||||
let rel = repo.relative(&src)?;
|
||||
let stored = repo.store.join(&rel);
|
||||
|
||||
let Some(meta) = symlink_metadata_opt(&src)? else {
|
||||
bail!("{} does not exist", rel.display());
|
||||
@@ -116,14 +115,16 @@ fn restore_one(ctx: &Ctx, repo: &Repo, path: &Path, report: &Report) -> Result<(
|
||||
);
|
||||
}
|
||||
|
||||
// followed to wherever under the base it points, so a checkout that moved
|
||||
// or a store laid out by an older ahab can still take its files back
|
||||
let dest = read_link(&src)?;
|
||||
if dest != stored {
|
||||
let Some(stored) = held(repo, &src, &dest) else {
|
||||
bail!(
|
||||
"{} points at {}, which is not where the store keeps it",
|
||||
"{} points at {}, which is not in the store",
|
||||
rel.display(),
|
||||
dest.display()
|
||||
);
|
||||
}
|
||||
};
|
||||
if symlink_metadata_opt(&stored)?.is_none() {
|
||||
bail!("{} is missing from the store", rel.display());
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
|
||||
use super::store::{Leads, Repo, leads, resolve, symlink_metadata_opt};
|
||||
use super::store::{Leads, Repo, held, leads, resolve, symlink_metadata_opt};
|
||||
use crate::cli::link as cli;
|
||||
use crate::cmd::{Cmd, LsFiles};
|
||||
use crate::ctx::Ctx;
|
||||
@@ -179,10 +179,10 @@ fn classify(repo: &Repo, rel: &Path, mark: char) -> Result<Option<Exposed>> {
|
||||
}
|
||||
|
||||
let dest = read_link(&src)?;
|
||||
if dest == repo.store.join(rel) {
|
||||
if held(repo, &src, &dest).is_some() {
|
||||
// it names the store, but what the store holds there can be a symlink of
|
||||
// its own leading straight back out, which is not being held at all
|
||||
return Ok(match leads(&src, &repo.store) {
|
||||
return Ok(match leads(&src, &repo.base) {
|
||||
Leads::Inside | Leads::Dangling => None,
|
||||
Leads::Outside(end) => Some(Exposed {
|
||||
mark,
|
||||
|
||||
@@ -289,6 +289,19 @@ pub(super) fn symlink_metadata_opt(path: &Path) -> Result<Option<std::fs::Metada
|
||||
}
|
||||
}
|
||||
|
||||
// where a link into the store points, if it is one: absolute, under the base.
|
||||
// the directory need not be this checkout's own, since a moved checkout or an
|
||||
// older layout still holds the file
|
||||
pub(super) fn held(repo: &Repo, link: &Path, dest: &Path) -> Option<PathBuf> {
|
||||
let dest = match dest.is_absolute() {
|
||||
true => dest.to_path_buf(),
|
||||
false => link.parent()?.join(dest),
|
||||
};
|
||||
let dest = normalized(dest).ok()?;
|
||||
|
||||
dest.starts_with(&repo.base).then_some(dest)
|
||||
}
|
||||
|
||||
// where a chain of symlinks actually ends up
|
||||
pub(super) enum Leads {
|
||||
// somewhere under the directory it was supposed to stay in
|
||||
|
||||
Reference in New Issue
Block a user