use std::path::{Path, PathBuf}; use std::process::Command; use forge_git::command::{GitCommand, GitEnv}; use forge_git::pktline::{FLUSH_PKT, pkt_line, service_advertisement_header}; use forge_git::repo::{init_bare, list_branches, repo_path}; use forge_git::service::{GitService, service_command}; use forge_git::setup::{write_hook_shims, write_managed_gitconfig}; use forge_git::version::parse_git_version; fn env_for(data_dir: &Path) -> GitEnv { GitEnv { git_bin: PathBuf::from("/usr/bin/git"), data_dir: data_dir.to_path_buf(), } } const ULID: &str = "01arz3ndektsv4rrffq69g5fav"; #[test] fn version_parse() { assert_eq!(parse_git_version("git version 2.56.0\n"), Some((2, 56, 0))); assert_eq!( parse_git_version("git version 2.39.2.windows.1"), Some((2, 39, 2)) ); assert_eq!(parse_git_version("git version 2.46"), Some((2, 46, 0))); assert_eq!( parse_git_version("git version 2.46.0-rc1"), Some((2, 46, 0)) ); assert_eq!(parse_git_version("garbage"), None); } #[test] fn pkt_line_encoding() { assert_eq!(pkt_line("a\n"), b"0006a\n".to_vec()); assert_eq!(pkt_line(""), b"0004".to_vec()); assert_eq!(FLUSH_PKT, b"0000"); let header = service_advertisement_header(GitService::UploadPack); assert_eq!(header, b"001e# service=git-upload-pack\n0000".to_vec()); } #[test] fn repo_path_layout() { let p = repo_path(Path::new("/data"), &ULID.to_uppercase()).unwrap(); assert_eq!(p, PathBuf::from(format!("/data/repos/01/ar/{ULID}.git"))); assert!(repo_path(Path::new("/data"), "not-a-ulid").is_err()); assert!(repo_path(Path::new("/data"), "01arz3ndektsv4rrffq69g5fa").is_err()); assert!(repo_path(Path::new("/data"), "01arz3ndektsv4rrffq69g5fau").is_err()); } #[tokio::test] async fn init_bare_and_list_branches_empty() { let dir = tempfile::tempdir().unwrap(); let env = env_for(dir.path()); let repo = repo_path(dir.path(), ULID).unwrap(); init_bare(&env, &repo, "trunk").await.unwrap(); assert!(repo.join("HEAD").exists()); let branches = list_branches(&env, &repo).await.unwrap(); assert!(branches.is_empty()); } #[tokio::test] async fn init_bare_rejects_bad_branch() { let dir = tempfile::tempdir().unwrap(); let env = env_for(dir.path()); let repo = dir.path().join("x.git"); assert!(init_bare(&env, &repo, "-x").await.is_err()); assert!(init_bare(&env, &repo, "a..b").await.is_err()); assert!(!repo.exists()); } #[test] fn user_revs_rejects_option_like_values() { let dir = tempfile::tempdir().unwrap(); let env = env_for(dir.path()); assert!(GitCommand::new(&env, "log").user_revs(&["-x"]).is_err()); assert!(GitCommand::new(&env, "log").user_revs(&[""]).is_err()); assert!(GitCommand::new(&env, "log").user_revs(&["main"]).is_ok()); } #[test] fn gitconfig_is_written() { let dir = tempfile::tempdir().unwrap(); write_managed_gitconfig(dir.path()).unwrap(); let cfg = dir.path().join("gitconfig"); let out = Command::new("/usr/bin/git") .args(["config", "--file"]) .arg(&cfg) .args(["--get", "core.hooksPath"]) .output() .unwrap(); assert!(out.status.success()); let hooks = String::from_utf8_lossy(&out.stdout); assert_eq!(hooks.trim(), dir.path().join("hooks").to_str().unwrap()); let out = Command::new("/usr/bin/git") .args(["config", "--file"]) .arg(&cfg) .args(["--get", "receive.denyNonFastForwards"]) .output() .unwrap(); assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "false"); } #[test] fn hook_shims_are_executable() { use std::os::unix::fs::PermissionsExt; let dir = tempfile::tempdir().unwrap(); write_hook_shims(dir.path(), Path::new("/usr/local/bin/forge")).unwrap(); for name in ["pre-receive", "post-receive"] { let p = dir.path().join("hooks").join(name); let mode = std::fs::metadata(&p).unwrap().permissions().mode(); assert_eq!(mode & 0o777, 0o755, "{name}"); let body = std::fs::read_to_string(&p).unwrap(); assert_eq!( body, format!("#!/bin/sh\nexec '/usr/local/bin/forge' hook {name}\n") ); } assert!(write_hook_shims(dir.path(), Path::new("/tmp/it's")).is_err()); assert!(write_hook_shims(dir.path(), Path::new("/tmp/a\nb")).is_err()); } #[tokio::test] async fn upload_pack_advertise_exits_zero() { let dir = tempfile::tempdir().unwrap(); let env = env_for(dir.path()); let repo = repo_path(dir.path(), ULID).unwrap(); init_bare(&env, &repo, "main").await.unwrap(); service_command(&env, GitService::UploadPack, &repo, true, true, None) .status() .await .unwrap(); }