-
-
Notifications
You must be signed in to change notification settings - Fork 321
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
172 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |