#[cfg(not(unix))]
compile_error!("repon-core requires a Unix target: see docs/spec/actions.md");
mod auto_update;
mod base;
mod cell;
mod core;
mod default_branch;
mod discovery;
mod entity;
mod environment;
mod executor;
mod fanout;
mod fetch;
mod filter;
mod git;
mod landing;
#[cfg(any(test, feature = "test-util"))]
pub mod liveness;
mod patch_equivalence;
mod poll;
mod snapshot;
#[cfg(test)]
mod test_support;
#[cfg(feature = "serde")]
mod wire;
pub use cell::{Cell, Generation, Settled, Timestamp, Unknown};
pub use core::AutoUpdateAttempt;
pub use core::FetchFailures;
pub use core::ManagementHandle;
pub use core::{ActionSpec, AutoUpdateSpec, Core, CoreSpec, FetchSpec, RepoOverride, Step};
pub use discovery::{Discovery, SetSpec, count, discover};
pub use entity::ActionReceipt;
pub use entity::AheadBehind;
pub use entity::CaptureElision;
pub use entity::DefaultBranch;
pub use entity::DefaultBranchStopped;
pub use entity::DeleteRisk;
pub use entity::Diagnostics;
pub use entity::DirtyCounts;
pub use entity::EntityKey;
pub use entity::EntityState;
pub use entity::Head;
pub use entity::Kind;
pub use entity::OwnWork;
pub use entity::Presence;
pub use entity::RunningStep;
pub use entity::Skip;
pub use entity::StepOutcome;
pub use entity::StepResult;
pub use entity::SyncState;
pub use entity::WorktreeState;
pub use environment::environment;
pub use filter::{Applicability, Filter, KeyVocabulary, vocabulary};
pub use git::{InProgressOperation, ProbeError, RecentCommit};
pub use snapshot::{RowSummary, Snapshot, summary};
#[cfg(feature = "serde")]
pub use wire::SettledDocument;
#[cfg(test)]
mod tests {
fn exported_name(item: &str, line: &str) -> String {
if let Some((_, alias)) = item.split_once(" as ") {
return alias.trim().to_string();
}
let name = item.trim();
assert!(
!name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_'),
"cannot read an exported name from `{item}` in `pub use` line `{line}`"
);
name.to_string()
}
fn crate_root_declarations(source: &str) -> &str {
source.split("mod tests {").next().unwrap_or(source)
}
fn crate_root_public_surface(source: &str) -> Vec<String> {
let mut names = Vec::new();
for line in crate_root_declarations(source).lines() {
let line = line.trim();
if !line.starts_with("pub ") {
continue;
}
if let Some(rest) = line.strip_prefix("pub mod ") {
let name = rest
.strip_suffix(';')
.unwrap_or_else(|| panic!("`pub mod` line is not `;`-terminated: `{line}`"));
names.push(exported_name(name, line));
continue;
}
if let Some(rest) = line.strip_prefix("pub const ") {
let name = rest
.split(':')
.next()
.unwrap_or_else(|| panic!("`pub const` line names nothing: `{line}`"));
names.push(exported_name(name, line));
continue;
}
let body = line
.strip_prefix("pub use ")
.and_then(|s| s.strip_suffix(';'))
.unwrap_or_else(|| {
panic!("crate-root `pub` line is in no form this scan reads: `{line}`")
});
match body.split_once('{') {
Some((_path, rest)) => {
let group = rest.strip_suffix('}').unwrap_or_else(|| {
panic!("`pub use` group is not `}}`-terminated: `{line}`")
});
for item in group.split(',') {
let item = item.trim();
if !item.is_empty() {
names.push(exported_name(item, line));
}
}
}
None => {
let last = body.rsplit("::").next().unwrap_or(body);
names.push(exported_name(last, line));
}
}
}
names
}
fn glossary_covers(glossary: &str, name: &str) -> bool {
let mut words = Vec::new();
let mut word = String::new();
for ch in name.chars() {
if ch == '_' {
if !word.is_empty() {
words.push(std::mem::take(&mut word));
}
continue;
}
if ch.is_uppercase() && word.ends_with(|last: char| !last.is_uppercase()) {
words.push(std::mem::take(&mut word));
}
word.push(ch);
}
if !word.is_empty() {
words.push(word);
}
let phrase = words.join(" ").to_lowercase();
glossary.to_lowercase().contains(&phrase)
}
#[test]
fn public_surface_matches_glossary() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let source = std::fs::read_to_string(manifest_dir.join("src/lib.rs"))
.expect("read this crate's own source");
let glossary = std::fs::read_to_string(manifest_dir.join("../../GLOSSARY.md"))
.expect("read the project glossary");
for name in crate_root_public_surface(&source) {
assert!(
glossary_covers(&glossary, &name),
"crate-root public name `{name}` has no matching entry in the project glossary"
);
}
}
fn modules_the_release_gate_requires_to_be_private(releasing: &str) -> Vec<String> {
let gate = releasing
.split("## Before the first crates.io publish")
.nth(1)
.expect("releasing.md must carry the pre-publish gate section");
let item = gate
.lines()
.find(|line| line.trim_start().starts_with("1. "))
.expect("the pre-publish gate must carry a numbered item 1");
let names: Vec<String> = item
.split('`')
.skip(1)
.step_by(2)
.filter_map(|code| Some(code.strip_prefix("mod ")?.strip_suffix(';')?.to_string()))
.collect();
assert!(
!names.is_empty(),
"cleared-gate item 1 names no `mod name;` fragment any more, so the privacy it \
records has nothing left to check: {item}"
);
names
}
#[test]
fn modules_the_release_gate_names_are_private_at_the_crate_root() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let source = std::fs::read_to_string(manifest_dir.join("src/lib.rs"))
.expect("read this crate's own source");
let releasing = std::fs::read_to_string(manifest_dir.join("../../docs/spec/releasing.md"))
.expect("read the releasing spec");
let declarations: Vec<&str> = crate_root_declarations(&source)
.lines()
.map(str::trim)
.collect();
for name in modules_the_release_gate_requires_to_be_private(&releasing) {
let private = format!("mod {name};");
let public = format!("pub {private}");
let declaration = declarations
.iter()
.find(|line| **line == private || **line == public)
.unwrap_or_else(|| {
panic!(
"`docs/spec/releasing.md`'s cleared-gate item 1 names `{private}`, \
which this crate root no longer declares in any form"
)
});
assert_eq!(
**declaration, private,
"`{name}` is public at this crate root, which `docs/spec/releasing.md`'s \
cleared-gate item 1 requires to stay private"
);
}
}
fn rust_source_files(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut files = Vec::new();
for entry in std::fs::read_dir(dir).expect("read a source directory") {
let path = entry.expect("read a directory entry").path();
if path.is_dir() {
files.extend(rust_source_files(&path));
} else if path.extension().is_some_and(|extension| extension == "rs") {
files.push(path);
}
}
files
}
#[test]
fn gix_interrupt_is_interrupted_is_never_used() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let banned = format!("interrupt::{}", "IS_INTERRUPTED");
let mut offending_locations = Vec::new();
for path in rust_source_files(&manifest_dir.join("src")) {
let source = std::fs::read_to_string(&path).expect("read a crate source file");
for (number, line) in source.lines().enumerate() {
if line.trim_start().starts_with("//") {
continue;
}
if line.contains(&banned) {
offending_locations.push(format!("{}:{}", path.display(), number + 1));
}
}
}
assert!(
offending_locations.is_empty(),
"gix's process-global interrupt static must never be used outside a comment, found at: {offending_locations:?}"
);
}
#[test]
fn no_source_file_in_this_crate_names_the_rendering_crates_that_parse_ansi() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let banned = [
format!("{}{}", "rata", "tui"),
format!("{}_{}", "ansi", "to_tui"),
];
let mut offending_locations = Vec::new();
for path in rust_source_files(&manifest_dir.join("src")) {
let source = std::fs::read_to_string(&path).expect("read a crate source file");
for (number, line) in source.lines().enumerate() {
if line.trim_start().starts_with("//") {
continue;
}
if banned.iter().any(|needle| line.contains(needle)) {
offending_locations.push(format!("{}:{}", path.display(), number + 1));
}
}
}
assert!(
offending_locations.is_empty(),
"found a rendering crate named in repon-core's own source, which must stay raw \
bytes with no interpretation: {offending_locations:?}"
);
}
#[test]
fn no_state_is_mapped_to_a_character_anywhere_in_this_crate() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let banned_return = format!("-{}", "> char");
let banned_arm = format!("={}", "> '");
let mut offending_locations = Vec::new();
for path in rust_source_files(&manifest_dir.join("src")) {
let source = std::fs::read_to_string(&path).expect("read a crate source file");
for (number, line) in source.lines().enumerate() {
if line.trim_start().starts_with("//") {
continue;
}
if line.contains(&banned_return) || line.contains(&banned_arm) {
offending_locations.push(format!("{}:{}", path.display(), number + 1));
}
}
}
assert!(
offending_locations.is_empty(),
"repon-core must never map a state to a character; the mapping belongs to the \
consumer, found at: {offending_locations:?}"
);
}
#[test]
fn test_util_is_never_a_default_feature() {
let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
let manifest = std::fs::read_to_string(manifest_dir.join("Cargo.toml"))
.expect("read this crate's own Cargo.toml");
let features_section = manifest
.split("[features]")
.nth(1)
.and_then(|rest| rest.split("\n[").next())
.unwrap_or("");
assert!(
features_section.contains("test-util"),
"expected a `test-util` feature declared in `[features]`; this test's own premise \
is stale if it moved: {manifest}"
);
let default_line = features_section
.lines()
.find(|line| line.trim_start().starts_with("default"));
assert!(
default_line.is_none_or(|line| !line.contains("test-util")),
"`test-util` must never be named in a default feature list, or it ships on every \
consumer's default build: {default_line:?}"
);
}
}