#![allow(clippy::unwrap_used)]
use crate::config::environment::{Environment, RepositoryOptions};
use crate::index::{IndexEntry, MODE_REGULAR};
use crate::objects::ObjectId;
use crate::objects::ObjectKind;
use crate::parallel::ThreadPolicy;
use crate::ref_storage::RefStorageFormat;
use crate::repo::{
init_bare_with_env_worktree, init_repository, init_repository_separate_git_dir,
init_worktree_config, repository_format_warning, resolve_git_directory_arg,
trace_repo_setup_if_requested, validate_repo_format, InitOptions, Repository,
RepositoryBuilder,
};
use crate::write_tree::write_tree_from_index;
use tempfile::tempdir;
fn index_entry(path: &str, oid: crate::objects::ObjectId) -> IndexEntry {
IndexEntry {
ctime_sec: 0,
ctime_nsec: 0,
mtime_sec: 0,
mtime_nsec: 0,
dev: 0,
ino: 0,
mode: MODE_REGULAR,
uid: 0,
gid: 0,
size: 0,
oid,
flags: path.len().min(0xfff) as u16,
flags_extended: None,
path: path.as_bytes().to_vec(),
base_index_pos: 0,
}
}
#[test]
fn repository_open_discover_and_index_roundtrip() -> crate::error::Result<()> {
let dir = tempdir().map_err(crate::error::Error::Io)?;
let repo = init_repository(dir.path(), false, "main", None, RefStorageFormat::Files)?;
let git_dir = dir.path().join(".git");
let blob = repo.odb().write(ObjectKind::Blob, b"seed\n")?;
let tree_body = format!("100644 seed.txt\0").into_bytes();
let mut tree_bytes = tree_body;
tree_bytes.extend_from_slice(blob.as_bytes());
let tree = repo.odb().write(ObjectKind::Tree, &tree_bytes)?;
let commit_body =
format!("tree {tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\ninit\n");
let tip = repo
.odb()
.write(ObjectKind::Commit, commit_body.as_bytes())?;
std::fs::write(git_dir.join("refs/heads/main"), format!("{tip}\n"))
.map_err(crate::error::Error::Io)?;
let _ = validate_repo_format(&git_dir)?;
let _ = repository_format_warning(&git_dir)?;
let _ = init_worktree_config(&git_dir)?;
let _ = resolve_git_directory_arg(&git_dir)?;
let repo =
RepositoryBuilder::new(RepositoryOptions::empty()).open(&git_dir, Some(dir.path()))?;
let _ = repo.thread_policy();
let _ = repo.environment();
let _ = repo.reference_unix_time();
let _ = repo.wall_clock_epoch();
let _ = repo.command_runner();
let _ = repo.config()?;
let _ = repo.refs();
let _ = repo.is_bare();
let _ = repo.index_path();
let _ = repo.index_path_for_env()?;
let _ = repo.bloom_pathspec_cwd();
let _ = repo.pathspec_precompose_enabled();
let discovered = Repository::discover(Some(dir.path()), &RepositoryOptions::empty())?;
let _ = discovered.resolve_ref_name("HEAD")?;
let mut index = discovered.load_index()?;
let blob = discovered.odb().write(ObjectKind::Blob, b"indexed\n")?;
index.entries_mut().push(index_entry("indexed.txt", blob));
let _ = write_tree_from_index(discovered.odb(), &index, "")?;
discovered.write_index(&mut index)?;
let _ = discovered.try_write_index(&mut index)?;
let _ = discovered.split_index_would_force_write(&index);
let _ = trace_repo_setup_if_requested(&discovered);
let with_policy = discovered.with_thread_policy(ThreadPolicy::default());
let _ = with_policy.network_trace_enabled();
Ok(())
}
#[test]
fn separate_git_dir_and_bare_init_paths() -> crate::error::Result<()> {
let wt = tempdir().map_err(crate::error::Error::Io)?;
let separate = wt.path().join("repo.git");
init_repository_separate_git_dir(
wt.path(),
&separate,
"main",
None,
RefStorageFormat::Files,
crate::objects::HashAlgo::Sha1,
)?;
let repo =
RepositoryBuilder::new(RepositoryOptions::empty()).open(&separate, Some(wt.path()))?;
let _ = repo.load_index()?;
let bare_dir = tempdir().map_err(crate::error::Error::Io)?;
init_repository(bare_dir.path(), true, "main", None, RefStorageFormat::Files)?;
let _bare = Repository::open(bare_dir.path(), &RepositoryOptions::empty())?;
let bare_wt = tempdir().map_err(crate::error::Error::Io)?;
let git = bare_wt.path().join(".git");
init_bare_with_env_worktree(
&git,
bare_wt.path(),
"main",
None,
RefStorageFormat::Files,
crate::objects::HashAlgo::Sha1,
)?;
let nested = tempdir().map_err(crate::error::Error::Io)?;
let nested_git = nested.path().join("nested.git");
Repository::init(
nested.path(),
&InitOptions {
separate_git_dir: Some(nested_git),
..InitOptions::default()
},
)?;
Ok(())
}
#[test]
fn open_for_serving_and_bare_worktree_at_path() -> crate::error::Result<()> {
let wt = tempdir().map_err(crate::error::Error::Io)?;
Repository::init(
wt.path(),
&InitOptions {
bare_with_worktree_at_path: true,
..InitOptions::default()
},
)?;
let mut serve_opts = RepositoryOptions::empty();
serve_opts.open_for_serving = true;
let _ = Repository::open(wt.path(), &serve_opts)?;
let bare_git = tempdir().map_err(crate::error::Error::Io)?;
let git_dir = bare_git.path().join("repo.git");
std::fs::create_dir_all(&git_dir).map_err(crate::error::Error::Io)?;
std::fs::write(git_dir.join("HEAD"), "ref: refs/heads/main\n")
.map_err(crate::error::Error::Io)?;
std::fs::create_dir_all(git_dir.join("refs/heads")).map_err(crate::error::Error::Io)?;
std::fs::create_dir_all(git_dir.join("objects")).map_err(crate::error::Error::Io)?;
std::fs::write(
git_dir.join("config"),
"[core]\n\trepositoryformatversion = 0\n",
)
.map_err(crate::error::Error::Io)?;
let objects = git_dir.join("objects");
let blob = {
let odb = crate::odb::Odb::new(&objects);
odb.write(ObjectKind::Blob, b"root\n")?
};
let tree_body = format!("100644 f.txt\0").into_bytes();
let mut tree_bytes = tree_body;
tree_bytes.extend_from_slice(blob.as_bytes());
let tree = {
let odb = crate::odb::Odb::new(&objects);
odb.write(ObjectKind::Tree, &tree_bytes)?
};
let commit_body =
format!("tree {tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\ninit\n");
let tip = {
let odb = crate::odb::Odb::new(&objects);
odb.write(ObjectKind::Commit, commit_body.as_bytes())?
};
std::fs::write(git_dir.join("refs/heads/main"), format!("{tip}\n"))
.map_err(crate::error::Error::Io)?;
let bare_path = bare_git.path().join("repo.git");
let _ = Repository::open(&bare_path, &serve_opts)?;
Ok(())
}
#[test]
fn setup_trace_and_replace_ref_read_paths() -> crate::error::Result<()> {
let dir = tempdir().map_err(crate::error::Error::Io)?;
std::fs::create_dir_all(dir.path().join("nested/deep")).map_err(crate::error::Error::Io)?;
let trace = dir.path().join("setup-trace.txt");
std::fs::write(&trace, b"").map_err(crate::error::Error::Io)?;
let trace_abs = trace.canonicalize().unwrap_or(trace);
let mut env = Environment::empty();
env.git_trace_setup = Some(trace_abs.to_string_lossy().into_owned());
env.cwd = dir.path().join("nested/deep");
let mut opts = RepositoryOptions::empty();
opts.environment = env;
let repo = init_repository(dir.path(), false, "main", None, RefStorageFormat::Files)?;
let git_dir = dir.path().join(".git");
let blob = repo.odb().write(ObjectKind::Blob, b"payload\n")?;
let tree_body = format!("100644 x.txt\0").into_bytes();
let mut tree_bytes = tree_body;
tree_bytes.extend_from_slice(blob.as_bytes());
let tree = repo.odb().write(ObjectKind::Tree, &tree_bytes)?;
let commit_body =
format!("tree {tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\nm\n");
let tip = repo
.odb()
.write(ObjectKind::Commit, commit_body.as_bytes())?;
std::fs::write(git_dir.join("refs/heads/main"), format!("{tip}\n"))
.map_err(crate::error::Error::Io)?;
let replacement = repo.odb().write(ObjectKind::Blob, b"replacement\n")?;
std::fs::create_dir_all(git_dir.join("refs/replace")).map_err(crate::error::Error::Io)?;
std::fs::write(
git_dir.join(format!("refs/replace/{}", tip.to_hex())),
format!("{replacement}\n"),
)
.map_err(crate::error::Error::Io)?;
let discovered = Repository::discover(Some(dir.path()), &opts)?;
trace_repo_setup_if_requested(&discovered).map_err(crate::error::Error::Io)?;
let contents = std::fs::read_to_string(&trace_abs).map_err(crate::error::Error::Io)?;
assert!(contents.contains("setup:"));
let replaced = discovered.read_replaced(&tip)?;
assert_eq!(replaced.kind, ObjectKind::Blob);
let _oid: ObjectId = tip;
Ok(())
}
#[test]
fn discover_honors_git_dir_and_ceiling_env() -> crate::error::Result<()> {
let dir = tempdir().map_err(crate::error::Error::Io)?;
let repo = init_repository(dir.path(), false, "main", None, RefStorageFormat::Files)?;
let git_dir = dir.path().join(".git");
let blob = repo.odb().write(ObjectKind::Blob, b"x\n")?;
let tree_body = format!("100644 x.txt\0").into_bytes();
let mut tree_bytes = tree_body;
tree_bytes.extend_from_slice(blob.as_bytes());
let tree = repo.odb().write(ObjectKind::Tree, &tree_bytes)?;
let commit_body =
format!("tree {tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\nm\n");
let tip = repo
.odb()
.write(ObjectKind::Commit, commit_body.as_bytes())?;
std::fs::write(git_dir.join("refs/heads/main"), format!("{tip}\n"))
.map_err(crate::error::Error::Io)?;
let mut env = Environment::empty();
env.cwd = dir.path().to_path_buf();
env.git_dir = Some(git_dir.to_string_lossy().into_owned());
let mut opts = RepositoryOptions::empty();
opts.environment = env.clone();
let explicit = Repository::discover(None, &opts)?;
assert_eq!(explicit.git_dir(), git_dir);
let mut env_wt = env.clone();
env_wt.git_work_tree = Some(dir.path().to_string_lossy().into_owned());
opts.environment = env_wt;
let with_wt = Repository::discover(None, &opts)?;
assert!(with_wt.work_tree().is_some());
let parent = dir
.path()
.parent()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "/".to_owned());
let mut env_ceil = Environment::empty();
env_ceil.cwd = dir.path().to_path_buf();
env_ceil.git_ceiling_directories = Some(format!(":{parent}"));
opts.environment = env_ceil;
let from_sub = Repository::discover(Some(dir.path()), &opts)?;
assert!(from_sub.work_tree().is_some());
Ok(())
}
#[test]
fn setup_trace_bare_layout_and_relative_git_dir() -> crate::error::Result<()> {
let layout = tempdir().map_err(crate::error::Error::Io)?;
let parent = layout.path().join("parent");
std::fs::create_dir_all(&parent).map_err(crate::error::Error::Io)?;
let bare_git = parent.join("repo.git");
init_repository(&bare_git, true, "main", None, RefStorageFormat::Files)?;
let blob = {
let repo = Repository::open(&bare_git, &RepositoryOptions::empty())?;
repo.odb().write(ObjectKind::Blob, b"root\n")?
};
let tree_body = format!("100644 f.txt\0").into_bytes();
let mut tree_bytes = tree_body;
tree_bytes.extend_from_slice(blob.as_bytes());
let tree = {
let repo = Repository::open(&bare_git, &RepositoryOptions::empty())?;
repo.odb().write(ObjectKind::Tree, &tree_bytes)?
};
let commit_body =
format!("tree {tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\ninit\n");
let tip = {
let repo = Repository::open(&bare_git, &RepositoryOptions::empty())?;
repo.odb()
.write(ObjectKind::Commit, commit_body.as_bytes())?
};
std::fs::write(bare_git.join("refs/heads/main"), format!("{tip}\n"))
.map_err(crate::error::Error::Io)?;
let sub = parent.join("sub");
std::fs::create_dir_all(&sub).map_err(crate::error::Error::Io)?;
let trace_bare = layout.path().join("trace-bare.txt");
std::fs::write(&trace_bare, b"").map_err(crate::error::Error::Io)?;
let trace_bare_abs = trace_bare.canonicalize().unwrap_or(trace_bare);
let mut env = Environment::empty();
env.git_trace_setup = Some(trace_bare_abs.to_string_lossy().into_owned());
env.cwd = sub.clone();
env.git_dir = Some("../repo.git".into());
let mut opts = RepositoryOptions::empty();
opts.environment = env;
let bare_repo = Repository::discover(None, &opts)?;
trace_repo_setup_if_requested(&bare_repo).map_err(crate::error::Error::Io)?;
let bare_trace = std::fs::read_to_string(&trace_bare_abs).map_err(crate::error::Error::Io)?;
assert!(bare_trace.contains("setup:"));
let wt_root = tempdir().map_err(crate::error::Error::Io)?;
let wt_repo = init_repository(wt_root.path(), false, "main", None, RefStorageFormat::Files)?;
let git_dir = wt_root.path().join(".git");
let wt_blob = wt_repo.odb().write(ObjectKind::Blob, b"x\n")?;
let wt_tree_body = format!("100644 x.txt\0").into_bytes();
let mut wt_tree_bytes = wt_tree_body;
wt_tree_bytes.extend_from_slice(wt_blob.as_bytes());
let wt_tree = wt_repo.odb().write(ObjectKind::Tree, &wt_tree_bytes)?;
let wt_commit =
format!("tree {wt_tree}\nauthor T <t@e.com> 1 +0000\ncommitter T <t@e.com> 1 +0000\n\nm\n");
let wt_tip = wt_repo
.odb()
.write(ObjectKind::Commit, wt_commit.as_bytes())?;
std::fs::write(git_dir.join("refs/heads/main"), format!("{wt_tip}\n"))
.map_err(crate::error::Error::Io)?;
let nested = wt_root.path().join("nested");
std::fs::create_dir_all(&nested).map_err(crate::error::Error::Io)?;
let trace_rel = layout.path().join("trace-rel.txt");
std::fs::write(&trace_rel, b"").map_err(crate::error::Error::Io)?;
let trace_rel_abs = trace_rel.canonicalize().unwrap_or(trace_rel);
let mut env_rel = Environment::empty();
env_rel.git_trace_setup = Some(trace_rel_abs.to_string_lossy().into_owned());
env_rel.cwd = nested.clone();
env_rel.git_dir = Some("../.git".into());
let mut opts_rel = RepositoryOptions::empty();
opts_rel.environment = env_rel;
let rel_repo = Repository::discover(None, &opts_rel)?;
trace_repo_setup_if_requested(&rel_repo).map_err(crate::error::Error::Io)?;
let rel_trace = std::fs::read_to_string(&trace_rel_abs).map_err(crate::error::Error::Io)?;
assert!(rel_trace.contains("setup:"));
let mut env_wt = Environment::empty();
env_wt.cwd = wt_root.path().join("nested");
env_wt.git_work_tree = Some(wt_root.path().to_string_lossy().into_owned());
env_wt.git_dir = Some(wt_root.path().join(".git").to_string_lossy().into_owned());
let trace_wt = layout.path().join("trace-wt.txt");
std::fs::write(&trace_wt, b"").map_err(crate::error::Error::Io)?;
let trace_wt_abs = trace_wt.canonicalize().unwrap_or(trace_wt);
env_wt.git_trace_setup = Some(trace_wt_abs.to_string_lossy().into_owned());
let mut opts_wt = RepositoryOptions::empty();
opts_wt.environment = env_wt;
let wt_discovered = Repository::discover(None, &opts_wt)?;
trace_repo_setup_if_requested(&wt_discovered).map_err(crate::error::Error::Io)?;
let wt_trace = std::fs::read_to_string(&trace_wt_abs).map_err(crate::error::Error::Io)?;
assert!(wt_trace.contains("setup:"));
Ok(())
}