Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

status support in gix (crate) #1049

Merged
merged 7 commits into from
Nov 11, 2023
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
feat!: Add git-style metadata support.
As opposed to the Rust standard library, this one will get the
ctime from the file itself, instead of from the inode.

That way, the index file written by `gix` will not continuously
be expensively rewritten by `git`, and vice versa.
Byron committed Nov 11, 2023
commit 3c8421f003bc3a5f2f51cee1b5cb6a526d5e0f38
12 changes: 7 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions gix-index/Cargo.toml
Original file line number Diff line number Diff line change
@@ -41,6 +41,10 @@ bitflags = "2"

document-features = { version = "0.2.0", optional = true }

[target.'cfg(not(windows))'.dependencies]
rustix = { version = "0.38.20", default-features = false, features = ["std", "fs"] }
libc = { version = "0.2.149" }

[package.metadata.docs.rs]
features = ["document-features", "serde"]
rustdoc-args = ["--cfg", "docsrs"]
10 changes: 4 additions & 6 deletions gix-index/src/entry/mode.rs
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ impl Mode {
/// can not be committed to git).
pub fn change_to_match_fs(
self,
stat: &std::fs::Metadata,
stat: &crate::fs::Metadata,
has_symlinks: bool,
executable_bit: bool,
) -> Option<Change> {
@@ -46,15 +46,13 @@ impl Mode {
Mode::SYMLINK if has_symlinks && !stat.is_symlink() => (),
Mode::SYMLINK if !has_symlinks && !stat.is_file() => (),
Mode::COMMIT | Mode::DIR if !stat.is_dir() => (),
Mode::FILE if executable_bit && gix_fs::is_executable(stat) => return Some(Change::ExecutableBit),
Mode::FILE_EXECUTABLE if executable_bit && !gix_fs::is_executable(stat) => {
return Some(Change::ExecutableBit)
}
Mode::FILE if executable_bit && stat.is_executable() => return Some(Change::ExecutableBit),
Mode::FILE_EXECUTABLE if executable_bit && !stat.is_executable() => return Some(Change::ExecutableBit),
_ => return None,
};
let new_mode = if stat.is_dir() {
Mode::COMMIT
} else if executable_bit && gix_fs::is_executable(stat) {
} else if executable_bit && stat.is_executable() {
Mode::FILE_EXECUTABLE
} else {
Mode::FILE
23 changes: 11 additions & 12 deletions gix-index/src/entry/stat.rs
Original file line number Diff line number Diff line change
@@ -76,11 +76,11 @@ impl Stat {
}

/// Creates stat information from the result of `symlink_metadata`.
pub fn from_fs(fstat: &std::fs::Metadata) -> Result<Stat, SystemTimeError> {
let mtime = fstat.modified().unwrap_or(std::time::UNIX_EPOCH);
let ctime = fstat.created().unwrap_or(std::time::UNIX_EPOCH);
pub fn from_fs(stat: &crate::fs::Metadata) -> Result<Stat, SystemTimeError> {
let mtime = stat.modified().unwrap_or(std::time::UNIX_EPOCH);
let ctime = stat.created().unwrap_or(std::time::UNIX_EPOCH);

#[cfg(not(unix))]
#[cfg(windows)]
let res = Stat {
mtime: mtime.try_into()?,
ctime: ctime.try_into()?,
@@ -89,24 +89,23 @@ impl Stat {
uid: 0,
gid: 0,
// truncation to 32 bits is on purpose (git does the same).
size: fstat.len() as u32,
size: stat.len() as u32,
};
#[cfg(unix)]
#[cfg(not(windows))]
let res = {
use std::os::unix::fs::MetadataExt;
Stat {
mtime: mtime.try_into().unwrap_or_default(),
ctime: ctime.try_into().unwrap_or_default(),
// truncating to 32 bits is fine here because
// that's what the linux syscalls returns
// just rust upcasts to 64 bits for some reason?
// numbers this large are impractical anyway (that's a lot of hard-drives).
dev: fstat.dev() as u32,
ino: fstat.ino() as u32,
uid: fstat.uid(),
gid: fstat.gid(),
dev: stat.dev() as u32,
ino: stat.ino() as u32,
uid: stat.uid(),
gid: stat.gid(),
// truncation to 32 bits is on purpose (git does the same).
size: fstat.len() as u32,
size: stat.len() as u32,
}
};

166 changes: 166 additions & 0 deletions gix-index/src/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! This module contains a `Metadata` implementation that must be used instead of `std::fs::Metadata` to assure
//! that the `ctime` information is populated exactly like the one in `git`, which wouldn't be the case on unix.
#![allow(clippy::useless_conversion)] // on some MacOOS conversions are required, but on linux usually not.
#![allow(clippy::unnecessary_cast)]

// it's allowed for good measure, in case there are systems that use different types for that.
use std::path::Path;
use std::time::{Duration, SystemTime};

/// A structure to partially mirror [`std::fs::Metadata`].
#[cfg(not(windows))]
pub struct Metadata(rustix::fs::Stat);

#[cfg(windows)]
/// A structure to partially mirror [`std::fs::Metadata`].
pub struct Metadata(std::fs::Metadata);

/// Lifecycle
impl Metadata {
/// Obtain the metadata at `path` without following symlinks.
pub fn from_path_no_follow(path: &Path) -> Result<Self, std::io::Error> {
#[cfg(not(windows))]
{
rustix::fs::lstat(path).map(Metadata).map_err(Into::into)
}
#[cfg(windows)]
path.symlink_metadata().map(Metadata)
}

/// Obtain the metadata at `path` without following symlinks.
pub fn from_file(file: &std::fs::File) -> Result<Self, std::io::Error> {
#[cfg(not(windows))]
{
rustix::fs::fstat(file).map(Metadata).map_err(Into::into)
}
#[cfg(windows)]
file.metadata().map(Metadata)
}
}

/// Access
#[allow(clippy::len_without_is_empty)]
impl Metadata {
/// Return true if the metadata belongs to a directory
pub fn is_dir(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode & libc::S_IFMT) == libc::S_IFDIR
}
#[cfg(windows)]
self.0.is_dir()
}

/// Return the time at which the underlying file was modified.
pub fn modified(&self) -> Option<SystemTime> {
#[cfg(not(windows))]
{
Some(system_time_from_secs_nanos(
self.0.st_mtime.try_into().ok()?,
self.0.st_mtime_nsec.try_into().ok()?,
))
}
#[cfg(windows)]
self.0.modified().ok()
}

/// Return the time at which the underlying file was created.
///
/// Note that this differes from [`std::fs::Metadata::created()`] which would return
/// the inode birth time, which is notably different to what `git` does.
pub fn created(&self) -> Option<SystemTime> {
#[cfg(not(windows))]
{
Some(system_time_from_secs_nanos(
self.0.st_ctime.try_into().ok()?,
self.0.st_ctime_nsec.try_into().ok()?,
))
}
#[cfg(windows)]
self.0.created().ok()
}

/// Return the size of the file in bytes.
pub fn len(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_size as u64
}
#[cfg(windows)]
self.0.len()
}

/// Return the device id on which the file is located, or 0 on windows.
pub fn dev(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_dev as u64
}
#[cfg(windows)]
0
}

/// Return the inode id tracking the file, or 0 on windows.
pub fn ino(&self) -> u64 {
#[cfg(not(windows))]
{
self.0.st_ino as u64
}
#[cfg(windows)]
0
}

/// Return the user-id of the file or 0 on windows.
pub fn uid(&self) -> u32 {
#[cfg(not(windows))]
{
self.0.st_uid as u32
}
#[cfg(windows)]
0
}

/// Return the group-id of the file or 0 on windows.
pub fn gid(&self) -> u32 {
#[cfg(not(windows))]
{
self.0.st_gid as u32
}
#[cfg(windows)]
0
}

/// Return `true` if the file's executable bit is set, or `false` on windows.
pub fn is_executable(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode & libc::S_IFMT) == libc::S_IFREG && self.0.st_mode & libc::S_IXUSR == libc::S_IXUSR
}
#[cfg(windows)]
gix_fs::is_executable(&self.0)
}

/// Return `true` if the file's is a symbolic link.
pub fn is_symlink(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode & libc::S_IFMT) == libc::S_IFLNK
}
#[cfg(windows)]
self.0.is_symlink()
}

/// Return `true` if this is a regular file, executable or not.
pub fn is_file(&self) -> bool {
#[cfg(not(windows))]
{
(self.0.st_mode & libc::S_IFMT) == libc::S_IFREG
}
#[cfg(windows)]
self.0.is_file()
}
}

fn system_time_from_secs_nanos(secs: u64, nanos: u32) -> SystemTime {
std::time::UNIX_EPOCH + Duration::new(secs, nanos)
}
2 changes: 2 additions & 0 deletions gix-index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -33,6 +33,8 @@ pub mod verify;
///
pub mod write;

pub mod fs;

/// All known versions of a git index file.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]