Skip to content

Commit

Permalink
refactor (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Feb 27, 2022
1 parent 5849d5b commit 6946300
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 173 deletions.
171 changes: 171 additions & 0 deletions git-worktree/src/index.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use git_hash::oid;

pub mod checkout {
use bstr::BString;
use quick_error::quick_error;

#[derive(Clone, Copy)]
pub struct Options {
pub symlinks: bool,
}

impl Default for Options {
fn default() -> Self {
Options { symlinks: true }
}
}

quick_error! {
#[derive(Debug)]
pub enum Error {
IllformedUtf8{ path: BString } {
display("Could not convert path to UTF8: {}", path)
}
Time(err: std::time::SystemTimeError) {
from()
source(err)
display("The clock was off when reading file related metadata after updating a file on disk")
}
Io(err: std::io::Error) {
from()
source(err)
display("IO error while writing blob or reading file metadata or changing filetype")
}
ObjectNotFound{ oid: git_hash::ObjectId, path: std::path::PathBuf } {
display("object {} for checkout at {} not found in object database", oid.to_hex(), path.display())
}
}
}
}

pub fn checkout<Find>(
index: &mut git_index::State,
path: impl AsRef<std::path::Path>,
mut find: Find,
options: checkout::Options,
) -> Result<(), checkout::Error>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
{
let root = path.as_ref();
let mut buf = Vec::new();
for (entry, entry_path) in index.entries_mut_with_paths() {
// TODO: write test for that
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
continue;
}

entry::checkout(entry, entry_path, &mut find, root, options, &mut buf)?;
}
Ok(())
}

pub(crate) mod entry {
use std::{
convert::TryInto,
fs::{create_dir_all, OpenOptions},
io::Write,
time::Duration,
};

use bstr::BStr;
use git_hash::oid;
use git_index::Entry;

use crate::index;

pub fn checkout<Find>(
entry: &mut Entry,
entry_path: &BStr,
find: &mut Find,
root: &std::path::Path,
index::checkout::Options { symlinks }: index::checkout::Options,
buf: &mut Vec<u8>,
) -> Result<(), index::checkout::Error>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
{
let dest = root.join(git_features::path::from_byte_slice(entry_path).map_err(|_| {
index::checkout::Error::IllformedUtf8 {
path: entry_path.to_owned(),
}
})?);
create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?

match entry.mode {
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
oid: entry.id,
path: root.to_path_buf(),
})?;
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
if entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o777);
}
let mut file = options.open(&dest)?;
file.write_all(obj.data)?;
let met = file.metadata()?;
let ctime = met
.created()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
let mtime = met
.modified()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;

update_fstat(entry, ctime, mtime)?;
}
git_index::entry::Mode::SYMLINK => {
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
oid: entry.id,
path: root.to_path_buf(),
})?;
let symlink_destination = git_features::path::from_byte_slice(obj.data)
.map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;
if symlinks {
#[cfg(unix)]
std::os::unix::fs::symlink(symlink_destination, &dest)?;
#[cfg(windows)]
if dest.exists() {
if dest.is_file() {
std::os::windows::fs::symlink_file(symlink_destination, &dest)?;
} else {
std::os::windows::fs::symlink_dir(symlink_destination, &dest)?;
}
}
} else {
std::fs::write(&dest, obj.data)?;
}
let met = std::fs::symlink_metadata(&dest)?;
let ctime = met
.created()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
let mtime = met
.modified()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
update_fstat(entry, ctime, mtime)?;
}
git_index::entry::Mode::DIR => todo!(),
git_index::entry::Mode::COMMIT => todo!(),
_ => unreachable!(),
}
Ok(())
}

fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), index::checkout::Error> {
let stat = &mut entry.stat;
stat.mtime.secs = mtime
.as_secs()
.try_into()
.expect("by 2038 we found a solution for this");
stat.mtime.nsecs = mtime.subsec_nanos();
stat.ctime.secs = ctime
.as_secs()
.try_into()
.expect("by 2038 we found a solution for this");
stat.ctime.nsecs = ctime.subsec_nanos();
Ok(())
}
}
174 changes: 1 addition & 173 deletions git-worktree/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,175 +1,3 @@
#![forbid(unsafe_code, rust_2018_idioms)]

pub mod index {
use git_hash::oid;

pub mod checkout {
use bstr::BString;
use quick_error::quick_error;

#[derive(Clone, Copy)]
pub struct Options {
pub symlinks: bool,
}

impl Default for Options {
fn default() -> Self {
Options { symlinks: true }
}
}

quick_error! {
#[derive(Debug)]
pub enum Error {
IllformedUtf8{ path: BString } {
display("Could not convert path to UTF8: {}", path)
}
Time(err: std::time::SystemTimeError) {
from()
source(err)
display("The clock was off when reading file related metadata after updating a file on disk")
}
Io(err: std::io::Error) {
from()
source(err)
display("IO error while writing blob or reading file metadata or changing filetype")
}
ObjectNotFound{ oid: git_hash::ObjectId, path: std::path::PathBuf } {
display("object {} for checkout at {} not found in object database", oid.to_hex(), path.display())
}
}
}
}

pub fn checkout<Find>(
index: &mut git_index::State,
path: impl AsRef<std::path::Path>,
mut find: Find,
options: checkout::Options,
) -> Result<(), checkout::Error>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
{
let root = path.as_ref();
let mut buf = Vec::new();
for (entry, entry_path) in index.entries_mut_with_paths() {
// TODO: write test for that
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
continue;
}

entry::checkout(entry, entry_path, &mut find, root, options, &mut buf)?;
}
Ok(())
}

pub(crate) mod entry {
use std::{
convert::TryInto,
fs::{create_dir_all, OpenOptions},
io::Write,
time::Duration,
};

use bstr::BStr;
use git_hash::oid;
use git_index::Entry;

use crate::index;

pub fn checkout<Find>(
entry: &mut Entry,
entry_path: &BStr,
find: &mut Find,
root: &std::path::Path,
index::checkout::Options { symlinks }: index::checkout::Options,
buf: &mut Vec<u8>,
) -> Result<(), index::checkout::Error>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
{
let dest = root.join(git_features::path::from_byte_slice(entry_path).map_err(|_| {
index::checkout::Error::IllformedUtf8 {
path: entry_path.to_owned(),
}
})?);
create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?

match entry.mode {
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
oid: entry.id,
path: root.to_path_buf(),
})?;
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
if entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o777);
}
let mut file = options.open(&dest)?;
file.write_all(obj.data)?;
let met = file.metadata()?;
let ctime = met
.created()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
let mtime = met
.modified()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;

update_fstat(entry, ctime, mtime)?;
}
git_index::entry::Mode::SYMLINK => {
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
oid: entry.id,
path: root.to_path_buf(),
})?;
let symlink_destination = git_features::path::from_byte_slice(obj.data)
.map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;
if symlinks {
#[cfg(unix)]
std::os::unix::fs::symlink(symlink_destination, &dest)?;
#[cfg(windows)]
if dest.exists() {
if dest.is_file() {
std::os::windows::fs::symlink_file(symlink_destination, &dest)?;
} else {
std::os::windows::fs::symlink_dir(symlink_destination, &dest)?;
}
}
} else {
std::fs::write(&dest, obj.data)?;
}
let met = std::fs::symlink_metadata(&dest)?;
let ctime = met
.created()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
let mtime = met
.modified()
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
update_fstat(entry, ctime, mtime)?;
}
git_index::entry::Mode::DIR => todo!(),
git_index::entry::Mode::COMMIT => todo!(),
_ => unreachable!(),
}
Ok(())
}

fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), index::checkout::Error> {
let stat = &mut entry.stat;
stat.mtime.secs = mtime
.as_secs()
.try_into()
.expect("by 2038 we found a solution for this");
stat.mtime.nsecs = mtime.subsec_nanos();
stat.ctime.secs = ctime
.as_secs()
.try_into()
.expect("by 2038 we found a solution for this");
stat.ctime.nsecs = ctime.subsec_nanos();
Ok(())
}
}
}
pub mod index;

0 comments on commit 6946300

Please sign in to comment.