Skip to main content

git_workflow/commands/
abandon.rs

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