diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index 64c026931..afdb551fc 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -309,9 +309,14 @@ impl MonoServiceLogic { loop { let clean_path = MonoServiceLogic::clean_path_str(&path_str); + let ref_path = if clean_path == "/" || clean_path.starts_with('/') { + clean_path + } else { + format!("/{clean_path}") + }; ref_updates.push(RefUpdate { - path: clean_path, + path: ref_path, tree_id: updated_tree_hash, }); @@ -4020,6 +4025,30 @@ mod test { assert!(paths.contains(&"/test")); } + #[test] + fn test_build_result_by_chain_normalizes_relative_paths_for_ref_updates() { + let old_hash = ObjectHash::from_str("1111111111111111111111111111111111111111").unwrap(); + let updated_child_hash = + ObjectHash::from_str("2222222222222222222222222222222222222222").unwrap(); + let item = TreeItem::new(TreeItemMode::Tree, old_hash, "src".to_string()); + let tree = Tree::from_tree_items(vec![item]).expect("tree should build"); + let update_chain = vec![Arc::new(tree)]; + + let result = MonoServiceLogic::build_result_by_chain( + PathBuf::from("project/buck2_test/src"), + update_chain, + updated_child_hash, + ) + .expect("build_result_by_chain should succeed"); + + let paths: Vec<&str> = result.ref_updates.iter().map(|r| r.path.as_str()).collect(); + assert!(paths.contains(&"/project/buck2_test/src")); + assert!(paths.contains(&"/project/buck2_test")); + + let selected = MonoApiService::ref_update_tree_id_for_path(&result, "/project/buck2_test"); + assert!(selected.is_some()); + } + #[tokio::test] async fn test_process_ref_updates_logic() { let ref_update = RefUpdate { diff --git a/ceres/src/build_trigger/changes_calculator.rs b/ceres/src/build_trigger/changes_calculator.rs index 0f11b5457..4ca8a2446 100644 --- a/ceres/src/build_trigger/changes_calculator.rs +++ b/ceres/src/build_trigger/changes_calculator.rs @@ -353,6 +353,56 @@ mod tests { ); } + #[test] + fn test_build_changes_keeps_added_paths_for_create_entry_variants() { + let changes = build_changes_for_repo( + "/project/buck2_test", + vec![ + ClDiffFile::New( + PathBuf::from("/project/buck2_test/src/new_file_abs.rs"), + ObjectHash::from_str("7777777777777777777777777777777777777777").unwrap(), + ), + ClDiffFile::New( + PathBuf::from("project/buck2_test/src/new_file_prefixed.rs"), + ObjectHash::from_str("8888888888888888888888888888888888888888").unwrap(), + ), + ClDiffFile::New( + PathBuf::from("src/new_file_relative.rs"), + ObjectHash::from_str("9999999999999999999999999999999999999999").unwrap(), + ), + ], + ) + .unwrap(); + + assert_eq!( + changes, + vec![ + Status::Added(ProjectRelativePath::new("src/new_file_abs.rs")), + Status::Added(ProjectRelativePath::new("src/new_file_prefixed.rs")), + Status::Added(ProjectRelativePath::new("src/new_file_relative.rs")), + ] + ); + } + + #[test] + fn test_build_changes_keeps_added_gitkeep_for_new_directory() { + let changes = build_changes_for_repo( + "/project/buck2_test", + vec![ClDiffFile::New( + PathBuf::from("project/buck2_test/src/new_dir/.gitkeep"), + ObjectHash::from_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), + )], + ) + .unwrap(); + + assert_eq!( + changes, + vec![Status::Added(ProjectRelativePath::new( + "src/new_dir/.gitkeep" + ))] + ); + } + #[test] fn test_detect_single_level_prefixed_candidate_returns_unique_match() { let tempdir = TempDir::new().expect("create tempdir"); diff --git a/moon/apps/web/components/CodeView/NewCodeView/NewCodeView.tsx b/moon/apps/web/components/CodeView/NewCodeView/NewCodeView.tsx index a6819d132..f67623658 100644 --- a/moon/apps/web/components/CodeView/NewCodeView/NewCodeView.tsx +++ b/moon/apps/web/components/CodeView/NewCodeView/NewCodeView.tsx @@ -34,6 +34,7 @@ const NewCodeView = ({ currentPath = '', onClose, defaultType = 'file' }: NewCod const lastSlashIndex = fullPath.lastIndexOf('/') const parentPath = lastSlashIndex > 0 ? fullPath.substring(0, lastSlashIndex) : lastSlashIndex === 0 ? '/' : '' + const normalizedParentPath = parentPath === '' ? '/' : parentPath.startsWith('/') ? parentPath : `/${parentPath}` // Use explicit name if provided, otherwise extract from path const fileName = name || (lastSlashIndex >= 0 ? fullPath.substring(lastSlashIndex + 1) : fullPath) @@ -41,7 +42,7 @@ const NewCodeView = ({ currentPath = '', onClose, defaultType = 'file' }: NewCod createEntryHook.mutate( { name: fileName, - path: parentPath, + path: normalizedParentPath, is_directory: fileType === 'folder', content: fileType === 'file' ? content : '', author_email: currentUser?.email,