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