Skip to main content

git_workflow/commands/
abandon.rs

1//! `gw abandon` command - Abandon current changes and return to home branch
2
3use super::helpers;
4use crate::error::{GwError, Result};
5use crate::git;
6use crate::output;
7use crate::state::{RepoType, SyncState, WorkingDirState};
8
9/// Execute the `abandon` command
10pub fn run(verbose: bool) -> Result<()> {
11    // Ensure we're in a git repo
12    if !git::is_git_repo() {
13        return Err(GwError::NotAGitRepository);
14    }
15
16    // Check for detached HEAD
17    if git::is_detached_head() {
18        return Err(GwError::Other(
19            "Cannot run from detached HEAD. Checkout a branch first.".to_string(),
20        ));
21    }
22
23    let repo_type = RepoType::detect()?;
24    let home_branch = repo_type.home_branch();
25    let current = git::current_branch()?;
26    let working_dir = WorkingDirState::detect();
27
28    println!();
29    output::info(&format!("Current branch: {}", output::bold(&current)));
30    output::info(&format!("Home branch: {}", output::bold(home_branch)));
31
32    // Check what will be affected
33    let has_changes = !working_dir.is_clean();
34    let has_untracked = git::has_untracked_files();
35    let sync_state = SyncState::detect(&current).unwrap_or(SyncState::NoUpstream);
36
37    // Detect default remote branch
38    let default_remote = git::get_default_remote_branch()?;
39    let default_branch = default_remote.strip_prefix("origin/").unwrap_or("main");
40
41    // Already on home branch with clean working directory and no untracked files
42    if !has_changes && !has_untracked && current == home_branch {
43        output::success("Already on home branch with clean working directory");
44        // Still sync with default remote (fast-forward only; stop on divergence)
45        git::fetch_prune(verbose)?;
46        helpers::pull_with_output(&default_remote, default_branch, verbose)?;
47        return Ok(());
48    }
49
50    // Warn about what will be lost. Untracked files get their own message
51    // below, so only mention tracked changes here to avoid double-reporting the
52    // untracked-only case.
53    if working_dir.has_tracked_changes() {
54        output::warn(&format!(
55            "Uncommitted changes will be DISCARDED: {}",
56            working_dir.description()
57        ));
58    }
59    if has_untracked {
60        output::warn("Untracked files will be DELETED");
61    }
62
63    // Warn about unpushed commits (they'll remain on the branch, not lost)
64    if let SyncState::HasUnpushedCommits { count } = sync_state {
65        output::warn(&format!(
66            "Branch '{}' has {} unpushed commit(s). They will remain on the branch.",
67            current, count
68        ));
69    }
70
71    // Discard changes if any (including untracked files)
72    if has_changes || has_untracked {
73        output::info("Discarding changes...");
74        git::discard_all_changes(verbose)?;
75        output::success("Changes discarded");
76    }
77
78    // Switch to home branch if not already there
79    if current != home_branch {
80        if !git::branch_exists(home_branch) {
81            output::info(&format!("Creating home branch from {}...", default_remote));
82            git::checkout_new_branch(home_branch, &default_remote, verbose)?;
83        } else {
84            git::checkout(home_branch, verbose)?;
85        }
86        output::success(&format!("Switched to {}", output::bold(home_branch)));
87    }
88
89    // Sync with default remote (fast-forward only; stop on divergence)
90    git::fetch_prune(verbose)?;
91    helpers::pull_with_output(&default_remote, default_branch, verbose)?;
92
93    output::ready("Ready", home_branch);
94    output::hints(&["gw new feature/your-feature  # Start fresh"]);
95
96    Ok(())
97}