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 let default_remote = git::get_default_remote_branch()?;
89 let default_branch = default_remote.strip_prefix("origin/").unwrap_or("main");
90
91 if pr.base_branch == default_branch {
93 output::success(&format!(
94 "Base is already '{}'. Nothing to sync.",
95 default_branch
96 ));
97 output::hints(&[&format!(
98 "git rebase {} # If you need to update",
99 default_remote
100 )]);
101 return Ok(());
102 }
103
104 let base_pr = match github::get_pr_for_branch(&pr.base_branch)? {
106 Some(base_pr) => base_pr,
107 None => {
108 output::warn(&format!(
109 "No PR found for base branch '{}'. Cannot determine if it's merged.",
110 pr.base_branch
111 ));
112 return Ok(());
113 }
114 };
115
116 if !base_pr.state.is_merged() {
117 let state_str = match &base_pr.state {
118 PrState::Open => "still open",
119 PrState::Closed => "closed (not merged)",
120 PrState::Merged { .. } => "merged",
121 };
122 output::warn(&format!(
123 "Base PR #{} ({}) is {}.",
124 base_pr.number, pr.base_branch, state_str
125 ));
126 output::hints(&["Wait for the base PR to be merged first"]);
127 return Ok(());
128 }
129
130 output::success(&format!(
132 "Base PR #{} ({}) is merged ✓",
133 base_pr.number, pr.base_branch
134 ));
135
136 println!();
137 output::info("Syncing...");
138
139 output::info(&format!(" Updating PR base to {}...", default_branch));
141 github::update_pr_base(pr.number, default_branch)?;
142
143 output::info(&format!(" Rebasing on {}...", default_remote));
145 if let Err(e) = git::rebase(&default_remote, verbose) {
146 output::error("Rebase failed. You may need to resolve conflicts manually.");
147 output::action("git rebase --continue # After resolving conflicts");
148 output::action("git rebase --abort # To cancel");
149 return Err(e);
150 }
151
152 output::info(" Force pushing...");
154 git::force_push_with_lease(¤t, verbose)?;
155
156 println!();
157 output::ready("Synced", ¤t);
158 output::hints(&[
159 &format!("PR #{} base is now '{}'", pr.number, default_branch),
160 "gw status # Check status",
161 ]);
162
163 Ok(())
164}