use grit_lib::config::environment::RepositoryOptions;
use grit_lib::error::Error;
use grit_lib::objects::odb::Odb;
use grit_lib::objects::{ObjectId, ObjectKind};
use grit_lib::repo::{InitOptions, Repository};
use grit_lib::revwalk::rev_list::{rev_list, ObjectFilter, RevListOptions};
use grit_lib::revwalk::rev_list_error::RevListError;
use grit_lib::revwalk::rev_parse::{
resolve_push_full_ref_for_branch, resolve_revision, resolve_upstream_symbolic_name,
};
use grit_lib::revwalk::rev_parse_error::RevParseError;
use std::fs;
use std::path::Path;
use tempfile::tempdir;
fn empty_tree(odb: &Odb) -> grit_lib::error::Result<ObjectId> {
odb.write_loose_materialize(ObjectKind::Tree, b"")
}
fn write_commit(odb: &Odb, msg: &str) -> grit_lib::error::Result<ObjectId> {
let tree = empty_tree(odb)?;
let body = format!(
"tree {tree}\nauthor T <t@e.com> 100 +0000\ncommitter T <t@e.com> 100 +0000\n\n{msg}\n"
);
odb.write_loose_materialize(ObjectKind::Commit, body.as_bytes())
}
fn open_bare_with_main(dir: &Path) -> grit_lib::error::Result<(Repository, ObjectId)> {
Repository::init(
dir,
&InitOptions::new()
.with_bare(true)
.with_initial_branch("main".to_owned())
.with_ref_storage(grit_lib::refs::ref_storage::RefStorageFormat::Files),
)?;
let repo = Repository::open(dir, &RepositoryOptions::empty())?;
let tip = write_commit(repo.odb(), "init")?;
fs::write(repo.git_dir().join("refs/heads/main"), format!("{tip}\n"))?;
Ok((repo, tip))
}
fn write_config(git_dir: &Path, body: &str) {
fs::write(git_dir.join("config"), body).expect("write config");
}
#[test]
fn upstream_missing_reports_no_upstream_variant() {
let dir = tempdir().expect("tempdir");
let (repo, _) = open_bare_with_main(dir.path()).expect("repo");
write_config(
repo.git_dir(),
"[core]\n\trepositoryformatversion = 0\n[branch \"main\"]\n",
);
let err = resolve_upstream_symbolic_name(&repo, "main@{u}").unwrap_err();
assert!(matches!(
err,
Error::RevParse(RevParseError::NoUpstream { .. })
));
}
#[test]
fn push_default_nothing_reports_variant() {
let dir = tempdir().expect("tempdir");
let (repo, _) = open_bare_with_main(dir.path()).expect("repo");
write_config(
repo.git_dir(),
"[core]\n\trepositoryformatversion = 0\n\
[remote \"origin\"]\n\turl = /tmp/x\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n\
[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n\
[push]\n\tdefault = nothing\n",
);
fs::create_dir_all(repo.git_dir().join("refs/remotes/origin")).expect("remotes dir");
fs::write(
repo.git_dir().join("refs/remotes/origin/main"),
b"0000000000000000000000000000000000000001\n",
)
.expect("tracking ref");
let err = resolve_push_full_ref_for_branch(&repo, "main").unwrap_err();
assert!(matches!(
err,
Error::RevParse(RevParseError::PushDefaultNothing)
));
}
#[test]
fn ambiguous_spec_reports_ambiguous_argument() {
let dir = tempdir().expect("tempdir");
let (repo, _) = open_bare_with_main(dir.path()).expect("repo");
let err = resolve_revision(&repo, "not-a-ref-or-path").unwrap_err();
assert!(matches!(
err,
Error::RevParse(RevParseError::AmbiguousArgument { .. })
));
}
#[test]
fn sparse_blob_unreadable_for_missing_spec() {
let dir = tempdir().expect("tempdir");
let (repo, tip) = open_bare_with_main(dir.path()).expect("repo");
let mut opts = RevListOptions::default();
opts.filter = Some(ObjectFilter::SparseOid("missing:path".into()));
let err = rev_list(&repo, &[tip.to_hex()], &[], &opts).unwrap_err();
assert!(matches!(
err,
Error::RevList(RevListError::SparseBlobUnreadable { .. })
));
}
#[test]
fn sparse_filter_unparsable_when_commit_not_blob() {
let dir = tempdir().expect("tempdir");
let (repo, tip) = open_bare_with_main(dir.path()).expect("repo");
let mut opts = RevListOptions::default();
opts.filter = Some(ObjectFilter::SparseOid(tip.to_hex()));
let err = rev_list(&repo, &[tip.to_hex()], &[], &opts).unwrap_err();
assert!(matches!(
err,
Error::RevList(RevListError::SparseFilterUnparsable { .. })
));
}
#[test]
fn sparse_filter_unparsable_on_invalid_utf8_blob() {
let dir = tempdir().expect("tempdir");
let (repo, tip) = open_bare_with_main(dir.path()).expect("repo");
let blob = repo
.odb()
.write_loose_materialize(ObjectKind::Blob, &[0xff, 0xfe])
.expect("blob");
let mut opts = RevListOptions::default();
opts.filter = Some(ObjectFilter::SparseOid(blob.to_hex()));
let err = rev_list(&repo, &[tip.to_hex()], &[], &opts).unwrap_err();
assert!(matches!(
err,
Error::RevList(RevListError::SparseFilterUnparsable { .. })
));
}
#[test]
fn push_refspec_missing_when_branch_not_mapped() {
let dir = tempdir().expect("tempdir");
let (repo, _) = open_bare_with_main(dir.path()).expect("repo");
write_config(
repo.git_dir(),
"[core]\n\trepositoryformatversion = 0\n\
[remote \"origin\"]\n\turl = /tmp/x\n\tfetch = +refs/heads/*:refs/remotes/origin/*\n\
\tpush = refs/heads/other:refs/heads/other\n\
[branch \"main\"]\n\tremote = origin\n\tmerge = refs/heads/main\n",
);
let err = resolve_push_full_ref_for_branch(&repo, "main").unwrap_err();
assert!(
matches!(
err,
Error::RevParse(RevParseError::PushRefspecMissing { .. })
),
"unexpected error: {err:?}"
);
}