Skip to content

Commit 6946300

Browse files
committed
refactor (#301)
1 parent 5849d5b commit 6946300

File tree

2 files changed

+172
-173
lines changed

2 files changed

+172
-173
lines changed

git-worktree/src/index.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
use git_hash::oid;
2+
3+
pub mod checkout {
4+
use bstr::BString;
5+
use quick_error::quick_error;
6+
7+
#[derive(Clone, Copy)]
8+
pub struct Options {
9+
pub symlinks: bool,
10+
}
11+
12+
impl Default for Options {
13+
fn default() -> Self {
14+
Options { symlinks: true }
15+
}
16+
}
17+
18+
quick_error! {
19+
#[derive(Debug)]
20+
pub enum Error {
21+
IllformedUtf8{ path: BString } {
22+
display("Could not convert path to UTF8: {}", path)
23+
}
24+
Time(err: std::time::SystemTimeError) {
25+
from()
26+
source(err)
27+
display("The clock was off when reading file related metadata after updating a file on disk")
28+
}
29+
Io(err: std::io::Error) {
30+
from()
31+
source(err)
32+
display("IO error while writing blob or reading file metadata or changing filetype")
33+
}
34+
ObjectNotFound{ oid: git_hash::ObjectId, path: std::path::PathBuf } {
35+
display("object {} for checkout at {} not found in object database", oid.to_hex(), path.display())
36+
}
37+
}
38+
}
39+
}
40+
41+
pub fn checkout<Find>(
42+
index: &mut git_index::State,
43+
path: impl AsRef<std::path::Path>,
44+
mut find: Find,
45+
options: checkout::Options,
46+
) -> Result<(), checkout::Error>
47+
where
48+
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
49+
{
50+
let root = path.as_ref();
51+
let mut buf = Vec::new();
52+
for (entry, entry_path) in index.entries_mut_with_paths() {
53+
// TODO: write test for that
54+
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
55+
continue;
56+
}
57+
58+
entry::checkout(entry, entry_path, &mut find, root, options, &mut buf)?;
59+
}
60+
Ok(())
61+
}
62+
63+
pub(crate) mod entry {
64+
use std::{
65+
convert::TryInto,
66+
fs::{create_dir_all, OpenOptions},
67+
io::Write,
68+
time::Duration,
69+
};
70+
71+
use bstr::BStr;
72+
use git_hash::oid;
73+
use git_index::Entry;
74+
75+
use crate::index;
76+
77+
pub fn checkout<Find>(
78+
entry: &mut Entry,
79+
entry_path: &BStr,
80+
find: &mut Find,
81+
root: &std::path::Path,
82+
index::checkout::Options { symlinks }: index::checkout::Options,
83+
buf: &mut Vec<u8>,
84+
) -> Result<(), index::checkout::Error>
85+
where
86+
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
87+
{
88+
let dest = root.join(git_features::path::from_byte_slice(entry_path).map_err(|_| {
89+
index::checkout::Error::IllformedUtf8 {
90+
path: entry_path.to_owned(),
91+
}
92+
})?);
93+
create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?
94+
95+
match entry.mode {
96+
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
97+
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
98+
oid: entry.id,
99+
path: root.to_path_buf(),
100+
})?;
101+
let mut options = OpenOptions::new();
102+
options.write(true).create_new(true);
103+
#[cfg(unix)]
104+
if entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
105+
use std::os::unix::fs::OpenOptionsExt;
106+
options.mode(0o777);
107+
}
108+
let mut file = options.open(&dest)?;
109+
file.write_all(obj.data)?;
110+
let met = file.metadata()?;
111+
let ctime = met
112+
.created()
113+
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
114+
let mtime = met
115+
.modified()
116+
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
117+
118+
update_fstat(entry, ctime, mtime)?;
119+
}
120+
git_index::entry::Mode::SYMLINK => {
121+
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
122+
oid: entry.id,
123+
path: root.to_path_buf(),
124+
})?;
125+
let symlink_destination = git_features::path::from_byte_slice(obj.data)
126+
.map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;
127+
if symlinks {
128+
#[cfg(unix)]
129+
std::os::unix::fs::symlink(symlink_destination, &dest)?;
130+
#[cfg(windows)]
131+
if dest.exists() {
132+
if dest.is_file() {
133+
std::os::windows::fs::symlink_file(symlink_destination, &dest)?;
134+
} else {
135+
std::os::windows::fs::symlink_dir(symlink_destination, &dest)?;
136+
}
137+
}
138+
} else {
139+
std::fs::write(&dest, obj.data)?;
140+
}
141+
let met = std::fs::symlink_metadata(&dest)?;
142+
let ctime = met
143+
.created()
144+
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
145+
let mtime = met
146+
.modified()
147+
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
148+
update_fstat(entry, ctime, mtime)?;
149+
}
150+
git_index::entry::Mode::DIR => todo!(),
151+
git_index::entry::Mode::COMMIT => todo!(),
152+
_ => unreachable!(),
153+
}
154+
Ok(())
155+
}
156+
157+
fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), index::checkout::Error> {
158+
let stat = &mut entry.stat;
159+
stat.mtime.secs = mtime
160+
.as_secs()
161+
.try_into()
162+
.expect("by 2038 we found a solution for this");
163+
stat.mtime.nsecs = mtime.subsec_nanos();
164+
stat.ctime.secs = ctime
165+
.as_secs()
166+
.try_into()
167+
.expect("by 2038 we found a solution for this");
168+
stat.ctime.nsecs = ctime.subsec_nanos();
169+
Ok(())
170+
}
171+
}

git-worktree/src/lib.rs

Lines changed: 1 addition & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,3 @@
11
#![forbid(unsafe_code, rust_2018_idioms)]
22

3-
pub mod index {
4-
use git_hash::oid;
5-
6-
pub mod checkout {
7-
use bstr::BString;
8-
use quick_error::quick_error;
9-
10-
#[derive(Clone, Copy)]
11-
pub struct Options {
12-
pub symlinks: bool,
13-
}
14-
15-
impl Default for Options {
16-
fn default() -> Self {
17-
Options { symlinks: true }
18-
}
19-
}
20-
21-
quick_error! {
22-
#[derive(Debug)]
23-
pub enum Error {
24-
IllformedUtf8{ path: BString } {
25-
display("Could not convert path to UTF8: {}", path)
26-
}
27-
Time(err: std::time::SystemTimeError) {
28-
from()
29-
source(err)
30-
display("The clock was off when reading file related metadata after updating a file on disk")
31-
}
32-
Io(err: std::io::Error) {
33-
from()
34-
source(err)
35-
display("IO error while writing blob or reading file metadata or changing filetype")
36-
}
37-
ObjectNotFound{ oid: git_hash::ObjectId, path: std::path::PathBuf } {
38-
display("object {} for checkout at {} not found in object database", oid.to_hex(), path.display())
39-
}
40-
}
41-
}
42-
}
43-
44-
pub fn checkout<Find>(
45-
index: &mut git_index::State,
46-
path: impl AsRef<std::path::Path>,
47-
mut find: Find,
48-
options: checkout::Options,
49-
) -> Result<(), checkout::Error>
50-
where
51-
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
52-
{
53-
let root = path.as_ref();
54-
let mut buf = Vec::new();
55-
for (entry, entry_path) in index.entries_mut_with_paths() {
56-
// TODO: write test for that
57-
if entry.flags.contains(git_index::entry::Flags::SKIP_WORKTREE) {
58-
continue;
59-
}
60-
61-
entry::checkout(entry, entry_path, &mut find, root, options, &mut buf)?;
62-
}
63-
Ok(())
64-
}
65-
66-
pub(crate) mod entry {
67-
use std::{
68-
convert::TryInto,
69-
fs::{create_dir_all, OpenOptions},
70-
io::Write,
71-
time::Duration,
72-
};
73-
74-
use bstr::BStr;
75-
use git_hash::oid;
76-
use git_index::Entry;
77-
78-
use crate::index;
79-
80-
pub fn checkout<Find>(
81-
entry: &mut Entry,
82-
entry_path: &BStr,
83-
find: &mut Find,
84-
root: &std::path::Path,
85-
index::checkout::Options { symlinks }: index::checkout::Options,
86-
buf: &mut Vec<u8>,
87-
) -> Result<(), index::checkout::Error>
88-
where
89-
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<git_object::BlobRef<'a>>,
90-
{
91-
let dest = root.join(git_features::path::from_byte_slice(entry_path).map_err(|_| {
92-
index::checkout::Error::IllformedUtf8 {
93-
path: entry_path.to_owned(),
94-
}
95-
})?);
96-
create_dir_all(dest.parent().expect("entry paths are never empty"))?; // TODO: can this be avoided to create dirs when needed only?
97-
98-
match entry.mode {
99-
git_index::entry::Mode::FILE | git_index::entry::Mode::FILE_EXECUTABLE => {
100-
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
101-
oid: entry.id,
102-
path: root.to_path_buf(),
103-
})?;
104-
let mut options = OpenOptions::new();
105-
options.write(true).create_new(true);
106-
#[cfg(unix)]
107-
if entry.mode == git_index::entry::Mode::FILE_EXECUTABLE {
108-
use std::os::unix::fs::OpenOptionsExt;
109-
options.mode(0o777);
110-
}
111-
let mut file = options.open(&dest)?;
112-
file.write_all(obj.data)?;
113-
let met = file.metadata()?;
114-
let ctime = met
115-
.created()
116-
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
117-
let mtime = met
118-
.modified()
119-
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
120-
121-
update_fstat(entry, ctime, mtime)?;
122-
}
123-
git_index::entry::Mode::SYMLINK => {
124-
let obj = find(&entry.id, buf).ok_or_else(|| index::checkout::Error::ObjectNotFound {
125-
oid: entry.id,
126-
path: root.to_path_buf(),
127-
})?;
128-
let symlink_destination = git_features::path::from_byte_slice(obj.data)
129-
.map_err(|_| index::checkout::Error::IllformedUtf8 { path: obj.data.into() })?;
130-
if symlinks {
131-
#[cfg(unix)]
132-
std::os::unix::fs::symlink(symlink_destination, &dest)?;
133-
#[cfg(windows)]
134-
if dest.exists() {
135-
if dest.is_file() {
136-
std::os::windows::fs::symlink_file(symlink_destination, &dest)?;
137-
} else {
138-
std::os::windows::fs::symlink_dir(symlink_destination, &dest)?;
139-
}
140-
}
141-
} else {
142-
std::fs::write(&dest, obj.data)?;
143-
}
144-
let met = std::fs::symlink_metadata(&dest)?;
145-
let ctime = met
146-
.created()
147-
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
148-
let mtime = met
149-
.modified()
150-
.map_or(Ok(Duration::default()), |x| x.duration_since(std::time::UNIX_EPOCH))?;
151-
update_fstat(entry, ctime, mtime)?;
152-
}
153-
git_index::entry::Mode::DIR => todo!(),
154-
git_index::entry::Mode::COMMIT => todo!(),
155-
_ => unreachable!(),
156-
}
157-
Ok(())
158-
}
159-
160-
fn update_fstat(entry: &mut Entry, ctime: Duration, mtime: Duration) -> Result<(), index::checkout::Error> {
161-
let stat = &mut entry.stat;
162-
stat.mtime.secs = mtime
163-
.as_secs()
164-
.try_into()
165-
.expect("by 2038 we found a solution for this");
166-
stat.mtime.nsecs = mtime.subsec_nanos();
167-
stat.ctime.secs = ctime
168-
.as_secs()
169-
.try_into()
170-
.expect("by 2038 we found a solution for this");
171-
stat.ctime.nsecs = ctime.subsec_nanos();
172-
Ok(())
173-
}
174-
}
175-
}
3+
pub mod index;

0 commit comments

Comments
 (0)