use crate::config::YamlConfig;
use crate::constants::{config_key, section};
use crate::{error, info, usage};
use chrono::Local;
use std::fs;
use std::path::Path;
use std::process::Command;
use super::io::get_report_dir;
pub fn handle_set_url(url: Option<&str>, config: &mut YamlConfig) {
match url {
Some(u) if !u.is_empty() => {
let old = config
.get_property(section::REPORT, config_key::GIT_REPO)
.cloned();
config.set_property(section::REPORT, config_key::GIT_REPO, u);
if let Some(dir) = get_report_dir(config) {
let git_dir = Path::new(&dir).join(".git");
if git_dir.exists() {
sync_git_remote(config);
}
}
match old {
Some(old_url) if !old_url.is_empty() => {
info!("git 仓库地址已更新: {} -> {}", old_url, u);
}
_ => {
info!("git 仓库地址已设置: {}", u);
}
}
}
_ => {
match config.get_property(section::REPORT, config_key::GIT_REPO) {
Some(url) if !url.is_empty() => {
info!("当前 git 仓库地址: {}", url);
}
_ => {
info!("尚未配置 git 仓库地址");
usage!("reportctl set-url <repo_url>");
}
}
}
}
}
pub fn handle_push(commit_msg: Option<&str>, config: &YamlConfig) {
let git_repo = config.get_property(section::REPORT, config_key::GIT_REPO);
match git_repo {
Some(url) if !url.is_empty() => {}
_ => {
error!("尚未配置 git 仓库地址,请先执行: j reportctl set-url <repo_url>");
return;
}
}
if !ensure_git_repo(config) {
return;
}
let default_msg = format!("update report {}", Local::now().format("%Y-%m-%d %H:%M"));
let msg = commit_msg.unwrap_or(&default_msg);
info!("正在推送周报到远程仓库...");
if let Some(status) = run_git(&["add", "."], config) {
if !status.success() {
error!("git add 失败");
return;
}
} else {
return;
}
if let Some(status) = run_git(&["commit", "-m", msg], config) {
if !status.success() {
info!("git commit 返回非零退出码(可能没有新变更)");
}
} else {
return;
}
if let Some(status) = run_git(&["push", "-u", "origin", "main"], config) {
if status.success() {
info!("周报已成功推送到远程仓库");
} else {
error!("git push 失败,请检查网络连接和仓库权限");
}
}
}
pub fn handle_pull(config: &YamlConfig) {
let git_repo = config.get_property(section::REPORT, config_key::GIT_REPO);
let repo_url = match git_repo {
Some(url) if !url.is_empty() => url.clone(),
_ => {
error!("尚未配置 git 仓库地址,请先执行: j reportctl set-url <repo_url>");
return;
}
};
let dir = match get_report_dir(config) {
Some(d) => d,
None => {
error!("无法确定日报目录");
return;
}
};
let git_dir = Path::new(&dir).join(".git");
if !git_dir.exists() {
pull_via_clone(&repo_url, &dir, config);
} else {
pull_existing_repo(&repo_url, &dir, config);
}
}
fn pull_via_clone(repo_url: &str, dir: &str, config: &YamlConfig) {
info!("日报目录尚未初始化,正在从远程仓库克隆...");
let report_path = config.report_file_path();
let has_existing = report_path.exists()
&& fs::metadata(&report_path)
.map(|m| m.len() > 0)
.unwrap_or(false);
if has_existing {
let backup_path = report_path.with_extension("md.bak");
if let Err(e) = fs::copy(&report_path, &backup_path) {
error!("备份现有日报文件失败: {}", e);
} else {
info!("已备份现有日报到: {:?}", backup_path);
}
}
let temp_dir = Path::new(dir).with_file_name(".report_clone_tmp");
let _ = fs::remove_dir_all(&temp_dir);
let result = Command::new("git")
.args(["clone", "-b", "main", repo_url, &temp_dir.to_string_lossy()])
.status();
match result {
Ok(status) if status.success() => {
let _ = fs::remove_dir_all(dir);
if let Err(e) = fs::rename(&temp_dir, dir) {
error!("移动克隆仓库失败: {},临时目录: {:?}", e, temp_dir);
return;
}
info!("成功从远程仓库克隆周报");
}
Ok(_) => {
error!("git clone 失败,请检查仓库地址和网络连接");
let _ = fs::remove_dir_all(&temp_dir);
}
Err(e) => {
error!("执行 git clone 失败: {}", e);
let _ = fs::remove_dir_all(&temp_dir);
}
}
}
fn pull_existing_repo(repo_url: &str, dir: &str, config: &YamlConfig) {
sync_git_remote(config);
let has_commits = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(dir)
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if !has_commits {
pull_empty_repo(dir, config);
} else {
pull_normal_repo(dir, config);
}
let _ = repo_url; }
fn pull_empty_repo(_dir: &str, config: &YamlConfig) {
info!("本地仓库尚无提交,正在从远程仓库拉取...");
let report_path = config.report_file_path();
if report_path.exists()
&& fs::metadata(&report_path)
.map(|m| m.len() > 0)
.unwrap_or(false)
{
let backup_path = report_path.with_extension("md.bak");
let _ = fs::copy(&report_path, &backup_path);
info!("已备份本地日报到: {:?}", backup_path);
}
if let Some(status) = run_git(&["fetch", "origin", "main"], config) {
if !status.success() {
error!("git fetch 失败,请检查网络连接和仓库地址");
return;
}
} else {
return;
}
if let Some(status) = run_git(&["reset", "--hard", "origin/main"], config) {
if status.success() {
info!("成功从远程仓库拉取周报");
} else {
error!("git reset 失败");
}
}
}
fn pull_normal_repo(dir: &str, config: &YamlConfig) {
info!("正在从远程仓库拉取最新周报...");
let _ = run_git(&["add", "-A"], config);
let stash_result = Command::new("git")
.args(["stash", "push", "-m", "auto-stash-before-pull"])
.current_dir(dir)
.output();
let has_stash = match &stash_result {
Ok(output) => {
let msg = String::from_utf8_lossy(&output.stdout);
!msg.contains("No local changes")
}
Err(_) => false,
};
let pull_ok = if let Some(status) = run_git(&["pull", "origin", "main", "--rebase"], config) {
if status.success() {
info!("周报已更新到最新版本");
true
} else {
error!("git pull 失败,请检查网络连接或手动解决冲突");
false
}
} else {
false
};
if has_stash
&& let Some(status) = run_git(&["stash", "pop"], config)
&& !status.success()
&& pull_ok
{
info!("stash pop 存在冲突,请手动合并本地修改(已保存在 git stash 中)");
}
}
fn run_git(args: &[&str], config: &YamlConfig) -> Option<std::process::ExitStatus> {
let dir = match get_report_dir(config) {
Some(d) => d,
None => {
error!("无法确定日报目录");
return None;
}
};
let result = Command::new("git").args(args).current_dir(&dir).status();
match result {
Ok(status) => Some(status),
Err(e) => {
error!("执行 git 命令失败: {}", e);
None
}
}
}
fn ensure_git_repo(config: &YamlConfig) -> bool {
let dir = match get_report_dir(config) {
Some(d) => d,
None => {
error!("无法确定日报目录");
return false;
}
};
let git_dir = Path::new(&dir).join(".git");
if git_dir.exists() {
sync_git_remote(config);
return true;
}
let git_repo = config.get_property(section::REPORT, config_key::GIT_REPO);
match git_repo {
Some(url) if !url.is_empty() => {
let repo_url = url.clone();
info!("日报目录尚未初始化 git 仓库,正在初始化...");
if let Some(status) = run_git(&["init", "-b", "main"], config) {
if !status.success() {
error!("git init 失败");
return false;
}
} else {
return false;
}
if let Some(status) = run_git(&["remote", "add", "origin", &repo_url], config) {
if !status.success() {
error!("git remote add 失败");
return false;
}
} else {
return false;
}
info!("git 仓库初始化完成,remote: {}", repo_url);
true
}
_ => {
error!("尚未配置 git 仓库地址,请先执行: j reportctl set-url <repo_url>");
false
}
}
}
fn sync_git_remote(config: &YamlConfig) {
let git_repo = match config.get_property(section::REPORT, config_key::GIT_REPO) {
Some(url) if !url.is_empty() => url.clone(),
_ => return,
};
let dir = match get_report_dir(config) {
Some(d) => d,
None => return,
};
let current_url = Command::new("git")
.args(["remote", "get-url", "origin"])
.current_dir(&dir)
.output();
match current_url {
Ok(output) if output.status.success() => {
let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
if url != git_repo {
let _ = run_git(&["remote", "set-url", "origin", &git_repo], config);
info!("已同步 remote origin: {} -> {}", url, git_repo);
}
}
_ => {
let _ = run_git(&["remote", "add", "origin", &git_repo], config);
}
}
}