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    // Fetch before switching so the home ref can be fast-forwarded first.
79    git::fetch_prune(verbose)?;
80
81    // Switch to home branch if not already there
82    if current != home_branch {
83        if !git::branch_exists(home_branch) {
84            output::info(&format!("Creating home branch from {}...", default_remote));
85            git::checkout_new_branch(home_branch, &default_remote, verbose)?;
86        } else {
87            // Ref first, checkout second: one working-tree transition, no
88            // stale-tree flash for anything watching the files.
89            helpers::fast_forward_home_ref(home_branch, &default_remote, verbose);
90            git::checkout(home_branch, verbose)?;
91        }
92        output::success(&format!("Switched to {}", output::bold(home_branch)));
93    }
94
95    // Sync with default remote (fast-forward only; stop on divergence)
96    helpers::pull_with_output(&default_remote, default_branch, verbose)?;
97
98    output::ready("Ready", home_branch);
99    output::hints(&["gw new feature/your-feature  # Start fresh"]);
100
101    Ok(())
102}