Skip to content

Commit

Permalink
strucural refactor (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Feb 7, 2022
1 parent d0c4563 commit cdca1df
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 121 deletions.
4 changes: 0 additions & 4 deletions git-index/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ mod access {
(e, path)
})
}

pub fn entries_mut(&mut self) -> &mut [Entry] {
&mut self.entries
}
}
}

Expand Down
253 changes: 141 additions & 112 deletions git-worktree/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,133 +1,162 @@
#![forbid(unsafe_code, rust_2018_idioms)]

use git_hash::oid;
use git_index::Entry;
use git_object::bstr::ByteSlice;
use quick_error::quick_error;
use std::convert::TryInto;
use std::fs::{create_dir_all, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub mod index {
use git_hash::oid;

quick_error! {
#[derive(Debug)]
pub enum Error {
Utf8(err: git_object::bstr::Utf8Error) {
from()
display("Could not convert path to UTF8: {}", err)
}
Time(err: std::time::SystemTimeError) {
from()
display("Could not read file time in proper format: {}", err)
}
U32Conversion(err: std::num::TryFromIntError) {
from()
display("Could not convert seconds to u32: {}", err)
pub mod checkout {
use quick_error::quick_error;

#[derive(Clone, Copy)]
pub struct Options {
pub symlinks: bool,
}
Io(err: std::io::Error) {
from()
display("IO error while writing blob or reading file metadata or changing filetype: {}", err)

impl Default for Options {
fn default() -> Self {
Options { symlinks: true }
}
}
NotFound(oid: git_hash::ObjectId, path: PathBuf) {
display("unable find object of {} ({})", path.display(), oid.to_hex())

quick_error! {
#[derive(Debug)]
pub enum Error {
PathToUtf8(err: git_object::bstr::Utf8Error) {
from()
source(err)
display("Could not convert path to UTF8")
}
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())
}
}
}
}
}

/// Copy index to `path`
pub fn copy_index<Find>(
index: &mut git_index::State,
path: impl AsRef<Path>,
mut find: Find,
opts: Options,
) -> Result<(), Error>
where
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
{
let path = path.as_ref();
let mut buf = Vec::new();
for (entry, entry_path) in index.entries_mut_with_paths() {
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
continue;
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() {
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
continue;
}

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

let dest = path.join(entry_path.to_path()?); // TODO: try to use os_str_bytes to avoid UTF8 conversion. Put that into git-ref too
create_dir_all(dest.parent().expect("entry paths are never empty"))?;
pub(crate) mod entry {
use crate::index;
use git_hash::oid;
use git_index::Entry;
use git_object::bstr::{BStr, ByteSlice};
use std::convert::TryInto;
use std::fs::{create_dir_all, OpenOptions};
use std::io::Write;
use std::time::Duration;

match entry.mode {
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
let obj = find(&entry.id, &mut buf).ok_or_else(|| Error::NotFound(entry.id, path.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))?;
pub fn checkout<Find>(
entry: &mut Entry,
entry_path: &BStr,
find: &mut Find,
root: &std::path::Path,
index::checkout::Options { symlinks }: index::checkout::Options,
mut 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(entry_path.to_path()?); // TODO: try to use os_str_bytes to avoid UTF8 conversion. Put that into git-ref too
create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?

update_fstat(entry, ctime, mtime)?;
}
git_index::entry::Mode::SYMLINK => {
let obj = find(&entry.id, &mut buf).ok_or_else(|| Error::NotFound(entry.id, path.to_path_buf()))?;
let linked_to = obj.data.to_path()?;
if opts.symlinks {
match entry.mode {
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
let obj = find(&entry.id, &mut buf)
.ok_or_else(|| index::checkout::Error::ObjectNotFound(entry.id, root.to_path_buf()))?;
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
std::os::unix::fs::symlink(linked_to, &dest)?;
#[cfg(windows)]
if dest.exists() {
if dest.is_file() {
std::os::windows::fs::symlink_file(linked_to, &dest)?;
} else {
std::os::windows::fs::symlink_dir(linked_to, &dest)?;
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, &mut buf)
.ok_or_else(|| index::checkout::Error::ObjectNotFound(entry.id, root.to_path_buf()))?;
let symlink_destination = obj.data.to_path()?;
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)?;
}
} 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)?;
}
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!(),
}
git_index::entry::Mode::DIR => todo!(),
git_index::entry::Mode::COMMIT => todo!(),
_ => unreachable!(),
Ok(())
}
}
Ok(())
}

fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), Error> {
let stat = &mut entry.stat;
stat.mtime.secs = mtime.as_secs().try_into()?;
stat.mtime.nsecs = mtime.subsec_nanos();
stat.ctime.secs = ctime.as_secs().try_into()?;
stat.ctime.nsecs = ctime.subsec_nanos();
Ok(())
}

/// Options for [copy_index](crate::copy_index)
pub struct Options {
/// Enable/disable symlinks
pub symlinks: bool,
}

impl Default for Options {
fn default() -> Self {
Options { symlinks: true }
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(())
}
}
}
13 changes: 8 additions & 5 deletions git-worktree/tests/copy_index/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{dir_structure, fixture_path};
use git_object::bstr::ByteSlice;
use git_odb::FindExt;
use git_worktree::{copy_index, Options};
use git_worktree::index;
use std::fs;

#[cfg(unix)]
Expand All @@ -16,11 +16,11 @@ fn test_copy_index() -> crate::Result<()> {
let output = output_dir.path();
let odb_handle = git_odb::at(path_git.join("objects"))?;

copy_index(
index::checkout(
&mut file,
&output,
move |oid, buf| odb_handle.find_blob(oid, buf).ok(),
Options::default(),
index::checkout::Options::default(),
)?;

let repo_files = dir_structure(&path);
Expand Down Expand Up @@ -61,11 +61,14 @@ fn test_copy_index_without_symlinks() -> crate::Result<()> {
let output = output_dir.path();
let odb_handle = git_odb::at(path_git.join("objects"))?;

copy_index(
index::checkout(
&mut file,
&output,
move |oid, buf| odb_handle.find_blob(oid, buf).ok(),
Options { symlinks: false },
index::checkout::Options {
symlinks: false,
..Default::default()
},
)?;

let repo_files = dir_structure(&path);
Expand Down

0 comments on commit cdca1df

Please sign in to comment.