//! Copyright (c) 2022-2026 GitDataAi All rights reserved. use crate::bare::GitBare; use crate::error::GitResult; use crate::pb::*; impl GitBare { /// Detect license by reading LICENSE/COPYING files and doing basic matching. pub fn find_license(&self) -> GitResult { let possible_paths = [ "LICENSE", "LICENSE.md", "LICENSE.txt", "LICENCE", "LICENCE.md", "LICENCE.txt", "COPYING", "COPYING.md", "COPYING.txt", "UNLICENSE", ]; for path in &possible_paths { let output = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "show", &format!("HEAD:{path}"), ]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if output.status.success() { let content = String::from_utf8_lossy(&output.stdout); let (spdx, name, conf) = detect_license(&content); if conf > 0.0 { return Ok(FindLicenseResponse { license_spdx: spdx.to_string(), license_name: name.to_string(), confidence: conf, license_path: path.to_string(), }); } } } Ok(FindLicenseResponse::default()) } } /// Very basic license detection by keyword matching. /// Returns (SPDX identifier, human-readable name, confidence). fn detect_license(content: &str) -> (&'static str, &'static str, f64) { let lower = content.to_lowercase(); if lower.contains("permission is hereby granted, free of charge") && lower.contains("mit") { return ("MIT", "MIT License", 0.95); } if lower.contains("apache license, version 2.0") || lower.contains("apache-2.0") { return ("Apache-2.0", "Apache License 2.0", 0.95); } if lower.contains("gnu general public license") && lower.contains("version 3") { return ("GPL-3.0", "GNU General Public License v3.0", 0.90); } if lower.contains("gnu general public license") && lower.contains("version 2") { return ("GPL-2.0", "GNU General Public License v2.0", 0.90); } if lower.contains("redistribution and use in source and binary forms") && lower.contains("neither the name of") { return ("BSD-3-Clause", "BSD 3-Clause License", 0.85); } if lower.contains("redistribution and use in source and binary forms") { return ("BSD-2-Clause", "BSD 2-Clause License", 0.80); } if lower.contains("gnu affero general public license") { return ("AGPL-3.0", "GNU Affero General Public License v3.0", 0.90); } if lower.contains("gnu lesser general public license") { return ("LGPL-3.0", "GNU Lesser General Public License v3.0", 0.85); } if lower.contains("mozilla public license") { return ("MPL-2.0", "Mozilla Public License 2.0", 0.90); } if lower.contains("this is free and unencumbered software released into the public domain") { return ("Unlicense", "The Unlicense", 0.95); } if lower.contains("permission to use, copy, modify, and/or distribute") && lower.contains("isc") { return ("ISC", "ISC License", 0.80); } ("", "", 0.0) }