git_workflow/commands/
sync.rs1use crate::error::{GwError, Result};
26use crate::git;
27use crate::github::{self, PrState};
28use crate::output;
29use crate::state::{RepoType, WorkingDirState};
30
31pub fn run(verbose: bool) -> Result<()> {
33 if !git::is_git_repo() {
35 return Err(GwError::NotAGitRepository);
36 }
37
38 let working_dir = WorkingDirState::detect();
39 if !working_dir.is_clean() {
40 output::error(&format!(
41 "You have uncommitted changes ({}).",
42 working_dir.description()
43 ));
44 output::action("git stash -u -m 'WIP before sync'");
45 return Err(GwError::UncommittedChanges);
46 }
47
48 let repo_type = RepoType::detect()?;
50 let home_branch = repo_type.home_branch();
51 let current = git::current_branch()?;
52
53 if current == home_branch {
55 output::warn("Already on home branch. Nothing to sync.");
56 output::hints(&["mise run git:new feature/... # Create a feature branch first"]);
57 return Ok(());
58 }
59
60 println!();
61 output::info(&format!("Branch: {}", output::bold(¤t)));
62
63 output::info("Fetching from origin...");
65 git::fetch_prune(verbose)?;
66
67 if !github::is_gh_available() {
69 return Err(GwError::Other(
70 "GitHub CLI (gh) is not available. Install it from https://cli.github.com/".into(),
71 ));
72 }
73
74 let pr = match github::get_pr_for_branch(¤t)? {
76 Some(pr) => pr,
77 None => {
78 output::warn("No PR found for this branch.");
79 output::hints(&["gh pr create # Create a PR first"]);
80 return Ok(());
81 }
82 };
83
84 output::info(&format!("PR: #{} ({})", pr.number, pr.title));
85 output::info(&format!("Base: {}", pr.base_branch));
86
87 if pr.base_branch == "main" {
89 output::success("Base is already 'main'. Nothing to sync.");
90 output::hints(&["git rebase origin/main # If you need to update"]);
91 return Ok(());
92 }
93
94 let base_pr = match github::get_pr_for_branch(&pr.base_branch)? {
96 Some(base_pr) => base_pr,
97 None => {
98 output::warn(&format!(
99 "No PR found for base branch '{}'. Cannot determine if it's merged.",
100 pr.base_branch
101 ));
102 return Ok(());
103 }
104 };
105
106 if !base_pr.state.is_merged() {
107 let state_str = match &base_pr.state {
108 PrState::Open => "still open",
109 PrState::Closed => "closed (not merged)",
110 PrState::Merged { .. } => "merged",
111 };
112 output::warn(&format!(
113 "Base PR #{} ({}) is {}.",
114 base_pr.number, pr.base_branch, state_str
115 ));
116 output::hints(&["Wait for the base PR to be merged first"]);
117 return Ok(());
118 }
119
120 output::success(&format!(
122 "Base PR #{} ({}) is merged ✓",
123 base_pr.number, pr.base_branch
124 ));
125
126 println!();
127 output::info("Syncing...");
128
129 output::info(" Updating PR base to main...");
131 github::update_pr_base(pr.number, "main")?;
132
133 output::info(" Rebasing on origin/main...");
135 if let Err(e) = git::rebase("origin/main", verbose) {
136 output::error("Rebase failed. You may need to resolve conflicts manually.");
137 output::action("git rebase --continue # After resolving conflicts");
138 output::action("git rebase --abort # To cancel");
139 return Err(e);
140 }
141
142 output::info(" Force pushing...");
144 git::force_push_with_lease(¤t, verbose)?;
145
146 println!();
147 output::ready("Synced", ¤t);
148 output::hints(&[
149 &format!("PR #{} base is now 'main'", pr.number),
150 "mise run git:status # Check status",
151 ]);
152
153 Ok(())
154}