Skip to content

Commit 45f37af

Browse files
committed
feat: add filter::Pipeline::worktree_file_to_object().
That way it's easier to correctly add whole files into the object database.
1 parent 23d2bed commit 45f37af

File tree

4 files changed

+136
-6
lines changed

4 files changed

+136
-6
lines changed

gix/src/filter.rs

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub mod pipeline {
1919
pub mod options {
2020
use crate::{bstr::BString, config};
2121

22-
/// The error returned by [Pipeline::options()][crate::filter::Pipeline::options()].
22+
/// The error returned by [Pipeline::options()](crate::filter::Pipeline::options()).
2323
#[derive(Debug, thiserror::Error)]
2424
#[allow(missing_docs)]
2525
pub enum Error {
@@ -39,7 +39,7 @@ pub mod pipeline {
3939

4040
///
4141
pub mod convert_to_git {
42-
/// The error returned by [Pipeline::convert_to_git()][crate::filter::Pipeline::convert_to_git()].
42+
/// The error returned by [Pipeline::convert_to_git()](crate::filter::Pipeline::convert_to_git()).
4343
#[derive(Debug, thiserror::Error)]
4444
#[allow(missing_docs)]
4545
pub enum Error {
@@ -52,7 +52,7 @@ pub mod pipeline {
5252

5353
///
5454
pub mod convert_to_worktree {
55-
/// The error returned by [Pipeline::convert_to_worktree()][crate::filter::Pipeline::convert_to_worktree()].
55+
/// The error returned by [Pipeline::convert_to_worktree()](crate::filter::Pipeline::convert_to_worktree()).
5656
#[derive(Debug, thiserror::Error)]
5757
#[allow(missing_docs)]
5858
pub enum Error {
@@ -62,6 +62,25 @@ pub mod pipeline {
6262
Convert(#[from] gix_filter::pipeline::convert::to_worktree::Error),
6363
}
6464
}
65+
66+
///
67+
pub mod worktree_file_to_object {
68+
use std::path::PathBuf;
69+
70+
/// The error returned by [Pipeline::worktree_file_to_object()](crate::filter::Pipeline::worktree_file_to_object()).
71+
#[derive(Debug, thiserror::Error)]
72+
#[allow(missing_docs)]
73+
pub enum Error {
74+
#[error("Cannot add worktree files in bare repositories")]
75+
MissingWorktree,
76+
#[error("Failed to perform IO for object creation for '{}'", path.display())]
77+
IO { source: std::io::Error, path: PathBuf },
78+
#[error(transparent)]
79+
WriteBlob(#[from] crate::object::write::Error),
80+
#[error(transparent)]
81+
ConvertToGit(#[from] crate::filter::pipeline::convert_to_git::Error),
82+
}
83+
}
6584
}
6685

6786
/// A git pipeline for transforming data *to-git* and *to-worktree*, based
@@ -133,7 +152,7 @@ impl Pipeline<'_> {
133152
/// Convert a `src` stream (to be found at `rela_path`, a repo-relative path) to a representation suitable for storage in `git`
134153
/// by using all attributes at `rela_path` and configuration of the repository to know exactly which filters apply.
135154
/// `index` is used in particularly rare cases where the CRLF filter in auto-mode tries to determine whether to apply itself,
136-
/// and it should match the state used when [instantiating this instance][Self::new()].
155+
/// and it should match the state used when [instantiating this instance](Self::new()).
137156
/// Note that the return-type implements [`std::io::Read`].
138157
pub fn convert_to_git<R>(
139158
&mut self,
@@ -187,6 +206,63 @@ impl Pipeline<'_> {
187206
)?)
188207
}
189208

209+
/// Add the worktree file at `rela_path` to the object database and return its `(id, entry)` for use in a tree or in the index, for instance.
210+
///
211+
/// `index` is used in particularly rare cases where the CRLF filter in auto-mode tries to determine whether to apply itself,
212+
/// and it should match the state used when [instantiating this instance](Self::new()).
213+
///
214+
/// Return `Ok(None)` the file didn't exist in the worktree, or if it was of an untrackable type.
215+
pub fn worktree_file_to_object(
216+
&mut self,
217+
rela_path: &BStr,
218+
index: &gix_index::State,
219+
) -> Result<Option<(gix_hash::ObjectId, gix_object::tree::EntryKind)>, pipeline::worktree_file_to_object::Error>
220+
{
221+
use pipeline::worktree_file_to_object::Error;
222+
223+
let rela_path_as_path = gix_path::from_bstr(rela_path);
224+
let repo = self.repo;
225+
let worktree_dir = repo.work_dir().ok_or(Error::MissingWorktree)?;
226+
let path = worktree_dir.join(&rela_path_as_path);
227+
let md = match std::fs::symlink_metadata(&path) {
228+
Ok(md) => md,
229+
Err(err) => {
230+
if gix_fs::io_err::is_not_found(err.kind(), err.raw_os_error()) {
231+
return Ok(None);
232+
} else {
233+
return Err(Error::IO { source: err, path });
234+
}
235+
}
236+
};
237+
let (id, kind) = if md.is_symlink() {
238+
let target = std::fs::read_link(&path).map_err(|source| Error::IO { source, path })?;
239+
let id = repo.write_blob(gix_path::into_bstr(target).as_ref())?;
240+
(id, gix_object::tree::EntryKind::Link)
241+
} else if md.is_file() {
242+
use gix_filter::pipeline::convert::ToGitOutcome;
243+
244+
let file = std::fs::File::open(&path).map_err(|source| Error::IO { source, path })?;
245+
let file_for_git = self.convert_to_git(file, rela_path_as_path.as_ref(), index)?;
246+
let id = match file_for_git {
247+
ToGitOutcome::Unchanged(mut file) => repo.write_blob_stream(&mut file)?,
248+
ToGitOutcome::Buffer(buf) => repo.write_blob(buf)?,
249+
ToGitOutcome::Process(mut read) => repo.write_blob_stream(&mut read)?,
250+
};
251+
252+
let kind = if gix_fs::is_executable(&md) {
253+
gix_object::tree::EntryKind::BlobExecutable
254+
} else {
255+
gix_object::tree::EntryKind::Blob
256+
};
257+
(id, kind)
258+
} else {
259+
// This is probably a type-change to something we can't track.
260+
return Ok(None);
261+
};
262+
263+
Ok(Some((id.detach(), kind)))
264+
}
265+
190266
/// Retrieve the static context that is made available to the process filters.
191267
///
192268
/// The context set here is relevant for the [`convert_to_git()`][Self::convert_to_git()] and

gix/tests/fixtures/generated-archives/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
/make_core_worktree_repo.tar
88
/make_signatures_repo.tar
99
/make_diff_repos.tar
10-
/make_submodule_with_worktree.tar
10+
/make_submodule_with_worktree.tar
11+
/repo_with_untracked_files.tar
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
echo content >file
6+
ln -s file link
7+
8+
echo binary >exe && chmod +x exe
9+
mkfifo fifo

gix/tests/gix/repository/filter.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn pipeline_in_nonbare_repo_without_index() -> crate::Result {
1010
use gix::bstr::ByteSlice;
1111
use gix_filter::driver::apply::Delay;
1212

13-
use crate::util::{named_repo, named_subrepo_opts};
13+
use crate::util::{hex_to_id, named_repo, named_subrepo_opts};
1414

1515
#[test]
1616
fn pipeline_in_repo_without_special_options() -> crate::Result {
@@ -31,6 +31,50 @@ fn pipeline_in_repo_without_special_options() -> crate::Result {
3131
Ok(())
3232
}
3333

34+
#[test]
35+
#[cfg(unix)]
36+
fn pipeline_worktree_file_to_object() -> crate::Result {
37+
let repo = named_repo("repo_with_untracked_files.sh")?;
38+
let (mut pipe, index) = repo.filter_pipeline(None)?;
39+
40+
assert_eq!(
41+
pipe.worktree_file_to_object("file".into(), &index)?,
42+
Some((
43+
hex_to_id("d95f3ad14dee633a758d2e331151e950dd13e4ed"),
44+
gix::object::tree::EntryKind::Blob
45+
))
46+
);
47+
assert_eq!(
48+
pipe.worktree_file_to_object("link".into(), &index)?,
49+
Some((
50+
hex_to_id("1a010b1c0f081b2e8901d55307a15c29ff30af0e"),
51+
gix::object::tree::EntryKind::Link
52+
))
53+
);
54+
assert_eq!(
55+
pipe.worktree_file_to_object("exe".into(), &index)?,
56+
Some((
57+
hex_to_id("a9128c283485202893f5af379dd9beccb6e79486"),
58+
gix::object::tree::EntryKind::BlobExecutable
59+
))
60+
);
61+
assert_eq!(
62+
pipe.worktree_file_to_object("missing".into(), &index)?,
63+
None,
64+
"Missing files are specifically typed and no error"
65+
);
66+
assert!(
67+
repo.work_dir().expect("non-bare").join("fifo").exists(),
68+
"there is a fifo"
69+
);
70+
assert_eq!(
71+
pipe.worktree_file_to_object("fifo".into(), &index)?,
72+
None,
73+
"untrackable entries are just ignored as if they didn't exist"
74+
);
75+
Ok(())
76+
}
77+
3478
#[test]
3579
fn pipeline_with_autocrlf() -> crate::Result {
3680
let repo = named_repo("make_config_repo.sh")?;

0 commit comments

Comments
 (0)