Compute the sync plan for one view: what to store and whether to declare. `remote` is the remote's parsed manifest, or `None` if the view does not exist on the remote (the remote log is empty). The fast-forward rule: the remote log must be a prefix of the local log. The suffix (everything beyond the prefix) is what needs storing — its dependencies are either earlier in the log or already on the
(
local: &ViewManifest,
remote: Option<&ViewManifest>,
_force: bool,
_is_leaf: bool,
)
| 196 | /// genuinely ambiguous (local is behind vs. local shrank), so it is left to the |
| 197 | /// prefix/divergence rules, which err toward "pull first, or `--force`". |
| 198 | pub fn plan_view_sync( |
| 199 | local: &ViewManifest, |
| 200 | remote: Option<&ViewManifest>, |
| 201 | _force: bool, |
| 202 | _is_leaf: bool, |
| 203 | ) -> Result<ViewSyncPlan, ViewSyncConflict> { |
| 204 | let remote = match remote { |
| 205 | None => { |
| 206 | return Ok(ViewSyncPlan { |
| 207 | suffix: local.changes.clone(), |
| 208 | declare: true, |
| 209 | forced: false, |
| 210 | shrink: false, |
| 211 | }); |
| 212 | } |
| 213 | Some(r) => r, |
| 214 | }; |
| 215 | |
| 216 | // View identity is immutable metadata. Membership is not compared as an |
| 217 | // ordered prefix: patches are nodes in a causal graph and independently |
| 218 | // added compatible nodes reconcile by set union. |
| 219 | if remote.scope != local.scope { |
| 220 | return Err(ViewSyncConflict::IdentityMismatch { |
| 221 | field: "scope", |
| 222 | local: local.scope.to_string(), |
| 223 | remote: remote.scope.to_string(), |
| 224 | }); |
| 225 | } |
| 226 | if remote.parent != local.parent { |
| 227 | let show = |p: &Option<String>| p.clone().unwrap_or_else(|| "(none)".to_string()); |
| 228 | return Err(ViewSyncConflict::IdentityMismatch { |
| 229 | field: "parent", |
| 230 | local: show(&local.parent), |
| 231 | remote: show(&remote.parent), |
| 232 | }); |
| 233 | } |
| 234 | |
| 235 | let remote_set: HashSet<Hash> = remote.changes.iter().copied().collect(); |
| 236 | let local_set: HashSet<Hash> = local.changes.iter().copied().collect(); |
| 237 | let suffix = local |
| 238 | .changes |
| 239 | .iter() |
| 240 | .filter(|hash| !remote_set.contains(hash)) |
| 241 | .copied() |
| 242 | .collect(); |
| 243 | Ok(ViewSyncPlan { |
| 244 | suffix, |
| 245 | // Normal sync is monotonic. If the remote already contains every local |
| 246 | // member, there is nothing to publish even when the remote union is |
| 247 | // larger. Otherwise the server unions the proposal with its current set. |
| 248 | declare: !local_set.is_subset(&remote_set), |
| 249 | forced: false, |
| 250 | shrink: false, |
| 251 | }) |
| 252 | } |
| 253 | |
| 254 | // Manifest Support Detection |
| 255 |