#![allow(dead_code)]
use serde::Deserialize;
use crate::forge::github::gh::GhCommandRunner;
use crate::forge::traits::ForgeRepository;
pub fn resolve_canonical_repository(
origin: &ForgeRepository,
override_repo: Option<&ForgeRepository>,
runner: &dyn GhCommandRunner,
) -> ForgeRepository {
if let Some(repo) = override_repo {
return repo.clone();
}
match fetch_parent(origin, runner) {
Some(parent) => parent,
None => origin.clone(),
}
}
#[derive(Deserialize)]
struct RepoView {
parent: Option<ParentRepo>,
}
#[derive(Deserialize)]
struct ParentRepo {
full_name: String,
}
fn fetch_parent(origin: &ForgeRepository, runner: &dyn GhCommandRunner) -> Option<ForgeRepository> {
let endpoint = format!("repos/{}/{}", origin.owner, origin.name);
let mut args = vec![
"api".to_string(),
endpoint,
"--jq".to_string(),
"{ parent: (.parent | if . == null then null else { full_name: .full_name } end) }"
.to_string(),
];
if origin.host != "github.com" {
args.push("--hostname".to_string());
args.push(origin.host.clone());
}
let output = runner.run(&args).ok()?;
let view: RepoView = serde_json::from_str(&output).ok()?;
let parent = view.parent?;
let (owner, name) = parent.full_name.split_once('/')?;
if owner.is_empty() || name.is_empty() {
return None;
}
Some(ForgeRepository::github(origin.host.clone(), owner, name))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forge::github::gh::{GhCommandError, GhCommandResult};
use std::cell::RefCell;
#[derive(Default)]
struct FakeRunner {
response: RefCell<Option<GhCommandResult<String>>>,
calls: RefCell<Vec<Vec<String>>>,
}
impl FakeRunner {
fn with_ok(body: &str) -> Self {
let me = Self::default();
*me.response.borrow_mut() = Some(Ok(body.to_string()));
me
}
fn with_err(err: GhCommandError) -> Self {
let me = Self::default();
*me.response.borrow_mut() = Some(Err(err));
me
}
}
impl GhCommandRunner for FakeRunner {
fn run(&self, args: &[String]) -> GhCommandResult<String> {
self.calls.borrow_mut().push(args.to_vec());
self.response
.borrow_mut()
.take()
.unwrap_or(Err(GhCommandError::Failed {
status: Some(1),
stderr: "no response configured".to_string(),
}))
}
}
fn fork() -> ForgeRepository {
ForgeRepository::github("github.com", "agavra", "slatedb")
}
fn upstream() -> ForgeRepository {
ForgeRepository::github("github.com", "slatedb", "slatedb")
}
#[test]
fn should_return_override_without_calling_gh() {
let runner = FakeRunner::default();
let override_repo = upstream();
let resolved = resolve_canonical_repository(&fork(), Some(&override_repo), &runner);
assert_eq!(resolved, upstream());
assert!(
runner.calls.borrow().is_empty(),
"override must short-circuit the gh api call"
);
}
#[test]
fn should_return_parent_when_gh_reports_fork() {
let runner = FakeRunner::with_ok(r#"{"parent":{"full_name":"slatedb/slatedb"}}"#);
let resolved = resolve_canonical_repository(&fork(), None, &runner);
assert_eq!(resolved, upstream());
}
#[test]
fn should_preserve_origin_host_on_enterprise() {
let ghe_fork = ForgeRepository::github("ghe.internal", "agavra", "slatedb");
let runner = FakeRunner::with_ok(r#"{"parent":{"full_name":"slatedb/slatedb"}}"#);
let resolved = resolve_canonical_repository(&ghe_fork, None, &runner);
assert_eq!(
resolved,
ForgeRepository::github("ghe.internal", "slatedb", "slatedb")
);
let calls = runner.calls.borrow();
let last = calls.last().expect("expected a gh call");
assert!(last.iter().any(|a| a == "--hostname"));
assert!(last.iter().any(|a| a == "ghe.internal"));
}
#[test]
fn should_fall_back_to_origin_when_repo_is_not_a_fork() {
let runner = FakeRunner::with_ok(r#"{"parent":null}"#);
let resolved = resolve_canonical_repository(&upstream(), None, &runner);
assert_eq!(resolved, upstream());
}
#[test]
fn should_fall_back_to_origin_when_gh_fails() {
let runner = FakeRunner::with_err(GhCommandError::MissingGh);
let resolved = resolve_canonical_repository(&fork(), None, &runner);
assert_eq!(resolved, fork());
}
#[test]
fn should_fall_back_to_origin_when_response_is_malformed() {
let runner = FakeRunner::with_ok("not json");
let resolved = resolve_canonical_repository(&fork(), None, &runner);
assert_eq!(resolved, fork());
}
#[test]
fn should_fall_back_to_origin_when_parent_full_name_is_malformed() {
let runner = FakeRunner::with_ok(r#"{"parent":{"full_name":"no-slash"}}"#);
let resolved = resolve_canonical_repository(&fork(), None, &runner);
assert_eq!(resolved, fork());
}
}