feat(onboarding): input validation + editable targets (#160)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 4m6s
CI / Deploy Dashboard (push) Successful in 3m11s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 4m6s
CI / Deploy Dashboard (push) Successful in 3m11s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped
This commit was merged in pull request #160.
This commit is contained in:
@@ -80,7 +80,10 @@ impl GitOps {
|
||||
|
||||
#[tracing::instrument(skip_all, fields(repo_name = %repo_name))]
|
||||
pub fn clone_or_fetch(&self, git_url: &str, repo_name: &str) -> Result<PathBuf, AgentError> {
|
||||
let repo_path = self.base_path.join(repo_name);
|
||||
// Names can contain slashes or other path-hostile characters (a target
|
||||
// named after a repo path, say); collapse to one safe directory segment
|
||||
// so the clone path never nests or breaks.
|
||||
let repo_path = self.base_path.join(sanitize_repo_dir(repo_name));
|
||||
|
||||
if repo_path.exists() {
|
||||
tracing::info!("fetching updates for existing repo");
|
||||
@@ -253,3 +256,46 @@ pub struct DiffFile {
|
||||
pub path: String,
|
||||
pub hunks: String,
|
||||
}
|
||||
|
||||
/// Collapse a repository name into a single filesystem-safe directory segment.
|
||||
/// Names may carry slashes or other path-hostile characters (a target named
|
||||
/// after a repo path, for instance); those would otherwise nest or break the
|
||||
/// clone path, so map anything outside `[A-Za-z0-9._-]` to `_`.
|
||||
fn sanitize_repo_dir(name: &str) -> String {
|
||||
let mapped: String = name
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
|
||||
c
|
||||
} else {
|
||||
'_'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let trimmed = mapped.trim_matches(|c| c == '.' || c == '_');
|
||||
if trimmed.is_empty() {
|
||||
"repo".to_string()
|
||||
} else {
|
||||
trimmed.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::sanitize_repo_dir;
|
||||
|
||||
#[test]
|
||||
fn sanitizes_path_hostile_names() {
|
||||
assert_eq!(
|
||||
sanitize_repo_dir("zephyr-example-app"),
|
||||
"zephyr-example-app"
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_repo_dir("ChristianRinn/bare_metal_stm32f411xe"),
|
||||
"ChristianRinn_bare_metal_stm32f411xe"
|
||||
);
|
||||
assert_eq!(sanitize_repo_dir("../../etc/passwd"), "etc_passwd");
|
||||
assert_eq!(sanitize_repo_dir("a b:c"), "a_b_c");
|
||||
assert_eq!(sanitize_repo_dir("///"), "repo");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user