Execute stash push (save changes).
(
&self,
repo: &mut Repository,
message: Option<String>,
include_untracked: bool,
keep: bool,
)
| 397 | |
| 398 | /// Execute stash push (save changes). |
| 399 | fn run_push( |
| 400 | &self, |
| 401 | repo: &mut Repository, |
| 402 | message: Option<String>, |
| 403 | include_untracked: bool, |
| 404 | keep: bool, |
| 405 | ) -> CliResult<()> { |
| 406 | // Check for uncommitted changes |
| 407 | let status = repo |
| 408 | .status(StatusOptions::default()) |
| 409 | .map_err(CliError::Repository)?; |
| 410 | |
| 411 | if status.is_clean() { |
| 412 | print_warning("No local changes to save"); |
| 413 | return Ok(()); |
| 414 | } |
| 415 | |
| 416 | // Get current view for metadata |
| 417 | let source_view = repo.current_view().to_string(); |
| 418 | |
| 419 | // Generate stash message |
| 420 | let stash_message = message |
| 421 | .or_else(|| self.message.clone()) |
| 422 | .unwrap_or_else(|| DEFAULT_STASH_MESSAGE.to_string()); |
| 423 | |
| 424 | // Create stash view name with metadata |
| 425 | let timestamp = Utc::now().timestamp_millis(); |
| 426 | let safe_source = source_view.replace('/', "-"); |
| 427 | let safe_message = stash_message |
| 428 | .chars() |
| 429 | .take(30) |
| 430 | .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_' || *c == ' ') |
| 431 | .collect::<String>() |
| 432 | .replace(' ', "-"); |
| 433 | |
| 434 | let stash_name = format!( |
| 435 | "{}{}_{}_{}", |
| 436 | STASH_PREFIX, safe_source, timestamp, safe_message |
| 437 | ); |
| 438 | |
| 439 | // Create orphan stash view |
| 440 | repo.create_view(&stash_name) |
| 441 | .map_err(CliError::Repository)?; |
| 442 | |
| 443 | // ── Save dirty files as raw bytes to a sidecar directory ──────── |
| 444 | // |
| 445 | // Stash is a **pure working-copy operation**. We save the exact |
| 446 | // bytes of every dirty file to `.atomic/stashes/<stash_name>/`. |
| 447 | // This is completely immune to graph mutations — even if the user |
| 448 | // records new changes between push and pop, pop will restore the |
| 449 | // exact content that was stashed. |
| 450 | // |
| 451 | // We do NOT go through the graph/record/apply machinery at all. |
| 452 | |
| 453 | let stash_dir = repo.dot_dir().join("stashes").join(&stash_name); |
| 454 | std::fs::create_dir_all(&stash_dir).map_err(|e| { |
| 455 | CliError::Internal(anyhow::anyhow!("Failed to create stash dir: {}", e)) |
| 456 | })?; |
no test coverage detected