use crate::context::CommandContext;
use crate::db::{self, Database};
use crate::error::Result;
use crate::models::{CritiqueStatus, ProblemStatus};
use crate::storage::MetadataStore;
use std::io::{self, Write};
fn prompt_yes_no(message: &str) -> bool {
print!("{} [Y/n] ", message);
if io::stdout().flush().is_err() {
return false;
}
let mut input = String::new();
if io::stdin().read_line(&mut input).is_err() {
return false;
}
let input = input.trim().to_lowercase();
input.is_empty() || input == "y" || input == "yes"
}
pub fn execute(
ctx: &CommandContext,
bookmarks: Vec<String>,
remote: &str,
no_prompt: bool,
dry_run: bool,
) -> Result<()> {
let store = &ctx.store;
let jj_client = ctx.jj();
let db_path = jj_client.repo_root().join(".jj").join("jjj.db");
if db_path.exists() {
let db = Database::open(&db_path)?;
println!("Syncing database to files...");
db::dump_to_markdown(&db, store)?;
println!("Validating metadata...");
let errors = db::validate(&db)?;
if !errors.is_empty() {
println!("Validation errors:");
for error in &errors {
println!(" \u{2717} {}", error);
}
return Err(crate::error::JjjError::Validation(
"Push aborted. Fix errors and retry.".to_string(),
));
}
println!(" \u{2713} All checks passed");
store.commit_changes("jjj: sync database before push")?;
}
if dry_run {
println!("Would push to {}:", remote);
for b in &bookmarks {
println!(" {}", b);
}
println!(" jjj");
return Ok(());
}
for bookmark in &bookmarks {
println!("Pushing {}...", bookmark);
let result = jj_client.execute(&[
"git",
"push",
"-b",
bookmark,
"--remote",
remote,
"--allow-empty-description",
]);
if result.is_err() {
jj_client.execute(&[
"git",
"push",
"-b",
bookmark,
"--remote",
remote,
"--allow-empty-description",
"--allow-new",
])?;
}
}
println!("Pushing jjj...");
let result = jj_client.execute(&[
"git",
"push",
"-b",
"jjj",
"--remote",
remote,
"--allow-empty-description",
]);
if result.is_err() {
jj_client.execute(&[
"git",
"push",
"-b",
"jjj",
"--remote",
remote,
"--allow-empty-description",
"--allow-new",
])?;
}
println!("Pushed to {}.", remote);
if db_path.exists() {
let db = Database::open(&db_path)?;
db::set_dirty(&db, false)?;
}
if !no_prompt {
check_and_prompt_approve_solve(store)?;
}
Ok(())
}
fn check_and_prompt_approve_solve(store: &MetadataStore) -> Result<()> {
let solutions = store.list_solutions()?;
let user = store.jj_client.user_name().unwrap_or_default();
for solution in solutions
.iter()
.filter(|s| s.is_active() && s.assignee.as_deref() == Some(&user))
{
let critiques = store.list_critiques_for_solution(&solution.id)?;
let open_critiques: Vec<_> = critiques
.iter()
.filter(|c| c.status == CritiqueStatus::Open)
.collect();
if open_critiques.is_empty() && !critiques.is_empty() {
if prompt_yes_no(&format!(
"All critiques on {} \"{}\" resolved. Approve solution?",
solution.id, solution.title
)) {
let mut solution = store.load_solution(&solution.id)?;
solution.approve();
store.save_solution(&solution)?;
println!(" Solution {} approved.", solution.id);
let problem = store.load_problem(&solution.problem_id)?;
if problem.status == ProblemStatus::Open
|| problem.status == ProblemStatus::InProgress
{
let other_active: Vec<_> = solutions
.iter()
.filter(|s| {
s.problem_id == solution.problem_id
&& s.is_active()
&& s.id != solution.id
})
.collect();
if other_active.is_empty()
&& prompt_yes_no(&format!(
"Problem {} \"{}\" has no other active solutions. Mark solved?",
problem.id, problem.title
))
{
let mut problem = store.load_problem(&solution.problem_id)?;
problem.set_status(ProblemStatus::Solved);
store.save_problem(&problem)?;
println!(" Problem {} solved.", problem.id);
}
}
}
}
}
Ok(())
}