Async implementation of the push command.
(&self)
| 486 | |
| 487 | /// Async implementation of the push command. |
| 488 | async fn run_async(&self) -> CliResult<()> { |
| 489 | // Find and open repository |
| 490 | let repo_root = find_repository_root()?; |
| 491 | let repo = Repository::open(&repo_root).map_err(CliError::Repository)?; |
| 492 | |
| 493 | // Resolve remote name, URL, and identity hint |
| 494 | let (remote_name, remote_url, identity_hint) = self.resolve_remote_url(&repo)?; |
| 495 | |
| 496 | // Determine views: the leaf we push, and its name on the remote. |
| 497 | let local_view = self.get_local_view(&repo); |
| 498 | let remote_view = self.get_remote_view(&local_view); |
| 499 | |
| 500 | // Print header |
| 501 | println!( |
| 502 | "Pushing to {} ({})", |
| 503 | style_view(&remote_name), |
| 504 | hint(&remote_url) |
| 505 | ); |
| 506 | |
| 507 | // Fail fast if we have no usable credentials for this remote. A push is |
| 508 | // a write that always requires auth; without a credential the server's |
| 509 | // negotiation endpoints return an ambiguous 404 (private projects are |
| 510 | // deliberately masked), which surfaces as a misleading "view not found". |
| 511 | // Catching the missing credential here gives the user an accurate, |
| 512 | // actionable error instead. |
| 513 | crate::commands::auth::check_push_credentials(&remote_url, identity_hint.as_deref())?; |
| 514 | |
| 515 | // Build the local ancestor chain, root → leaf. A draft's parent must |
| 516 | // exist on the remote before the draft's manifest can reference it, |
| 517 | // so the whole chain is synced in order. |
| 518 | let chain = build_view_chain(&local_view, |name| { |
| 519 | repo.get_view_info(name).map(|info| info.parent_name) |
| 520 | }) |
| 521 | .map_err(|e| match e { |
| 522 | ChainError::Cycle { view } => CliError::InvalidRepository { |
| 523 | reason: format!("view parent chain contains a cycle at '{}'", view), |
| 524 | }, |
| 525 | ChainError::Lookup(err) => CliError::Repository(err), |
| 526 | })?; |
| 527 | |
| 528 | // Connect to remote |
| 529 | let spinner = create_spinner("Connecting to remote..."); |
| 530 | let config = self |
| 531 | .build_remote_config(&remote_url, identity_hint.as_deref()) |
| 532 | .await; |
| 533 | let remote = HttpRemote::with_config(&remote_url, config).map_err(|e| { |
| 534 | finish_error(&spinner, "Failed to connect"); |
| 535 | convert_remote_error(e, &remote_url) |
| 536 | })?; |
| 537 | finish_success(&spinner, "Connected"); |
| 538 | |
| 539 | // Fetch remote view metadata only. Push does not import remote graph |
| 540 | // nodes or mutate local view metadata: doing so would change the current |
| 541 | // view's effective closure without materializing its working copy. The |
| 542 | // server performs the local∪remote set union when publishing refs. |
| 543 | let remote_names: Vec<String> = chain |
| 544 | .iter() |
| 545 | .map(|name| { |
no test coverage detected