hololake-system-architecture/product-source/hololake-native-desktop/src-tauri/src/code_channel.rs

953 lines
31 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: AGPL-3.0-or-later
use serde::{Deserialize, Serialize};
use std::cmp::Reverse;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
use std::path::{Component, Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
use tauri::{AppHandle, Manager};
use tauri_plugin_dialog::DialogExt;
use url::Url;
use uuid::Uuid;
const SNAPSHOT_SCHEMA: &str = "hololake.code-channel/v1";
const REGISTRY_SCHEMA: &str = "hololake.code-channel-registry/v1";
const ALLOWED_HOSTS: &[&str] = &["guanghulab.com", "guanghubingshuo.com"];
const MAX_TREE_ENTRIES: usize = 1_000;
const MAX_CODE_FILE_BYTES: u64 = 2 * 1024 * 1024;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CloneCodeChannelInput {
pub url: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CodeChannelEntry {
pub channel_id: String,
pub name: String,
pub source_kind: String,
pub local_path: String,
pub remote_url: Option<String>,
pub git_head: String,
pub branch: String,
pub repository_clean: bool,
pub registered_at_unix_ms: u128,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeChannelSnapshot {
pub schema: &'static str,
pub state: &'static str,
pub storage_root: String,
pub channels: Vec<CodeChannelEntry>,
pub authority: &'static str,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct BrowseCodeChannelInput {
pub channel_id: String,
pub path: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeTreeEntry {
pub path: String,
pub name: String,
pub kind: &'static str,
pub size_bytes: u64,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeTreeSnapshot {
pub schema: &'static str,
pub channel_id: String,
pub path: String,
pub entries: Vec<CodeTreeEntry>,
pub truncated: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct ReadCodeChannelFileInput {
pub channel_id: String,
pub path: String,
}
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeFileProjection {
pub schema: &'static str,
pub channel_id: String,
pub path: String,
pub format: String,
pub source: String,
pub human_markdown: String,
pub size_bytes: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct CodeChannelRegistry {
schema: String,
channels: Vec<CodeChannelEntry>,
}
#[derive(Clone, Debug)]
struct ValidatedCloneUrl {
normalized: String,
owner: String,
repository: String,
}
#[tauri::command]
pub async fn get_code_channel_snapshot(app: AppHandle) -> Result<CodeChannelSnapshot, String> {
let root = code_channel_root(&app)?;
tauri::async_runtime::spawn_blocking(move || snapshot_at(&root))
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn clone_code_channel(
app: AppHandle,
input: CloneCodeChannelInput,
) -> Result<CodeChannelSnapshot, String> {
let root = code_channel_root(&app)?;
tauri::async_runtime::spawn_blocking(move || clone_at(&root, input))
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn select_local_code_channel(
app: AppHandle,
) -> Result<Option<CodeChannelSnapshot>, String> {
let picker = app.clone();
let selected = tauri::async_runtime::spawn_blocking(move || {
picker
.dialog()
.file()
.set_title("选择本地 Git 代码频道")
.blocking_pick_folder()
})
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_PICKER_JOIN_FAILED: {error}"))?;
let Some(selected) = selected else {
return Ok(None);
};
let selected = selected
.into_path()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_PICKER_PATH_INVALID: {error}"))?;
let root = code_channel_root(&app)?;
tauri::async_runtime::spawn_blocking(move || register_local_at(&root, &selected).map(Some))
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn browse_code_channel(
app: AppHandle,
input: BrowseCodeChannelInput,
) -> Result<CodeTreeSnapshot, String> {
let root = code_channel_root(&app)?;
tauri::async_runtime::spawn_blocking(move || browse_at(&root, input))
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_JOIN_FAILED: {error}"))?
}
#[tauri::command]
pub async fn read_code_channel_file(
app: AppHandle,
input: ReadCodeChannelFileInput,
) -> Result<CodeFileProjection, String> {
let root = code_channel_root(&app)?;
tauri::async_runtime::spawn_blocking(move || read_code_file_at(&root, input))
.await
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_JOIN_FAILED: {error}"))?
}
fn code_channel_root(app: &AppHandle) -> Result<PathBuf, String> {
let root = app
.path()
.app_data_dir()
.map_err(|error| format!("HOLOLAKE_APP_DATA_UNAVAILABLE: {error}"))?
.join("code-channel-v1");
ensure_root(&root)?;
Ok(root)
}
pub(crate) fn register_managed_repository(
app: &AppHandle,
repository: &Path,
name: String,
) -> Result<CodeChannelEntry, String> {
let root = code_channel_root(app)?;
let local_path = repository.to_string_lossy().into_owned();
let existing_channel_id = read_registry(&root)?
.channels
.into_iter()
.find(|item| item.local_path == local_path)
.map(|item| item.channel_id);
let mut entry = entry_for_repository(repository, "GH_PNCC_NATIVE", None, Some(name))?;
if let Some(channel_id) = existing_channel_id {
entry.channel_id = channel_id;
}
upsert_entry(&root, entry)?;
read_registry(&root)?
.channels
.into_iter()
.find(|item| item.local_path == local_path)
.ok_or_else(|| "HOLOLAKE_USER_PNCC_CHANNEL_REGISTRATION_FAILED".to_string())
}
fn ensure_root(root: &Path) -> Result<(), String> {
fs::create_dir_all(root.join("repositories"))
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_STORAGE_UNAVAILABLE: {error}"))?;
fs::set_permissions(root, fs::Permissions::from_mode(0o700))
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_PERMISSION_FAILED: {error}"))?;
Ok(())
}
fn snapshot_at(root: &Path) -> Result<CodeChannelSnapshot, String> {
ensure_root(root)?;
let mut registry = read_registry(root)?;
for channel in &mut registry.channels {
if let Ok(inspection) = inspect_repository(Path::new(&channel.local_path)) {
channel.git_head = inspection.git_head;
channel.branch = inspection.branch;
channel.repository_clean = inspection.repository_clean;
}
}
registry
.channels
.sort_by_key(|entry| Reverse(entry.registered_at_unix_ms));
Ok(CodeChannelSnapshot {
schema: SNAPSHOT_SCHEMA,
state: "READY",
storage_root: root.to_string_lossy().into_owned(),
channels: registry.channels,
authority: "LOCAL_SOURCE_ACCESS_ONLY_NO_PUSH_OR_DEPLOY_AUTHORITY",
})
}
fn browse_at(root: &Path, input: BrowseCodeChannelInput) -> Result<CodeTreeSnapshot, String> {
let (channel, repository) = registered_repository(root, &input.channel_id)?;
let relative = input.path.unwrap_or_default();
let directory = safe_repository_path(&repository, &relative)?;
if !directory.is_dir() {
return Err("HOLOLAKE_CODE_CHANNEL_DIRECTORY_INVALID".into());
}
let mut entries = fs::read_dir(&directory)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_TREE_UNAVAILABLE: {error}"))?
.collect::<Result<Vec<_>, _>>()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_TREE_UNAVAILABLE: {error}"))?;
entries.sort_by_key(|entry| {
let is_file = entry.file_type().map(|kind| kind.is_file()).unwrap_or(true);
(is_file, entry.file_name())
});
let mut projected = Vec::new();
for entry in entries {
if projected.len() >= MAX_TREE_ENTRIES {
break;
}
let name = entry.file_name().to_string_lossy().into_owned();
if name == ".git" || name == ".DS_Store" || name.starts_with("._") {
continue;
}
let kind = entry
.file_type()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_TREE_UNAVAILABLE: {error}"))?;
if kind.is_symlink() || (!kind.is_dir() && !kind.is_file()) {
continue;
}
let entry_path = entry.path();
let relative_path = relative_posix(&repository, &entry_path)?;
let size_bytes = if kind.is_file() {
entry
.metadata()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_TREE_UNAVAILABLE: {error}"))?
.len()
} else {
0
};
projected.push(CodeTreeEntry {
path: relative_path,
name,
kind: if kind.is_dir() { "directory" } else { "file" },
size_bytes,
});
}
Ok(CodeTreeSnapshot {
schema: "hololake.code-channel-tree/v1",
channel_id: channel.channel_id,
path: relative,
truncated: projected.len() >= MAX_TREE_ENTRIES,
entries: projected,
})
}
fn read_code_file_at(
root: &Path,
input: ReadCodeChannelFileInput,
) -> Result<CodeFileProjection, String> {
let (channel, repository) = registered_repository(root, &input.channel_id)?;
let file = safe_repository_path(&repository, &input.path)?;
let metadata = fs::metadata(&file)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_FILE_UNAVAILABLE: {error}"))?;
if !metadata.is_file() || metadata.len() > MAX_CODE_FILE_BYTES || !is_supported_text_file(&file)
{
return Err("HOLOLAKE_CODE_CHANNEL_FILE_UNSUPPORTED".into());
}
let source = fs::read_to_string(&file)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_FILE_INVALID_UTF8: {error}"))?;
let format = file_format(&file);
let human_markdown = human_projection(&input.path, &format, &source);
Ok(CodeFileProjection {
schema: "hololake.code-channel-file-projection/v1",
channel_id: channel.channel_id,
path: input.path,
format,
source,
human_markdown,
size_bytes: metadata.len(),
})
}
fn registered_repository(
root: &Path,
channel_id: &str,
) -> Result<(CodeChannelEntry, PathBuf), String> {
let registry = read_registry(root)?;
let channel = registry
.channels
.into_iter()
.find(|entry| entry.channel_id == channel_id)
.ok_or("HOLOLAKE_CODE_CHANNEL_NOT_REGISTERED")?;
let repository = PathBuf::from(&channel.local_path)
.canonicalize()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REPOSITORY_UNAVAILABLE: {error}"))?;
if !repository.is_dir() {
return Err("HOLOLAKE_CODE_CHANNEL_REPOSITORY_UNAVAILABLE".into());
}
Ok((channel, repository))
}
fn safe_repository_path(root: &Path, relative: &str) -> Result<PathBuf, String> {
let relative_path = Path::new(relative);
if relative_path.is_absolute()
|| relative_path
.components()
.any(|component| !matches!(component, Component::Normal(_)))
{
if relative.is_empty() {
return Ok(root.to_path_buf());
}
return Err("HOLOLAKE_CODE_CHANNEL_PATH_INVALID".into());
}
if relative.is_empty() {
return Ok(root.to_path_buf());
}
let candidate = root.join(relative_path);
let metadata = fs::symlink_metadata(&candidate)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_PATH_UNAVAILABLE: {error}"))?;
if metadata.file_type().is_symlink() {
return Err("HOLOLAKE_CODE_CHANNEL_SYMLINK_DENIED".into());
}
let canonical = candidate
.canonicalize()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_PATH_UNAVAILABLE: {error}"))?;
if !canonical.starts_with(root) {
return Err("HOLOLAKE_CODE_CHANNEL_PATH_ESCAPE".into());
}
Ok(canonical)
}
fn relative_posix(root: &Path, path: &Path) -> Result<String, String> {
Ok(path
.strip_prefix(root)
.map_err(|_| "HOLOLAKE_CODE_CHANNEL_PATH_INVALID".to_string())?
.to_string_lossy()
.replace('\\', "/"))
}
fn is_supported_text_file(path: &Path) -> bool {
let name = path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("");
if ["README", "LICENSE", "Dockerfile", "Makefile", "Procfile"].contains(&name) {
return true;
}
matches!(
path.extension()
.and_then(|value| value.to_str())
.unwrap_or("")
.to_ascii_lowercase()
.as_str(),
"md" | "markdown"
| "txt"
| "json"
| "jsonl"
| "yaml"
| "yml"
| "toml"
| "hdlp"
| "rs"
| "ts"
| "tsx"
| "js"
| "jsx"
| "mjs"
| "cjs"
| "py"
| "sh"
| "zsh"
| "bash"
| "css"
| "scss"
| "html"
| "xml"
| "sql"
| "graphql"
| "go"
| "java"
| "kt"
| "swift"
| "c"
| "h"
| "cpp"
| "hpp"
| "proto"
| "ini"
| "conf"
| "env"
)
}
fn file_format(path: &Path) -> String {
path.extension()
.and_then(|value| value.to_str())
.filter(|value| !value.is_empty())
.unwrap_or("text")
.to_ascii_lowercase()
}
fn human_projection(path: &str, format: &str, source: &str) -> String {
if matches!(format, "md" | "markdown") {
return source.to_string();
}
let title = Path::new(path)
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("代码文件");
if format == "json" {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(source) {
return format!(
"# {title}\n\n> 来自代码频道的 JSON 结构,已转换为可读层级。\n\n{}",
json_as_markdown(&value, 2)
);
}
}
if matches!(format, "hdlp" | "yaml" | "yml" | "toml" | "ini" | "conf") {
let rows = source
.lines()
.filter_map(|line| {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
return None;
}
trimmed
.split_once(':')
.or_else(|| trimmed.split_once('='))
.map(|(key, value)| format!("| {} | {} |", key.trim(), value.trim()))
})
.take(120)
.collect::<Vec<_>>();
if !rows.is_empty() {
return format!(
"# {title}\n\n> 来自代码频道的结构化文件。原始机器字段已转成阅读表格。\n\n| 字段 | 内容 |\n| --- | --- |\n{}",
rows.join("\n")
);
}
}
format!(
"# {title}\n\n> 来自代码频道的 `{format}` 文件。下方保留原始内容,便于核验。\n\n```{format}\n{source}\n```"
)
}
fn json_as_markdown(value: &serde_json::Value, level: usize) -> String {
match value {
serde_json::Value::Object(map) => map
.iter()
.map(|(key, value)| match value {
serde_json::Value::Object(_) | serde_json::Value::Array(_) => format!(
"{} {key}\n\n{}",
"#".repeat(level.min(5)),
json_as_markdown(value, level + 1)
),
_ => format!("- **{key}**{}", scalar_json(value)),
})
.collect::<Vec<_>>()
.join("\n\n"),
serde_json::Value::Array(values) => values
.iter()
.enumerate()
.map(|(index, value)| match value {
serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
format!("{}. {}", index + 1, json_as_markdown(value, level + 1))
}
_ => format!("- {}", scalar_json(value)),
})
.collect::<Vec<_>>()
.join("\n"),
_ => scalar_json(value),
}
}
fn scalar_json(value: &serde_json::Value) -> String {
value
.as_str()
.map(ToOwned::to_owned)
.unwrap_or_else(|| value.to_string())
}
fn clone_at(root: &Path, input: CloneCodeChannelInput) -> Result<CodeChannelSnapshot, String> {
ensure_root(root)?;
let validated = validate_clone_url(&input.url)?;
let destination = root.join("repositories").join(format!(
"{}--{}",
safe_segment(&validated.owner),
safe_segment(&validated.repository)
));
if destination.exists() {
return Err("HOLOLAKE_CODE_CHANNEL_DESTINATION_EXISTS".into());
}
let output = Command::new("/usr/bin/git")
.env("GIT_TERMINAL_PROMPT", "0")
.env("GIT_ASKPASS", "/usr/bin/false")
.env("SSH_ASKPASS", "/usr/bin/false")
.env("GIT_CONFIG_NOSYSTEM", "1")
.args([
"-c",
"credential.helper=",
"-c",
"core.askPass=",
"clone",
"--origin",
"origin",
"--no-tags",
])
.arg(&validated.normalized)
.arg(&destination)
.output()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_CLONE_FAILED: {error}"))?;
if !output.status.success() {
if destination.starts_with(root.join("repositories")) {
let _ = fs::remove_dir_all(&destination);
}
return Err(format!(
"HOLOLAKE_CODE_CHANNEL_CLONE_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
let entry = entry_for_repository(
&destination,
"CLONED_HTTPS",
Some(validated.normalized),
Some(format!("{}/{}", validated.owner, validated.repository)),
)?;
upsert_entry(root, entry)?;
snapshot_at(root)
}
fn register_local_at(root: &Path, selected: &Path) -> Result<CodeChannelSnapshot, String> {
ensure_root(root)?;
let canonical = selected
.canonicalize()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_LOCAL_UNAVAILABLE: {error}"))?;
if !canonical.is_dir() {
return Err("HOLOLAKE_CODE_CHANNEL_LOCAL_INVALID".into());
}
let remote = git_optional(&canonical, &["remote", "get-url", "origin"])
.and_then(|value| redact_remote_url(value.trim()));
let entry = entry_for_repository(&canonical, "LOCAL_GIT", remote, None)?;
upsert_entry(root, entry)?;
snapshot_at(root)
}
struct RepositoryInspection {
git_head: String,
branch: String,
repository_clean: bool,
}
fn inspect_repository(path: &Path) -> Result<RepositoryInspection, String> {
let inside = git(path, &["rev-parse", "--is-inside-work-tree"], "INSPECT")?;
if inside.trim() != "true" {
return Err("HOLOLAKE_CODE_CHANNEL_NOT_GIT".into());
}
let git_head = git(path, &["rev-parse", "HEAD"], "READ_HEAD")?
.trim()
.to_string();
let branch = git_optional(path, &["symbolic-ref", "--short", "HEAD"])
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.unwrap_or_else(|| "DETACHED".into());
let repository_clean = git(path, &["status", "--porcelain"], "READ_STATUS")?
.trim()
.is_empty();
Ok(RepositoryInspection {
git_head,
branch,
repository_clean,
})
}
fn entry_for_repository(
path: &Path,
source_kind: &str,
remote_url: Option<String>,
explicit_name: Option<String>,
) -> Result<CodeChannelEntry, String> {
let inspection = inspect_repository(path)?;
let name = explicit_name.unwrap_or_else(|| {
path.file_name()
.and_then(|value| value.to_str())
.unwrap_or("代码频道")
.to_string()
});
Ok(CodeChannelEntry {
channel_id: format!("channel-{}", Uuid::new_v4()),
name,
source_kind: source_kind.into(),
local_path: path.to_string_lossy().into_owned(),
remote_url,
git_head: inspection.git_head,
branch: inspection.branch,
repository_clean: inspection.repository_clean,
registered_at_unix_ms: now_unix_ms()?,
})
}
fn validate_clone_url(raw: &str) -> Result<ValidatedCloneUrl, String> {
let raw = raw.trim();
if raw.len() > 2_048 {
return Err("HOLOLAKE_CODE_CHANNEL_URL_INVALID".into());
}
let mut parsed = Url::parse(raw).map_err(|_| "HOLOLAKE_CODE_CHANNEL_URL_INVALID")?;
if parsed.scheme() != "https"
|| !parsed.username().is_empty()
|| parsed.password().is_some()
|| parsed.query().is_some()
|| parsed.fragment().is_some()
|| parsed.port().is_some()
{
return Err("HOLOLAKE_CODE_CHANNEL_URL_INVALID".into());
}
let host = parsed
.host_str()
.ok_or("HOLOLAKE_CODE_CHANNEL_URL_INVALID")?
.to_lowercase();
if !ALLOWED_HOSTS.contains(&host.as_str()) {
return Err("HOLOLAKE_CODE_CHANNEL_HOST_NOT_TRUSTED".into());
}
let segments = parsed
.path_segments()
.ok_or("HOLOLAKE_CODE_CHANNEL_URL_INVALID")?
.filter(|segment| !segment.is_empty())
.collect::<Vec<_>>();
if segments.len() != 3 || segments[0] != "code" {
return Err("HOLOLAKE_CODE_CHANNEL_URL_INVALID".into());
}
let owner = segments[1].to_string();
let repository = segments[2]
.strip_suffix(".git")
.unwrap_or(segments[2])
.to_string();
if !valid_git_segment(&owner) || !valid_git_segment(&repository) {
return Err("HOLOLAKE_CODE_CHANNEL_URL_INVALID".into());
}
parsed.set_path(&format!("/code/{owner}/{repository}.git"));
Ok(ValidatedCloneUrl {
normalized: parsed.to_string(),
owner,
repository,
})
}
fn valid_git_segment(value: &str) -> bool {
!value.is_empty()
&& value.len() <= 100
&& value != "."
&& value != ".."
&& value
.chars()
.all(|character| character.is_ascii_alphanumeric() || "-_.".contains(character))
}
fn redact_remote_url(raw: &str) -> Option<String> {
let mut parsed = Url::parse(raw).ok()?;
if parsed.scheme() != "https" {
return None;
}
parsed.set_username("").ok()?;
parsed.set_password(None).ok()?;
parsed.set_query(None);
parsed.set_fragment(None);
Some(parsed.to_string())
}
fn safe_segment(value: &str) -> String {
value
.chars()
.map(|character| {
if character.is_ascii_alphanumeric() || "-_.".contains(character) {
character
} else {
'-'
}
})
.collect()
}
fn upsert_entry(root: &Path, entry: CodeChannelEntry) -> Result<(), String> {
let mut registry = read_registry(root)?;
registry
.channels
.retain(|existing| existing.local_path != entry.local_path);
registry.channels.push(entry);
write_registry(root, &registry)
}
fn read_registry(root: &Path) -> Result<CodeChannelRegistry, String> {
let path = root.join("channels.json");
if !path.exists() {
return Ok(CodeChannelRegistry {
schema: REGISTRY_SCHEMA.into(),
channels: Vec::new(),
});
}
let bytes = fs::read(&path)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_UNAVAILABLE: {error}"))?;
let registry: CodeChannelRegistry = serde_json::from_slice(&bytes)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_INVALID: {error}"))?;
if registry.schema != REGISTRY_SCHEMA {
return Err("HOLOLAKE_CODE_CHANNEL_REGISTRY_SCHEMA_INVALID".into());
}
Ok(registry)
}
fn write_registry(root: &Path, registry: &CodeChannelRegistry) -> Result<(), String> {
let bytes = serde_json::to_vec_pretty(registry)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_INVALID: {error}"))?;
let temporary = root.join(format!("channels.json.tmp-{}", Uuid::new_v4()));
let final_path = root.join("channels.json");
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.mode(0o600)
.open(&temporary)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_WRITE_FAILED: {error}"))?;
file.write_all(&bytes)
.and_then(|_| file.sync_all())
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_WRITE_FAILED: {error}"))?;
fs::rename(&temporary, &final_path)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_REGISTRY_WRITE_FAILED: {error}"))?;
Ok(())
}
fn git(root: &Path, args: &[&str], operation: &str) -> Result<String, String> {
let output = Command::new("/usr/bin/git")
.current_dir(root)
.env("GIT_TERMINAL_PROMPT", "0")
.args(args)
.output()
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_GIT_{operation}_FAILED: {error}"))?;
if !output.status.success() {
return Err(format!(
"HOLOLAKE_CODE_CHANNEL_GIT_{operation}_FAILED: {}",
String::from_utf8_lossy(&output.stderr).trim()
));
}
String::from_utf8(output.stdout)
.map_err(|error| format!("HOLOLAKE_CODE_CHANNEL_GIT_{operation}_INVALID_UTF8: {error}"))
}
fn git_optional(root: &Path, args: &[&str]) -> Option<String> {
let output = Command::new("/usr/bin/git")
.current_dir(root)
.env("GIT_TERMINAL_PROMPT", "0")
.args(args)
.output()
.ok()?;
output
.status
.success()
.then(|| String::from_utf8_lossy(&output.stdout).into_owned())
}
fn now_unix_ms() -> Result<u128, String> {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.map_err(|error| format!("HOLOLAKE_CLOCK_INVALID: {error}"))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn clone_url_is_normalized_to_official_https_git_endpoint() {
let parsed =
validate_clone_url("https://guanghulab.com/code/bingshuo/guanghu-ice-heart").unwrap();
assert_eq!(
parsed.normalized,
"https://guanghulab.com/code/bingshuo/guanghu-ice-heart.git"
);
assert_eq!(parsed.owner, "bingshuo");
assert_eq!(parsed.repository, "guanghu-ice-heart");
}
#[test]
fn clone_url_rejects_credentials_queries_and_unknown_hosts() {
assert!(
validate_clone_url("https://user:secret@guanghulab.com/code/bingshuo/repo").is_err()
);
assert!(
validate_clone_url("https://guanghulab.com/code/bingshuo/repo?token=secret").is_err()
);
assert!(validate_clone_url("https://example.com/code/bingshuo/repo").is_err());
}
#[test]
fn remote_redaction_removes_credentials_and_fragments() {
assert_eq!(
redact_remote_url("https://user:secret@example.com/a/b.git?x=1#readme"),
Some("https://example.com/a/b.git".into())
);
assert_eq!(redact_remote_url("git@example.com:a/b.git"), None);
}
#[test]
fn local_repository_registration_is_persistent_and_deduplicated() {
let root = tempdir().unwrap();
let repository = tempdir().unwrap();
ensure_root(root.path()).unwrap();
git(
repository.path(),
&["init", "--initial-branch=main"],
"TEST_INIT",
)
.unwrap();
git(
repository.path(),
&["config", "user.name", "Test"],
"TEST_CONFIG",
)
.unwrap();
git(
repository.path(),
&["config", "user.email", "test@example.invalid"],
"TEST_CONFIG",
)
.unwrap();
fs::write(repository.path().join("README.md"), "# test\n").unwrap();
git(repository.path(), &["add", "README.md"], "TEST_ADD").unwrap();
git(
repository.path(),
&["commit", "-m", "initial"],
"TEST_COMMIT",
)
.unwrap();
register_local_at(root.path(), repository.path()).unwrap();
register_local_at(root.path(), repository.path()).unwrap();
let snapshot = snapshot_at(root.path()).unwrap();
assert_eq!(snapshot.channels.len(), 1);
assert_eq!(snapshot.channels[0].branch, "main");
assert!(snapshot.channels[0].repository_clean);
}
#[test]
fn registered_repository_can_be_browsed_and_projected_as_knowledge() {
let root = tempdir().unwrap();
let repository = tempdir().unwrap();
ensure_root(root.path()).unwrap();
git(
repository.path(),
&["init", "--initial-branch=main"],
"TEST_INIT",
)
.unwrap();
git(
repository.path(),
&["config", "user.name", "Test"],
"TEST_CONFIG",
)
.unwrap();
git(
repository.path(),
&["config", "user.email", "test@example.invalid"],
"TEST_CONFIG",
)
.unwrap();
fs::create_dir(repository.path().join("config")).unwrap();
fs::write(
repository.path().join("config/system.json"),
r#"{"name":"光湖","state":"ready","routes":["native","mcp"]}"#,
)
.unwrap();
git(repository.path(), &["add", "."], "TEST_ADD").unwrap();
git(
repository.path(),
&["commit", "-m", "initial"],
"TEST_COMMIT",
)
.unwrap();
let snapshot = register_local_at(root.path(), repository.path()).unwrap();
let channel_id = snapshot.channels[0].channel_id.clone();
let tree = browse_at(
root.path(),
BrowseCodeChannelInput {
channel_id: channel_id.clone(),
path: None,
},
)
.unwrap();
assert!(tree
.entries
.iter()
.any(|entry| entry.name == "config" && entry.kind == "directory"));
let projection = read_code_file_at(
root.path(),
ReadCodeChannelFileInput {
channel_id,
path: "config/system.json".into(),
},
)
.unwrap();
assert_eq!(projection.format, "json");
assert!(projection
.human_markdown
.contains("来自代码频道的 JSON 结构"));
assert!(projection.human_markdown.contains("**state**ready"));
}
#[test]
fn code_tree_denies_parent_escape() {
let repository = tempdir().unwrap();
assert!(safe_repository_path(repository.path(), "../secret").is_err());
}
}