Skip to content

Commit 58e194d

Browse files
authored
fix: Fix the update logic for missing paths. (#1893)
reduce parms Signed-off-by: Ruizhi Huang <231220075@smail.nju.edu.cn>
1 parent c1197cd commit 58e194d

File tree

1 file changed

+245
-65
lines changed

1 file changed

+245
-65
lines changed

ceres/src/api_service/mono_api_service.rs

Lines changed: 245 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ pub struct RefUpdate {
140140
tree_id: ObjectHash,
141141
}
142142

143+
struct ApplyChangeContext<'a> {
144+
components: &'a [String],
145+
chain_paths: &'a [PathBuf],
146+
chain_trees: &'a [Tree],
147+
tree_cache: &'a mut HashMap<PathBuf, Tree>,
148+
new_trees: &'a mut HashMap<ObjectHash, Tree>,
149+
}
150+
143151
/// `MonoServiceLogic` is a helper struct for `MonoApiService` containing stateless logic.
144152
///
145153
/// It encapsulates the pure logic methods of `MonoApiService` that do not depend on
@@ -1479,7 +1487,7 @@ impl MonoApiService {
14791487
};
14801488

14811489
// Build chain of trees from root to parent, using updated cache when available
1482-
let mut components: Vec<String> = parent_path
1490+
let components: Vec<String> = parent_path
14831491
.components()
14841492
.filter_map(|c| match c {
14851493
std::path::Component::RootDir => None,
@@ -1496,13 +1504,12 @@ impl MonoApiService {
14961504
];
14971505

14981506
let mut cursor = PathBuf::from("/");
1499-
for comp in &components {
1500-
cursor = cursor.join(comp);
1507+
let mut missing_components: Option<Vec<String>> = None;
1508+
for (idx, comp) in components.iter().enumerate() {
15011509
let parent_tree = chain_trees
15021510
.last()
15031511
.ok_or_else(|| GitError::CustomError("Empty tree chain".to_string()))?;
15041512

1505-
// Try to find existing child directory; if missing, create an empty tree placeholder
15061513
let maybe_child = parent_tree.tree_items.iter().find(|it| it.name == *comp);
15071514
let child_tree = if let Some(child_item) = maybe_child {
15081515
if child_item.mode != TreeItemMode::Tree {
@@ -1518,6 +1525,7 @@ impl MonoApiService {
15181525
comp
15191526
)));
15201527
}
1528+
cursor = cursor.join(comp);
15211529
let child_hash = child_item.id;
15221530
if let Some(cached) = tree_cache.get(&cursor) {
15231531
cached.clone()
@@ -1542,26 +1550,30 @@ impl MonoApiService {
15421550
Tree::from_mega_model(model)
15431551
}
15441552
} else {
1545-
// Absent in target head: create an empty directory tree and continue
1546-
let empty = Tree::from_tree_items(Vec::new())
1547-
.map_err(|e| GitError::CustomError(e.to_string()))?;
1548-
// Cache the newly created empty directory so subsequent diffs within
1549-
// the same path chain reuse the same tree instance.
1550-
debug!(
1551-
cl_link = %cl.link,
1552-
path_component = %comp,
1553-
cursor = %cursor.to_string_lossy(),
1554-
"apply_changes: synthesize missing directory"
1555-
);
1556-
tree_cache.insert(cursor.clone(), empty.clone());
1557-
new_trees.insert(empty.id, empty.clone());
1558-
empty
1553+
missing_components = Some(components[idx..].to_vec());
1554+
break;
15591555
};
15601556

15611557
chain_paths.push(cursor.clone());
15621558
chain_trees.push(child_tree);
15631559
}
15641560

1561+
if let Some(missing) = missing_components {
1562+
let mut ctx = ApplyChangeContext {
1563+
components: &components,
1564+
chain_paths: &chain_paths,
1565+
chain_trees: &chain_trees,
1566+
tree_cache: &mut tree_cache,
1567+
new_trees: &mut new_trees,
1568+
};
1569+
if let Some(updated_root) =
1570+
Self::apply_missing_path_update(&cl.link, missing, op, file_name, &mut ctx)?
1571+
{
1572+
root_tree = updated_root;
1573+
}
1574+
continue;
1575+
}
1576+
15651577
let parent_dir_abs = cursor.clone();
15661578

15671579
// Update parent tree with the file change
@@ -1588,7 +1600,7 @@ impl MonoApiService {
15881600
}
15891601
}
15901602

1591-
let mut updated_tree =
1603+
let updated_tree =
15921604
Tree::from_tree_items(items).map_err(|e| GitError::CustomError(e.to_string()))?;
15931605
// If parent tree id did not change (no-op), skip propagation for this diff.
15941606
if updated_tree.id == parent_tree.id {
@@ -1601,54 +1613,23 @@ impl MonoApiService {
16011613
);
16021614
continue;
16031615
}
1604-
tree_cache.insert(parent_dir_abs, updated_tree.clone());
1605-
new_trees.insert(updated_tree.id, updated_tree.clone());
1616+
Self::record_tree(
1617+
parent_dir_abs,
1618+
&updated_tree,
1619+
&mut tree_cache,
1620+
&mut new_trees,
1621+
);
16061622

16071623
// Propagate updated hashes up to root
1608-
components.reverse();
1609-
for (idx, comp) in components.iter().enumerate() {
1610-
let parent_index = chain_trees
1611-
.len()
1612-
.checked_sub(idx + 1)
1613-
.ok_or_else(|| GitError::CustomError("Tree chain underflow".to_string()))?;
1614-
1615-
let mut parent_tree = chain_trees[parent_index].clone();
1616-
let mut parent_items = parent_tree.tree_items.clone();
1617-
if let Some(pos) = parent_items.iter().position(|it| it.name == *comp) {
1618-
parent_items[pos].id = updated_tree.id;
1619-
} else {
1620-
// Missing entry: create a new directory item pointing to updated child
1621-
parent_items.push(TreeItem::new(
1622-
TreeItemMode::Tree,
1623-
updated_tree.id,
1624-
comp.clone(),
1625-
));
1626-
// Keep deterministic ordering
1627-
parent_items.sort_by(|a, b| a.name.cmp(&b.name));
1628-
debug!(
1629-
cl_link = %cl.link,
1630-
parent_path = %chain_paths
1631-
.get(parent_index)
1632-
.map(|p| p.to_string_lossy().to_string())
1633-
.unwrap_or_else(|| "<missing>".into()),
1634-
created_entry = %comp,
1635-
"apply_changes: inserted missing parent entry"
1636-
);
1637-
}
1638-
1639-
parent_tree = Tree::from_tree_items(parent_items)
1640-
.map_err(|e| GitError::CustomError(e.to_string()))?;
1641-
1642-
let parent_path_idx = chain_paths.get(parent_index).cloned().ok_or_else(|| {
1643-
GitError::CustomError("Tree path chain underflow".to_string())
1644-
})?;
1645-
tree_cache.insert(parent_path_idx.clone(), parent_tree.clone());
1646-
new_trees.insert(parent_tree.id, parent_tree.clone());
1647-
updated_tree = parent_tree;
1648-
}
1649-
1650-
// After propagation, updated_tree is the new root
1651-
root_tree = updated_tree;
1624+
root_tree = Self::propagate_up(
1625+
&cl.link,
1626+
updated_tree,
1627+
&components,
1628+
&chain_paths,
1629+
&chain_trees,
1630+
&mut tree_cache,
1631+
&mut new_trees,
1632+
)?;
16521633
}
16531634

16541635
let result = TreeUpdateResult {
@@ -1673,6 +1654,205 @@ impl MonoApiService {
16731654
.await
16741655
}
16751656

1657+
fn apply_missing_path_update(
1658+
cl_link: &str,
1659+
missing: Vec<String>,
1660+
op: Option<ObjectHash>,
1661+
file_name: &str,
1662+
ctx: &mut ApplyChangeContext<'_>,
1663+
) -> Result<Option<Tree>, GitError> {
1664+
debug_assert!(
1665+
!missing.iter().any(|c| c == file_name),
1666+
"missing path components should not include file name"
1667+
);
1668+
if op.is_none() {
1669+
debug!(
1670+
cl_link,
1671+
missing_path = %missing.join("/"),
1672+
"apply_changes: delete on missing path (no-op)"
1673+
);
1674+
return Ok(None);
1675+
}
1676+
1677+
let new_hash = op.ok_or_else(|| {
1678+
GitError::CustomError("Missing blob hash for new/modified file".to_string())
1679+
})?;
1680+
1681+
if missing.is_empty() {
1682+
// No missing directories: update directly under the last existing parent.
1683+
let parent_path = ctx.chain_paths.last().cloned().unwrap_or_else(PathBuf::new);
1684+
let parent_tree = ctx
1685+
.chain_trees
1686+
.last()
1687+
.cloned()
1688+
.ok_or_else(|| GitError::CustomError("Root tree missing".to_string()))?;
1689+
let updated_tree = Self::update_parent_tree(
1690+
cl_link,
1691+
&parent_tree,
1692+
file_name,
1693+
TreeItemMode::Blob,
1694+
new_hash,
1695+
None,
1696+
)?;
1697+
Self::record_tree(parent_path, &updated_tree, ctx.tree_cache, ctx.new_trees);
1698+
1699+
return Ok(Some(Self::propagate_up(
1700+
cl_link,
1701+
updated_tree,
1702+
ctx.components,
1703+
ctx.chain_paths,
1704+
ctx.chain_trees,
1705+
ctx.tree_cache,
1706+
ctx.new_trees,
1707+
)?));
1708+
}
1709+
1710+
// Build missing subtree from leaf (parent dir) upward without empty trees.
1711+
let file_item = TreeItem::new(TreeItemMode::Blob, new_hash, file_name.to_string());
1712+
let mut updated_tree = Tree::from_tree_items(vec![file_item])
1713+
.map_err(|e| GitError::CustomError(e.to_string()))?;
1714+
1715+
let mut missing_paths: Vec<PathBuf> = Vec::new();
1716+
let mut base = ctx.chain_paths.last().cloned().unwrap_or_else(PathBuf::new);
1717+
for comp in &missing {
1718+
base = base.join(comp);
1719+
missing_paths.push(base.clone());
1720+
}
1721+
1722+
if let Some(parent_path) = missing_paths.last() {
1723+
Self::record_tree(
1724+
parent_path.clone(),
1725+
&updated_tree,
1726+
ctx.tree_cache,
1727+
ctx.new_trees,
1728+
);
1729+
} else {
1730+
Self::record_tree(PathBuf::new(), &updated_tree, ctx.tree_cache, ctx.new_trees);
1731+
}
1732+
1733+
for (child_name, path) in missing
1734+
.iter()
1735+
.rev()
1736+
.skip(1)
1737+
.zip(missing_paths.iter().rev().skip(1))
1738+
{
1739+
let wrapper = Tree::from_tree_items(vec![TreeItem::new(
1740+
TreeItemMode::Tree,
1741+
updated_tree.id,
1742+
child_name.clone(),
1743+
)])
1744+
.map_err(|e| GitError::CustomError(e.to_string()))?;
1745+
updated_tree = wrapper;
1746+
Self::record_tree(path.clone(), &updated_tree, ctx.tree_cache, ctx.new_trees);
1747+
}
1748+
1749+
// Attach the newly built subtree to the last existing parent.
1750+
let parent_tree = ctx
1751+
.chain_trees
1752+
.last()
1753+
.cloned()
1754+
.ok_or_else(|| GitError::CustomError("Root tree missing".to_string()))?;
1755+
let attach_name = missing
1756+
.first()
1757+
.ok_or_else(|| GitError::CustomError("Missing component chain empty".to_string()))?;
1758+
updated_tree = Self::update_parent_tree(
1759+
cl_link,
1760+
&parent_tree,
1761+
attach_name,
1762+
TreeItemMode::Tree,
1763+
updated_tree.id,
1764+
None,
1765+
)?;
1766+
let parent_path = ctx.chain_paths.last().cloned().unwrap_or_else(PathBuf::new);
1767+
Self::record_tree(parent_path, &updated_tree, ctx.tree_cache, ctx.new_trees);
1768+
1769+
Ok(Some(Self::propagate_up(
1770+
cl_link,
1771+
updated_tree,
1772+
ctx.components,
1773+
ctx.chain_paths,
1774+
ctx.chain_trees,
1775+
ctx.tree_cache,
1776+
ctx.new_trees,
1777+
)?))
1778+
}
1779+
1780+
fn update_parent_tree(
1781+
cl_link: &str,
1782+
parent_tree: &Tree,
1783+
name: &str,
1784+
mode: TreeItemMode,
1785+
id: ObjectHash,
1786+
debug_parent_path: Option<&PathBuf>,
1787+
) -> Result<Tree, GitError> {
1788+
let mut parent_items = parent_tree.tree_items.clone();
1789+
if let Some(pos) = parent_items.iter().position(|it| it.name == name) {
1790+
parent_items[pos].id = id;
1791+
} else {
1792+
parent_items.push(TreeItem::new(mode, id, name.to_string()));
1793+
parent_items.sort_by(|a, b| a.name.cmp(&b.name));
1794+
if let Some(path) = debug_parent_path {
1795+
debug!(
1796+
cl_link,
1797+
parent_path = %path.to_string_lossy(),
1798+
created_entry = %name,
1799+
"apply_changes: inserted missing parent entry"
1800+
);
1801+
}
1802+
}
1803+
1804+
Tree::from_tree_items(parent_items).map_err(|e| GitError::CustomError(e.to_string()))
1805+
}
1806+
1807+
fn record_tree(
1808+
path: PathBuf,
1809+
tree: &Tree,
1810+
tree_cache: &mut HashMap<PathBuf, Tree>,
1811+
new_trees: &mut HashMap<ObjectHash, Tree>,
1812+
) {
1813+
tree_cache.insert(path, tree.clone());
1814+
new_trees.insert(tree.id, tree.clone());
1815+
}
1816+
1817+
fn propagate_up(
1818+
cl_link: &str,
1819+
mut updated_tree: Tree,
1820+
components: &[String],
1821+
chain_paths: &[PathBuf],
1822+
chain_trees: &[Tree],
1823+
tree_cache: &mut HashMap<PathBuf, Tree>,
1824+
new_trees: &mut HashMap<ObjectHash, Tree>,
1825+
) -> Result<Tree, GitError> {
1826+
debug_assert!(
1827+
components.len() >= chain_trees.len().saturating_sub(1),
1828+
"components length must cover parent chain"
1829+
);
1830+
1831+
for parent_index in (0..chain_trees.len().saturating_sub(1)).rev() {
1832+
let comp = components
1833+
.get(parent_index)
1834+
.ok_or_else(|| GitError::CustomError("Tree path chain underflow".to_string()))?;
1835+
1836+
let parent_tree = Self::update_parent_tree(
1837+
cl_link,
1838+
&chain_trees[parent_index],
1839+
comp,
1840+
TreeItemMode::Tree,
1841+
updated_tree.id,
1842+
chain_paths.get(parent_index),
1843+
)?;
1844+
1845+
let parent_path_idx = chain_paths
1846+
.get(parent_index)
1847+
.cloned()
1848+
.ok_or_else(|| GitError::CustomError("Tree path chain underflow".to_string()))?;
1849+
Self::record_tree(parent_path_idx, &parent_tree, tree_cache, new_trees);
1850+
updated_tree = parent_tree;
1851+
}
1852+
1853+
Ok(updated_tree)
1854+
}
1855+
16761856
/// Merges a CL without checking for conflicts.
16771857
/// Caller is responsible for ensuring no conflicts exist before calling this method.
16781858
async fn merge_cl_unchecked(&self, username: &str, cl: mega_cl::Model) -> Result<(), GitError> {

0 commit comments

Comments
 (0)