use colored::Colorize;
use dialoguer::Select;
use crate::git;
pub fn run() {
let branch = git::current_branch();
let files = git::parse_status();
if !files.is_empty() {
println!();
println!(
" {} You have {} uncommitted change(s).",
"⚠".yellow(),
files.len()
);
println!(" Save or stash them before syncing.");
println!();
println!(" {}:", "Suggestions".dimmed());
println!(" {} {}", "→".dimmed(), "g save \"...\"".cyan());
println!(" {} {}", "→".dimmed(), "g stash \"...\"".cyan());
println!();
return;
}
let has_upstream = git::ahead_behind().is_some();
if !has_upstream {
println!();
println!(
" {} No upstream configured for '{}'.",
"⚠".yellow(),
branch.cyan()
);
println!(" Push first with {}", "g push".cyan());
println!();
return;
}
println!();
println!(" Pulling latest changes from remote...");
println!();
let result = git::run(&["pull", "--no-rebase"]);
if result.success {
if result.stdout.contains("Already up to date") {
println!(" {} Already up to date.", "✔".green());
} else {
println!(" {} Synced successfully.", "✔".green().bold());
if !result.stdout.is_empty() {
let lines: Vec<&str> = result.stdout.lines().collect();
let summary_lines: Vec<&&str> = lines.iter().take(5).collect();
for line in summary_lines {
println!(" {}", line.dimmed());
}
if lines.len() > 5 {
println!(" {} more lines...", format!("(+{})", lines.len() - 5).dimmed());
}
}
}
println!();
return;
}
let stderr = &result.stderr;
let stdout = &result.stdout;
let has_conflict = stderr.contains("CONFLICT")
|| stderr.contains("Merge conflict")
|| stdout.contains("CONFLICT")
|| stdout.contains("Merge conflict");
if has_conflict {
let conflict_files = find_conflict_files();
println!(
" {} Merge conflict detected!",
"⚠".yellow().bold()
);
println!();
if !conflict_files.is_empty() {
println!(" {}:", "Conflicting files".yellow());
for f in &conflict_files {
println!(" {} {}", "!".yellow(), f);
}
println!();
}
let options = &[
"Accept incoming changes (theirs)",
"Keep your version (ours)",
"Abort merge (go back to before sync)",
];
let selection = Select::new()
.with_prompt(" What would you like to do?")
.items(options)
.default(0)
.interact();
let selection = match selection {
Ok(s) => s,
Err(_) => {
println!(" Aborting merge...");
git::run(&["merge", "--abort"]);
println!(" {} Merge aborted.", "✔".green());
println!();
return;
}
};
println!();
match selection {
0 => {
for f in &conflict_files {
git::run(&["checkout", "--theirs", f]);
git::run(&["add", f]);
}
let commit = git::run(&["commit", "--no-edit"]);
if commit.success {
println!(" {} Conflicts resolved — accepted incoming changes.", "✔".green().bold());
} else {
println!(" {} Failed to complete merge: {}", "✖".red(), commit.stderr);
}
}
1 => {
for f in &conflict_files {
git::run(&["checkout", "--ours", f]);
git::run(&["add", f]);
}
let commit = git::run(&["commit", "--no-edit"]);
if commit.success {
println!(" {} Conflicts resolved — kept your version.", "✔".green().bold());
} else {
println!(" {} Failed to complete merge: {}", "✖".red(), commit.stderr);
}
}
_ => {
git::run(&["merge", "--abort"]);
println!(" {} Merge aborted. You're back to where you started.", "✔".green());
}
}
println!();
} else {
println!(" {} Sync failed: {}", "✖".red(), stderr);
println!();
}
}
fn find_conflict_files() -> Vec<String> {
let result = git::run(&["diff", "--name-only", "--diff-filter=U"]);
if result.success && !result.stdout.is_empty() {
result.stdout.lines().map(|l| l.to_string()).collect()
} else {
vec![]
}
}