mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-28 12:05:22 +03:00
Support executable blobs
This commit is contained in:
parent
6d52977202
commit
511ca5212e
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2624,12 +2624,14 @@ dependencies = [
|
||||
"gitbutler-branch",
|
||||
"gitbutler-branch-actions",
|
||||
"gitbutler-command-context",
|
||||
"gitbutler-oxidize",
|
||||
"gitbutler-project",
|
||||
"gitbutler-reference",
|
||||
"gitbutler-repo",
|
||||
"gitbutler-storage",
|
||||
"gitbutler-url",
|
||||
"gitbutler-user",
|
||||
"gix",
|
||||
"gix-testtools",
|
||||
"keyring",
|
||||
"once_cell",
|
||||
|
@ -2,7 +2,6 @@
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::path::PathBuf;
|
||||
use std::{io::Write, path::Path, process::Stdio, str};
|
||||
|
||||
use crate::{Config, LogUntil};
|
||||
@ -17,6 +16,7 @@ use gitbutler_oxidize::{
|
||||
git2_signature_to_gix_signature, git2_to_gix_object_id, gix_to_git2_oid, gix_to_git2_signature,
|
||||
};
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
use gix::fs::is_executable;
|
||||
use gix::objs::WriteTo;
|
||||
use tracing::instrument;
|
||||
|
||||
@ -196,11 +196,21 @@ impl RepositoryExt for git2::Repository {
|
||||
} else if (status.is_index_deleted() && !status.is_wt_new()) || status.is_wt_deleted() {
|
||||
tree_update_builder.remove(path);
|
||||
} else {
|
||||
let file_path = worktree_path.join(path);
|
||||
let file = std::fs::read(file_path)?;
|
||||
let blob = self.blob(&file)?;
|
||||
let file_path = worktree_path.join(path).to_owned();
|
||||
|
||||
tree_update_builder.upsert(path, blob, git2::FileMode::Blob);
|
||||
if file_path.is_symlink() {
|
||||
} else {
|
||||
let file = std::fs::read(&file_path)?;
|
||||
let blob = self.blob(&file)?;
|
||||
|
||||
let file_type = if is_executable(&file_path.metadata()?) {
|
||||
git2::FileMode::BlobExecutable
|
||||
} else {
|
||||
git2::FileMode::Blob
|
||||
};
|
||||
|
||||
tree_update_builder.upsert(path, blob, file_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,12 @@
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
fs::{File, Permissions},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use gitbutler_repo::RepositoryExt as _;
|
||||
use gitbutler_testsupport::testing_repository::{assert_tree_matches, TestingRepository};
|
||||
use gitbutler_testsupport::testing_repository::{
|
||||
assert_tree_matches, assert_tree_matches_with_mode, EntryAttribute, TestingRepository,
|
||||
};
|
||||
|
||||
/// These tests exercise the truth table that we use to update the HEAD
|
||||
/// tree to match the worktree.
|
||||
@ -19,7 +24,6 @@ use gitbutler_testsupport::testing_repository::{assert_tree_matches, TestingRepo
|
||||
/// | modify | modify | upsert |
|
||||
#[cfg(test)]
|
||||
mod head_upsert_truthtable {
|
||||
|
||||
use super::*;
|
||||
|
||||
// | add | delete | no-action |
|
||||
@ -410,3 +414,77 @@ fn should_not_change_index() {
|
||||
&[("file1.txt", b"content1")],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_behavior() {
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(
|
||||
None,
|
||||
&[
|
||||
("dir1/file1.txt", "content1"),
|
||||
("dir2/file2.txt", "content2"),
|
||||
],
|
||||
);
|
||||
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
|
||||
// Update a file in a directory
|
||||
std::fs::write(
|
||||
test_repository.tempdir.path().join("dir1/file1.txt"),
|
||||
"new1",
|
||||
)
|
||||
.unwrap();
|
||||
// Make a new directory and file
|
||||
std::fs::create_dir(test_repository.tempdir.path().join("dir3")).unwrap();
|
||||
std::fs::write(
|
||||
test_repository.tempdir.path().join("dir3/file1.txt"),
|
||||
"new2",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
||||
|
||||
assert_tree_matches(
|
||||
&test_repository.repository,
|
||||
&tree,
|
||||
&[
|
||||
("dir1/file1.txt", b"new1"),
|
||||
("dir2/file2.txt", b"content2"),
|
||||
("dir3/file1.txt", b"new2"),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn executable_blobs() {
|
||||
use std::{io::Write, os::unix::fs::PermissionsExt as _};
|
||||
|
||||
let test_repository = TestingRepository::open();
|
||||
|
||||
let commit = test_repository.commit_tree(None, &[]);
|
||||
test_repository
|
||||
.repository
|
||||
.branch("master", &commit, true)
|
||||
.unwrap();
|
||||
|
||||
let mut file = File::create(test_repository.tempdir.path().join("file1.txt")).unwrap();
|
||||
file.set_permissions(Permissions::from_mode(0o755)).unwrap();
|
||||
file.write_all(b"content1").unwrap();
|
||||
|
||||
let tree: git2::Tree = test_repository.repository.create_wd_tree().unwrap();
|
||||
|
||||
assert_tree_matches_with_mode(
|
||||
&test_repository.repository,
|
||||
tree.id(),
|
||||
&[(
|
||||
"file1.txt",
|
||||
b"content1",
|
||||
&[EntryAttribute::Blob, EntryAttribute::Executable],
|
||||
)],
|
||||
);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ tempfile = "3.10.1"
|
||||
keyring.workspace = true
|
||||
serde_json = "1.0"
|
||||
gix-testtools = "0.15.0"
|
||||
gix.workspace = true
|
||||
parking_lot.workspace = true
|
||||
gitbutler-branch-actions = { path = "../gitbutler-branch-actions" }
|
||||
gitbutler-repo = { path = "../gitbutler-repo" }
|
||||
@ -27,3 +28,4 @@ gitbutler-branch.workspace = true
|
||||
gitbutler-reference.workspace = true
|
||||
gitbutler-storage.workspace = true
|
||||
gitbutler-url.workspace = true
|
||||
gitbutler-oxidize.workspace = true
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::{fs, path::Path};
|
||||
use std::fs;
|
||||
|
||||
use gitbutler_oxidize::git2_to_gix_object_id;
|
||||
use gix_testtools::bstr::ByteSlice as _;
|
||||
use tempfile::{tempdir, TempDir};
|
||||
|
||||
@ -50,7 +51,14 @@ impl TestingRepository {
|
||||
}
|
||||
// Write any files
|
||||
for (file_name, contents) in files {
|
||||
fs::write(self.tempdir.path().join(file_name), contents).unwrap();
|
||||
let path = self.tempdir.path().join(file_name);
|
||||
let parent = path.parent().unwrap();
|
||||
|
||||
if !parent.exists() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
|
||||
fs::write(path, contents).unwrap();
|
||||
}
|
||||
|
||||
// Update the index
|
||||
@ -94,16 +102,60 @@ pub fn assert_tree_matches<'a>(
|
||||
tree: &git2::Tree<'a>,
|
||||
files: &[(&str, &[u8])],
|
||||
) {
|
||||
for (path, content) in files {
|
||||
let blob = tree.get_path(Path::new(path)).unwrap().id();
|
||||
let blob: git2::Blob<'a> = repository.find_blob(blob).unwrap();
|
||||
assert_tree_matches_with_mode(
|
||||
repository,
|
||||
tree.id(),
|
||||
&files
|
||||
.iter()
|
||||
.map(|(path, content)| (*path, *content, &[] as &[EntryAttribute]))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum EntryAttribute {
|
||||
Tree,
|
||||
Commit,
|
||||
Link,
|
||||
Blob,
|
||||
Executable,
|
||||
}
|
||||
|
||||
pub fn assert_tree_matches_with_mode(
|
||||
repository: &git2::Repository,
|
||||
tree: git2::Oid,
|
||||
files: &[(&str, &[u8], &[EntryAttribute])],
|
||||
) {
|
||||
let gix_repository = gix::open(repository.path()).unwrap();
|
||||
|
||||
let tree = gix_repository
|
||||
.find_tree(git2_to_gix_object_id(tree))
|
||||
.unwrap();
|
||||
|
||||
for (path, content, entry_attributes) in files {
|
||||
let tree_entry = tree.lookup_entry_by_path(path).unwrap().unwrap();
|
||||
let object = gix_repository.find_object(tree_entry.id()).unwrap();
|
||||
assert_eq!(
|
||||
blob.content(),
|
||||
object.data,
|
||||
*content,
|
||||
"{}: expect {} == {}",
|
||||
path,
|
||||
blob.content().to_str_lossy(),
|
||||
object.data.to_str_lossy(),
|
||||
content.to_str_lossy()
|
||||
);
|
||||
|
||||
for entry_attribute in *entry_attributes {
|
||||
match entry_attribute {
|
||||
EntryAttribute::Tree => assert!(tree_entry.mode().is_tree(), "{} is a tree", path),
|
||||
EntryAttribute::Commit => {
|
||||
assert!(tree_entry.mode().is_commit(), "{} is a commit", path)
|
||||
}
|
||||
EntryAttribute::Link => assert!(tree_entry.mode().is_link(), "{} is a link", path),
|
||||
EntryAttribute::Blob => assert!(tree_entry.mode().is_blob(), "{} is a blob", path),
|
||||
EntryAttribute::Executable => {
|
||||
assert!(tree_entry.mode().is_executable(), "{} is executable", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user