Replace lexical Rust fragments with real modules

This commit is contained in:
ms2k 2026-07-19 05:12:57 +09:00
commit 247819250b
3 changed files with 3302 additions and 2671 deletions

View file

@ -1,5 +1,12 @@
use super::{
Classification, ClassificationSnapshot, Duration, Errno, File, FileType, FlockOperation,
HashMap, INodeNo, IgnoredWorkspace, LockFlags, Mode, OFlags, OsStr, OsStrExt, OsString,
OsStringExt, Path, PathBuf, ReplyEmpty, ReplyXattr, SystemTime, TimeOrNow, Timespec,
UNIX_EPOCH, UTIME_NOW, UTIME_OMIT, io, tracked_paths_from_index,
};
impl IgnoredWorkspace {
fn remove(&self, parent: INodeNo, name: &OsStr, directory: bool, reply: ReplyEmpty) {
pub(super) fn remove(&self, parent: INodeNo, name: &OsStr, directory: bool, reply: ReplyEmpty) {
let result = (|| {
let path = self.child_path(parent, name)?;
let snapshot = self.classifier.snapshot_for(&path);
@ -9,7 +16,7 @@ impl IgnoredWorkspace {
reply_result(result, reply);
}
fn with_temporary_file<T>(
pub(super) fn with_temporary_file<T>(
&self,
inode: INodeNo,
operation: impl FnOnce(&File) -> io::Result<T>,
@ -23,7 +30,7 @@ impl IgnoredWorkspace {
operation(&file)
}
fn require_index_publication_safe(
pub(super) fn require_index_publication_safe(
&self,
backing: Classification,
source: &Path,
@ -50,46 +57,46 @@ impl IgnoredWorkspace {
}
}
struct ResolvedEntry {
path: PathBuf,
backing: Classification,
stat: rustix::fs::Stat,
pub(super) struct ResolvedEntry {
pub(super) path: PathBuf,
pub(super) backing: Classification,
pub(super) stat: rustix::fs::Stat,
}
struct OpenFile {
file: File,
backing: Classification,
access_mode: i32,
pub(super) struct OpenFile {
pub(super) file: File,
pub(super) backing: Classification,
pub(super) access_mode: i32,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct PosixLockKey {
backing: Classification,
device: u64,
inode: u64,
owner: u64,
pub(super) struct PosixLockKey {
pub(super) backing: Classification,
pub(super) device: u64,
pub(super) inode: u64,
pub(super) owner: u64,
}
struct DirectoryStream {
directory: File,
entries: Vec<DirectoryEntry>,
_snapshot: ClassificationSnapshot,
pub(super) struct DirectoryStream {
pub(super) directory: File,
pub(super) entries: Vec<DirectoryEntry>,
pub(super) _snapshot: ClassificationSnapshot,
}
struct DirectoryEntry {
inode: INodeNo,
kind: FileType,
name: OsString,
pub(super) struct DirectoryEntry {
pub(super) inode: INodeNo,
pub(super) kind: FileType,
pub(super) name: OsString,
}
struct NodeTable {
pub(super) struct NodeTable {
paths: HashMap<u64, PathBuf>,
inodes: HashMap<NodeKey, u64>,
next_inode: u64,
}
impl NodeTable {
fn new() -> Self {
pub(super) fn new() -> Self {
Self {
paths: HashMap::from([(INodeNo::ROOT.0, PathBuf::new())]),
inodes: HashMap::new(),
@ -97,11 +104,11 @@ impl NodeTable {
}
}
fn path(&self, inode: INodeNo) -> Option<PathBuf> {
pub(super) fn path(&self, inode: INodeNo) -> Option<PathBuf> {
self.paths.get(&inode.0).cloned()
}
fn inode(&mut self, entry: &ResolvedEntry) -> INodeNo {
pub(super) fn inode(&mut self, entry: &ResolvedEntry) -> INodeNo {
let key = NodeKey {
path: entry.path.clone(),
backing: entry.backing,
@ -127,13 +134,13 @@ struct NodeKey {
inode: u64,
}
fn open_flags(flags: i32) -> OFlags {
pub(super) fn open_flags(flags: i32) -> OFlags {
let mut result = OFlags::from_bits_retain(u32::from_ne_bytes(flags.to_ne_bytes()));
result.remove(OFlags::CREATE | OFlags::EXCL | OFlags::NOFOLLOW | OFlags::CLOEXEC);
result
}
fn file_type(mode: u32) -> io::Result<FileType> {
pub(super) fn file_type(mode: u32) -> io::Result<FileType> {
match mode & libc::S_IFMT {
libc::S_IFREG => Ok(FileType::RegularFile),
libc::S_IFDIR => Ok(FileType::Directory),
@ -146,7 +153,7 @@ fn file_type(mode: u32) -> io::Result<FileType> {
}
}
fn system_time(seconds: i64, nanoseconds: u64) -> SystemTime {
pub(super) fn system_time(seconds: i64, nanoseconds: u64) -> SystemTime {
if seconds >= 0 {
UNIX_EPOCH
+ Duration::new(
@ -158,7 +165,7 @@ fn system_time(seconds: i64, nanoseconds: u64) -> SystemTime {
}
}
fn timestamp(value: Option<TimeOrNow>) -> Timespec {
pub(super) fn timestamp(value: Option<TimeOrNow>) -> Timespec {
match value {
Some(TimeOrNow::Now) => Timespec {
tv_sec: 0,
@ -178,18 +185,18 @@ fn timestamp(value: Option<TimeOrNow>) -> Timespec {
}
}
fn writable_xattr(name: &OsStr) -> bool {
pub(super) fn writable_xattr(name: &OsStr) -> bool {
matches!(
name.as_bytes(),
b"system.posix_acl_access" | b"system.posix_acl_default"
) || name.as_bytes().starts_with(b"user.")
}
fn unsupported_lock_flags(flags: LockFlags) -> bool {
pub(super) fn unsupported_lock_flags(flags: LockFlags) -> bool {
flags.bits() & !LockFlags::FUSE_LK_FLOCK.bits() != 0
}
fn bsd_lock_operation(typ: i32, sleep: bool) -> io::Result<FlockOperation> {
pub(super) fn bsd_lock_operation(typ: i32, sleep: bool) -> io::Result<FlockOperation> {
match (typ, sleep) {
(libc::F_RDLCK, true) => Ok(FlockOperation::LockShared),
(libc::F_RDLCK, false) => Ok(FlockOperation::NonBlockingLockShared),
@ -201,7 +208,7 @@ fn bsd_lock_operation(typ: i32, sleep: bool) -> io::Result<FlockOperation> {
}
}
fn record_lock(start: u64, end: u64, typ: i32) -> io::Result<libc::flock> {
pub(super) fn record_lock(start: u64, end: u64, typ: i32) -> io::Result<libc::flock> {
if start > end {
return Err(io::Error::from_raw_os_error(libc::EINVAL));
}
@ -235,7 +242,7 @@ fn record_lock(start: u64, end: u64, typ: i32) -> io::Result<libc::flock> {
})
}
fn fuse_lock(lock: &libc::flock) -> io::Result<(u64, u64, i32, u32)> {
pub(super) fn fuse_lock(lock: &libc::flock) -> io::Result<(u64, u64, i32, u32)> {
let start =
u64::try_from(lock.l_start).map_err(|_| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
let end = if lock.l_len == 0 {
@ -255,18 +262,18 @@ fn fuse_lock(lock: &libc::flock) -> io::Result<(u64, u64, i32, u32)> {
))
}
fn errno(error: &io::Error) -> Errno {
pub(super) fn errno(error: &io::Error) -> Errno {
Errno::from_i32(error.raw_os_error().unwrap_or(libc::EIO))
}
fn reply_result(result: io::Result<()>, reply: ReplyEmpty) {
pub(super) fn reply_result(result: io::Result<()>, reply: ReplyEmpty) {
match result {
Ok(()) => reply.ok(),
Err(error) => reply.error(errno(&error)),
}
}
fn reply_xattr(result: io::Result<Vec<u8>>, size: u32, reply: ReplyXattr) {
pub(super) fn reply_xattr(result: io::Result<Vec<u8>>, size: u32, reply: ReplyXattr) {
match result {
Ok(bytes) if size == 0 => reply.size(u32::try_from(bytes.len()).unwrap_or(u32::MAX)),
Ok(bytes) if bytes.len() <= size as usize => reply.data(&bytes),
@ -274,4 +281,3 @@ fn reply_xattr(result: io::Result<Vec<u8>>, size: u32, reply: ReplyXattr) {
Err(error) => reply.error(errno(&error)),
}
}

View file

@ -1,3 +1,19 @@
use super::nodes::{
DirectoryStream, OpenFile, ResolvedEntry, bsd_lock_operation, errno, fuse_lock, record_lock,
reply_result, reply_xattr, timestamp, unsupported_lock_flags, writable_xattr,
};
use super::{
Arc, AsFd, BsdFileFlags, CopyFileRangeFlags, DirectoryEntry, Errno, FallocateFlags, FcntlArg,
FileAttr, FileExt, FileHandle, FileType, Filesystem, FopenFlags, FuseRenameFlags, Generation,
Gid, INodeNo, IgnoredWorkspace, InitFlags, KernelConfig, LockFlags, LockOwner, Mode, OFlags,
OpenFlags, Ordering, OsStr, OsString, Path, RenameFlags, ReplyAttr, ReplyCreate, ReplyData,
ReplyDirectory, ReplyEmpty, ReplyEntry, ReplyLock, ReplyLseek, ReplyOpen, ReplyStatfs,
ReplyWrite, ReplyXattr, Request, SeekFrom, SystemTime, TTL, TimeOrNow, Timestamps, Uid,
WriteFlags, XattrFlags, bsd_flock, copy_file_range, fallocate, fchmod, fchown, fcntl,
fgetxattr, flistxattr, fremovexattr, fsetxattr, fstat, fstatvfs, ftruncate, futimens, io,
mknodat, open_flags, seek,
};
impl Filesystem for IgnoredWorkspace {
fn init(&mut self, _request: &Request, config: &mut KernelConfig) -> io::Result<()> {
config
@ -857,4 +873,3 @@ impl Filesystem for IgnoredWorkspace {
}
}
}

View file

@ -1,264 +1,259 @@
#[cfg(test)]
mod tests {
use std::fs::{self, OpenOptions};
use std::io::{Read, Seek, Write};
use std::os::fd::{AsFd, AsRawFd};
use std::sync::atomic::{AtomicU64, Ordering};
use std::fs::{self, OpenOptions};
use std::io::{Read, Seek, Write};
use std::os::fd::{AsFd, AsRawFd};
use std::sync::atomic::{AtomicU64, Ordering};
use fuser::{Config, LockOwner, MountOption};
use nix::fcntl::{FcntlArg, fcntl};
use fuser::{Config, LockOwner, MountOption};
use nix::fcntl::{FcntlArg, fcntl};
use super::{Classification, IgnoredWorkspace, OpenFile, fuse_lock, record_lock};
use super::nodes::{OpenFile, fuse_lock, record_lock};
use super::{Classification, IgnoredWorkspace};
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
static SEQUENCE: AtomicU64 = AtomicU64::new(0);
#[test]
fn posix_lock_owners_use_distinct_backing_lock_domains() {
let fixture = Fixture::new();
repository(&fixture.workspace);
let path = fixture.workspace.join("ordinary");
fs::write(&path, b"lock").unwrap();
let filesystem =
IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let open = OpenFile {
file: OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap(),
backing: Classification::Host,
access_mode: libc::O_RDWR,
};
let first_owner = LockOwner(101);
let second_owner = LockOwner(202);
let first = filesystem.posix_lock_file(&open, first_owner).unwrap();
let second = filesystem.posix_lock_file(&open, second_owner).unwrap();
assert_ne!(first.as_raw_fd(), second.as_raw_fd());
#[test]
fn posix_lock_owners_use_distinct_backing_lock_domains() {
let fixture = Fixture::new();
repository(&fixture.workspace);
let path = fixture.workspace.join("ordinary");
fs::write(&path, b"lock").unwrap();
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let open = OpenFile {
file: OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap(),
backing: Classification::Host,
access_mode: libc::O_RDWR,
};
let first_owner = LockOwner(101);
let second_owner = LockOwner(202);
let first = filesystem.posix_lock_file(&open, first_owner).unwrap();
let second = filesystem.posix_lock_file(&open, second_owner).unwrap();
assert_ne!(first.as_raw_fd(), second.as_raw_fd());
let lock = record_lock(0, 0, libc::F_WRLCK).unwrap();
fcntl(first.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
let error = fcntl(second.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err();
assert_eq!(error, nix::errno::Errno::EAGAIN);
let lock = record_lock(0, 0, libc::F_WRLCK).unwrap();
fcntl(first.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
let error = fcntl(second.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err();
assert_eq!(error, nix::errno::Errno::EAGAIN);
drop(first);
filesystem.release_posix_locks(&open, first_owner).unwrap();
fcntl(second.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
drop(first);
filesystem.release_posix_locks(&open, first_owner).unwrap();
fcntl(second.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
}
#[test]
fn record_lock_ranges_round_trip() {
for (start, end) in [(0, 0), (7, 19), (11, u64::MAX)] {
let lock = record_lock(start, end, libc::F_RDLCK).unwrap();
assert_eq!(fuse_lock(&lock).unwrap(), (start, end, libc::F_RDLCK, 0));
}
}
#[test]
fn record_lock_ranges_round_trip() {
for (start, end) in [(0, 0), (7, 19), (11, u64::MAX)] {
let lock = record_lock(start, end, libc::F_RDLCK).unwrap();
assert_eq!(fuse_lock(&lock).unwrap(), (start, end, libc::F_RDLCK, 0));
}
}
#[test]
fn disabled_isolation_routes_ignored_paths_to_the_host_backing() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
let filesystem = IgnoredWorkspace::from_authorities_with_policy(
fixture.workspace.clone(),
fs::File::open(&fixture.workspace).unwrap(),
fs::File::open(&fixture.isolated).unwrap(),
None,
false,
)
.unwrap();
let path = std::path::Path::new("ignored/item");
let snapshot = filesystem.classifier.snapshot_for(path);
assert_eq!(
filesystem.classify(&snapshot, path, false),
Classification::Host
);
}
#[test]
fn disabled_isolation_routes_ignored_paths_to_the_host_backing() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
let filesystem = IgnoredWorkspace::from_authorities_with_policy(
fixture.workspace.clone(),
fs::File::open(&fixture.workspace).unwrap(),
fs::File::open(&fixture.isolated).unwrap(),
None,
false,
)
#[test]
#[ignore = "requires a usable host FUSE device"]
fn physical_router_preserves_lock_domains_across_transition() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
fs::create_dir(fixture.workspace.join("ignored")).unwrap();
fs::write(fixture.workspace.join("ignored/locked"), b"host").unwrap();
fs::create_dir(fixture.isolated.join("ignored")).unwrap();
fs::write(fixture.isolated.join("ignored/locked"), b"isolated").unwrap();
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let mut config = Config::default();
config.mount_options.extend([
MountOption::RW,
MountOption::DefaultPermissions,
MountOption::NoDev,
MountOption::NoSuid,
]);
let session = fuser::spawn_mount2(filesystem, &fixture.mountpoint, &config).unwrap();
let mounted_path = fixture.mountpoint.join("ignored/locked");
let old = OpenOptions::new()
.read(true)
.write(true)
.open(&mounted_path)
.unwrap();
let path = std::path::Path::new("ignored/item");
let snapshot = filesystem.classifier.snapshot_for(path);
assert_eq!(
filesystem.classify(&snapshot, path, false),
Classification::Host
);
}
let lock = record_lock(0, 0, libc::F_WRLCK).unwrap();
fcntl(old.as_fd(), FcntlArg::F_SETLK(&lock)).unwrap();
#[test]
#[ignore = "requires a usable host FUSE device"]
fn physical_router_preserves_lock_domains_across_transition() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
fs::create_dir(fixture.workspace.join("ignored")).unwrap();
fs::write(fixture.workspace.join("ignored/locked"), b"host").unwrap();
fs::create_dir(fixture.isolated.join("ignored")).unwrap();
fs::write(fixture.isolated.join("ignored/locked"), b"isolated").unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"different/\n").unwrap();
let new = OpenOptions::new()
.read(true)
.write(true)
.open(&mounted_path)
.unwrap();
assert_eq!(fs::read(&mounted_path).unwrap(), b"host");
let filesystem =
IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let mut config = Config::default();
config.mount_options.extend([
MountOption::RW,
MountOption::DefaultPermissions,
MountOption::NoDev,
MountOption::NoSuid,
]);
let session = fuser::spawn_mount2(filesystem, &fixture.mountpoint, &config).unwrap();
let isolated = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.isolated.join("ignored/locked"))
.unwrap();
assert_eq!(
fcntl(isolated.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err(),
nix::errno::Errno::EAGAIN
);
let host = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.workspace.join("ignored/locked"))
.unwrap();
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
let unlock = record_lock(0, 0, libc::F_UNLCK).unwrap();
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&unlock)).unwrap();
let mounted_path = fixture.mountpoint.join("ignored/locked");
let old = OpenOptions::new()
.read(true)
.write(true)
.open(&mounted_path)
.unwrap();
let lock = record_lock(0, 0, libc::F_WRLCK).unwrap();
fcntl(old.as_fd(), FcntlArg::F_SETLK(&lock)).unwrap();
fcntl(new.as_fd(), FcntlArg::F_SETLK(&lock)).unwrap();
assert_eq!(
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err(),
nix::errno::Errno::EAGAIN
);
drop(old);
fcntl(isolated.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
drop(new);
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"different/\n").unwrap();
let new = OpenOptions::new()
.read(true)
.write(true)
.open(&mounted_path)
.unwrap();
assert_eq!(fs::read(&mounted_path).unwrap(), b"host");
session.umount_and_join().unwrap();
}
let isolated = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.isolated.join("ignored/locked"))
.unwrap();
assert_eq!(
fcntl(isolated.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err(),
nix::errno::Errno::EAGAIN
);
let host = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.workspace.join("ignored/locked"))
.unwrap();
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
let unlock = record_lock(0, 0, libc::F_UNLCK).unwrap();
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&unlock)).unwrap();
#[test]
#[ignore = "requires a usable host FUSE device"]
fn physical_router_preserves_routes_transitions_and_cross_route_atomicity() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
fs::create_dir(fixture.workspace.join("ignored")).unwrap();
fs::write(fixture.workspace.join("ignored/sentinel"), b"host").unwrap();
fs::create_dir(fixture.isolated.join("ignored")).unwrap();
fs::write(fixture.isolated.join("ignored/sentinel"), b"isolated").unwrap();
fcntl(new.as_fd(), FcntlArg::F_SETLK(&lock)).unwrap();
assert_eq!(
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap_err(),
nix::errno::Errno::EAGAIN
);
drop(old);
fcntl(isolated.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
drop(new);
fcntl(host.as_fd(), FcntlArg::F_OFD_SETLK(&lock)).unwrap();
let filesystem = IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let mut config = Config::default();
config.mount_options.extend([
MountOption::RW,
MountOption::DefaultPermissions,
MountOption::NoDev,
MountOption::NoSuid,
]);
let session = fuser::spawn_mount2(filesystem, &fixture.mountpoint, &config).unwrap();
session.umount_and_join().unwrap();
}
assert_eq!(
fs::read(fixture.mountpoint.join("ignored/sentinel")).unwrap(),
b"isolated"
);
fs::write(fixture.mountpoint.join("ordinary"), b"bidirectional").unwrap();
assert_eq!(
fs::read(fixture.workspace.join("ordinary")).unwrap(),
b"bidirectional"
);
#[test]
#[ignore = "requires a usable host FUSE device"]
fn physical_router_preserves_routes_transitions_and_cross_route_atomicity() {
let fixture = Fixture::new();
repository(&fixture.workspace);
fs::write(fixture.workspace.join(".gitignore"), b"ignored/\n").unwrap();
fs::create_dir(fixture.workspace.join("ignored")).unwrap();
fs::write(fixture.workspace.join("ignored/sentinel"), b"host").unwrap();
fs::create_dir(fixture.isolated.join("ignored")).unwrap();
fs::write(fixture.isolated.join("ignored/sentinel"), b"isolated").unwrap();
let mut pinned = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.mountpoint.join("ignored/sentinel"))
.unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"different/\n").unwrap();
assert_eq!(
fs::read(fixture.mountpoint.join("ignored/sentinel")).unwrap(),
b"host"
);
pinned.write_all(b"-pinned").unwrap();
pinned.flush().unwrap();
let mut pinned_bytes = Vec::new();
pinned.rewind().unwrap();
pinned.read_to_end(&mut pinned_bytes).unwrap();
assert_eq!(pinned_bytes, b"-pinnedd");
assert_eq!(
fs::read(fixture.workspace.join("ignored/sentinel")).unwrap(),
b"host"
);
let filesystem =
IgnoredWorkspace::open(&fixture.workspace, &fixture.isolated, None).unwrap();
let mut config = Config::default();
config.mount_options.extend([
MountOption::RW,
MountOption::DefaultPermissions,
MountOption::NoDev,
MountOption::NoSuid,
]);
let session = fuser::spawn_mount2(filesystem, &fixture.mountpoint, &config).unwrap();
fs::write(fixture.workspace.join("host-source"), b"source").unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"destination\n").unwrap();
let error = fs::rename(
fixture.mountpoint.join("host-source"),
fixture.mountpoint.join("destination"),
)
.unwrap_err();
assert_eq!(error.raw_os_error(), Some(libc::EXDEV));
assert_eq!(
fs::read(fixture.workspace.join("host-source")).unwrap(),
b"source"
);
assert!(!fixture.isolated.join("destination").exists());
assert_eq!(
fs::read(fixture.mountpoint.join("ignored/sentinel")).unwrap(),
b"isolated"
);
fs::write(fixture.mountpoint.join("ordinary"), b"bidirectional").unwrap();
assert_eq!(
fs::read(fixture.workspace.join("ordinary")).unwrap(),
b"bidirectional"
);
drop(pinned);
session.umount_and_join().unwrap();
}
let mut pinned = OpenOptions::new()
.read(true)
.write(true)
.open(fixture.mountpoint.join("ignored/sentinel"))
.unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"different/\n").unwrap();
assert_eq!(
fs::read(fixture.mountpoint.join("ignored/sentinel")).unwrap(),
b"host"
);
pinned.write_all(b"-pinned").unwrap();
pinned.flush().unwrap();
let mut pinned_bytes = Vec::new();
pinned.rewind().unwrap();
pinned.read_to_end(&mut pinned_bytes).unwrap();
assert_eq!(pinned_bytes, b"-pinnedd");
assert_eq!(
fs::read(fixture.workspace.join("ignored/sentinel")).unwrap(),
b"host"
);
fn repository(root: &std::path::Path) {
fs::create_dir_all(root.join(".git/info")).unwrap();
fs::create_dir_all(root.join(".git/hooks")).unwrap();
fs::write(root.join(".git/config"), b"[core]\nignoreCase = false\n").unwrap();
fs::write(root.join(".git/info/exclude"), b"").unwrap();
let mut index = b"DIRC".to_vec();
index.extend_from_slice(&2u32.to_be_bytes());
index.extend_from_slice(&0u32.to_be_bytes());
index.extend_from_slice(&[0; 20]);
fs::write(root.join(".git/index"), index).unwrap();
}
fs::write(fixture.workspace.join("host-source"), b"source").unwrap();
fs::write(fixture.workspace.join(".gitignore"), b"destination\n").unwrap();
let error = fs::rename(
fixture.mountpoint.join("host-source"),
fixture.mountpoint.join("destination"),
)
.unwrap_err();
assert_eq!(error.raw_os_error(), Some(libc::EXDEV));
assert_eq!(
fs::read(fixture.workspace.join("host-source")).unwrap(),
b"source"
);
assert!(!fixture.isolated.join("destination").exists());
struct Fixture {
root: std::path::PathBuf,
workspace: std::path::PathBuf,
isolated: std::path::PathBuf,
mountpoint: std::path::PathBuf,
}
drop(pinned);
session.umount_and_join().unwrap();
}
fn repository(root: &std::path::Path) {
fs::create_dir_all(root.join(".git/info")).unwrap();
fs::create_dir_all(root.join(".git/hooks")).unwrap();
fs::write(root.join(".git/config"), b"[core]\nignoreCase = false\n").unwrap();
fs::write(root.join(".git/info/exclude"), b"").unwrap();
let mut index = b"DIRC".to_vec();
index.extend_from_slice(&2u32.to_be_bytes());
index.extend_from_slice(&0u32.to_be_bytes());
index.extend_from_slice(&[0; 20]);
fs::write(root.join(".git/index"), index).unwrap();
}
struct Fixture {
root: std::path::PathBuf,
workspace: std::path::PathBuf,
isolated: std::path::PathBuf,
mountpoint: std::path::PathBuf,
}
impl Fixture {
fn new() -> Self {
let sequence = SEQUENCE.fetch_add(1, Ordering::Relaxed);
let root = std::env::current_dir()
.unwrap()
.join("target/ignored-workspace-tests")
.join(format!("physical-{}-{sequence}", std::process::id()));
let workspace = root.join("workspace");
let isolated = root.join("isolated");
let mountpoint = root.join("mountpoint");
fs::create_dir_all(&workspace).unwrap();
fs::create_dir(&isolated).unwrap();
fs::create_dir(&mountpoint).unwrap();
Self {
root,
workspace,
isolated,
mountpoint,
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).unwrap();
impl Fixture {
fn new() -> Self {
let sequence = SEQUENCE.fetch_add(1, Ordering::Relaxed);
let root = std::env::current_dir()
.unwrap()
.join("target/ignored-workspace-tests")
.join(format!("physical-{}-{sequence}", std::process::id()));
let workspace = root.join("workspace");
let isolated = root.join("isolated");
let mountpoint = root.join("mountpoint");
fs::create_dir_all(&workspace).unwrap();
fs::create_dir(&isolated).unwrap();
fs::create_dir(&mountpoint).unwrap();
Self {
root,
workspace,
isolated,
mountpoint,
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).unwrap();
}
}