Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 176 additions & 67 deletions WebMoMMI/src/github/changelog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::de::{Error, MapAccess, Visitor};
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::fs::{read_dir, File};
use std::fs::{create_dir_all, read_dir, File, OpenOptions};
use std::io::Write;
use std::path::Path;
use std::process::Command;
Expand All @@ -16,12 +16,57 @@ use std::sync::{Mutex, MutexGuard};
use std::thread;
use std::time::{Duration, Instant};

const LOG_DIR: &str = "logs";
const LOG_FILE: &str = "logs/changelog.log";

fn get_timestamp() -> String {
Command::new("date")
.arg("+%Y-%m-%d %H:%M:%S")
.output()
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|_| "???".to_string())
}

fn log_to_file(level: &str, message: &str) {
let timestamp = get_timestamp();
let line = format!("{} [{}] {}\n", timestamp, level, message);

match level {
"ERROR" | "WARNING" => eprint!("{}", line),
_ => print!("{}", line),
}

let _ = create_dir_all(LOG_DIR);
if let Ok(mut file) = OpenOptions::new()
.create(true)
.append(true)
.open(LOG_FILE)
{
let _ = file.write_all(line.as_bytes());
}
}

fn log_info(message: &str) {
log_to_file("INFO", message);
}

fn log_warning(message: &str) {
log_to_file("WARNING", message);
}

fn log_error(message: &str) {
log_to_file("ERROR", message);
}

pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfig>) {
log_info(&format!("Received PR event: action={:?}, merged={}, PR#{}",
event.action, event.pull_request.merged, event.number));

if event.action != PullRequestAction::Closed
|| !event.pull_request.merged
|| !config.has_changelog_repo_path()
{
// Not a merge
log_info("PR not a merge or no changelog repo configured, skipping");
return;
}

Expand All @@ -34,9 +79,12 @@ pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfi
let additions = parse_body_changelog(&event.pull_request.body);

if additions.len() == 0 {
log_info("No changelogs found in PR body");
return;
}

log_info(&format!("Found {} changelogs in PR#{}", additions.len(), event.number));

let changelog = Changelog {
author: event.pull_request.user.login.clone(),
changes: additions,
Expand All @@ -47,8 +95,8 @@ pub fn try_handle_changelog_pr(event: &PullRequestEvent, config: &Arc<MoMMIConfi
changelog_path.push(&format!("html/changelogs/PR-{}-temp.yml", event.number));

match write_temp_changelog(&changelog_path, changelog) {
Err(e) => eprintln!("Error writing changelog temp file: {:?}", e),
_ => {}
Err(e) => log_error(&format!("Error writing changelog temp file: {:?}", e)),
_ => log_info(&format!("Wrote temp changelog for PR-{}", event.number)),
};

process_changelogs(config);
Expand All @@ -70,7 +118,7 @@ pub fn try_handle_changelog_push(event: &PushEvent, config: &Arc<MoMMIConfig>) {
.iter()
.flat_map(|c| c.added.iter().chain(c.modified.iter()))
{
println!("{}", filename);
log_info(&format!("Push event file: {}", filename));
if IS_CHANGELOG_RE.is_match(filename) {
process_changelogs(config);
return;
Expand Down Expand Up @@ -206,12 +254,14 @@ pub enum ChangelogEntryType {
}

pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
log_info("process_changelogs called");
let mut lock = CHANGELOG_MANAGER.lock().unwrap();
let should_spawn_thread = lock.last_time.is_none();
lock.last_time = Some(Instant::now());

if should_spawn_thread {
// Nobody currently processing.
log_info("Spawning new changelog thread");
lock.last_time = Some(Instant::now());
let config = config.clone();
thread::Builder::new()
Expand All @@ -220,32 +270,40 @@ pub fn process_changelogs(config: &Arc<MoMMIConfig>) {
handle_changelog_thread(config);
})
.unwrap();
} else {
log_info("Changelog thread already running, updated last_time");
}
}

fn handle_changelog_thread(config: Arc<MoMMIConfig>) {
let delay = config.get_changelog_delay();
log_info(&format!("Changelog thread started, delay={}s", delay));

loop {
let time = {
let lock = CHANGELOG_MANAGER.lock().unwrap();
let elapsed = lock.last_time.as_ref().unwrap().elapsed();
if elapsed.as_secs() > delay {
log_info("Delay elapsed, starting changelog processing");
return do_changelog(lock, config);
}

match Duration::from_secs(delay).checked_sub(elapsed) {
Some(t) => t,
None => return do_changelog(lock, config),
None => {
log_info("Delay elapsed, starting changelog processing");
return do_changelog(lock, config);
}
}
};
log_info(&format!("Waiting {:?} before processing", time));
thread::sleep(time);
}
}

// Pass the lock directly so we don't risk race conditions.
fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>) {
println!("Running changelogs!");
log_info("Running changelogs!");
// Get what we need and drop the lock.
// so we don't hang everything for the time it takes for the git commands and stuff.
lock.last_time = None;
Expand All @@ -256,19 +314,44 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
.get_ssh_key()
.map(|p| format!("ssh -i {}", p.to_string_lossy()));

// Git pull the repo.
let mut command = Command::new("git");
command
.arg("pull")
.arg("origin")
.arg("--rebase")
let mut fetch_cmd = Command::new("git");
fetch_cmd
.args(["fetch", "origin"])
.current_dir(&path);
if let Some(ref ssh_command) = ssh_config {
command.env("GIT_SSH_COMMAND", &ssh_command);
fetch_cmd.env("GIT_SSH_COMMAND", &ssh_command);
}
let fetch_output = fetch_cmd.output();

match &fetch_output {
Ok(o) if o.status.success() => log_info("git fetch successful"),
Ok(o) => {
log_error(&format!("git fetch failed: {}", String::from_utf8_lossy(&o.stderr)));
return;
}
Err(e) => {
log_error(&format!("git fetch error: {:?}", e));
return;
}
}
let status = command.status().unwrap();

assert!(status.success());
// discard changes to changelog.html and .all-changelogs.yml, those are handled by github action now
let reset_output = Command::new("git")
.args(["reset", "--hard", "origin/Bleeding-Edge"])
.current_dir(&path)
.output();

match &reset_output {
Ok(o) if o.status.success() => log_info("git reset successful"),
Ok(o) => {
log_error(&format!("git reset failed: {}", String::from_utf8_lossy(&o.stderr)));
return;
}
Err(e) => {
log_error(&format!("git reset error: {:?}", e));
return;
}
}

let mut changelog_dir_path = path.to_owned();
changelog_dir_path.push("html/changelogs");
Expand All @@ -282,20 +365,37 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
if file_name.starts_with(".")
|| !file_name.ends_with(".yml")
|| file_name == "example.yml"
|| file_name.starts_with("AutoChangeLog") //generated by github action, lets not duplicate report these
{
continue;
}

println!("{}", file_name);
log_info(&format!("Processing changelog file: {}", file_name));

let file = File::open(entry.path()).unwrap();
let data: Changelog = serde_yaml::from_reader(&file).unwrap();
let file = match File::open(entry.path()) {
Ok(f) => f,
Err(e) => {
log_error(&format!("Failed to open {}: {:?}", file_name, e));
continue;
}
};
let data: Changelog = match serde_yaml::from_reader(&file) {
Ok(d) => d,
Err(e) => {
log_error(&format!("Failed to parse {}: {:?}", file_name, e));
continue;
}
};

if data.changes.len() == 0 {
log_warning(&format!("Changelog {} has no changes, skipping", file_name));
continue;
}

commloop(addr, pass, "changelog", "", data).unwrap();
match commloop(addr, pass, "changelog", "", &data) {
Ok(_) => log_info(&format!("Changelog for {} sent to commloop", file_name)),
Err(e) => log_error(&format!("Failed sending changelog for {}: {:?}", file_name, e)),
}
}
}

Expand All @@ -305,63 +405,70 @@ fn do_changelog(mut lock: MutexGuard<ChangelogManager>, config: Arc<MoMMIConfig>
.arg("html/changelog.html")
.arg("html/changelogs")
.current_dir(&path)
.status()
.unwrap();
.status();

assert!(status.success());
match status {
Ok(s) if s.success() => log_info("Changelog script successful"),
Ok(s) => log_error(&format!("Changelog script failed: {:?}", s)),
Err(e) => log_error(&format!("Changelog script failed badly: {:?}", e)),
}

Command::new("git")
.arg("update-index")
.arg("--refresh")
.current_dir(&path)
.status()
.unwrap();

// See if repo is dirty.
let status = Command::new("git")
.arg("diff-index")
.arg("--exit-code")
.arg("HEAD")
.current_dir(&path)
.status()
.unwrap();

if status.code().unwrap_or(0) == 0 {
// No changes, nothing to commit.
return;
}
// Job below is handled by a github action now

let status = Command::new("git")
.arg("add")
.arg(".")
.arg("-A")
.current_dir(&path)
.status()
.unwrap();

assert!(status.success());
// Command::new("git")
// .arg("update-index")
// .arg("--refresh")
// .current_dir(&path)
// .status()
// .unwrap();

let status = Command::new("git")
.arg("commit")
.arg("-m")
.arg("[ci skip] Automatic changelog update.")
.current_dir(&path)
.status()
.unwrap();
// // See if repo is dirty.
// let status = Command::new("git")
// .arg("diff-index")
// .arg("--exit-code")
// .arg("HEAD")
// .current_dir(&path)
// .status()
// .unwrap();

assert!(status.success());
// if status.code().unwrap_or(0) == 0 {
// // No changes, nothing to commit.
// return;
// }

// Git push the repo.
let mut command = Command::new("git");
command.arg("push").arg("origin").current_dir(&path);
if let Some(ref ssh_command) = ssh_config {
command.env("GIT_SSH_COMMAND", &ssh_command);
}
let status = command.status().unwrap();
// let status = Command::new("git")
// .arg("add")
// .arg(".")
// .arg("-A")
// .current_dir(&path)
// .status()
// .unwrap();

assert!(status.success());
// assert!(status.success());

println!("done");
// let status = Command::new("git")
// .arg("commit")
// .arg("-m")
// .arg("[ci skip] Automatic changelog update.")
// .current_dir(&path)
// .status()
// .unwrap();

// assert!(status.success());

// Git push the repo.
//let mut command = Command::new("git");
//command.arg("push").arg("origin").current_dir(&path);
//if let Some(ref ssh_command) = ssh_config {
// command.env("GIT_SSH_COMMAND", &ssh_command);
//}
//let status = command.status().unwrap();
//
//assert!(status.success());

log_info("Changelog processing complete");
}

#[cfg(test)]
Expand All @@ -379,3 +486,5 @@ mod tests {
assert_eq!(entries[1], ChangelogEntry(ChangelogEntryType::Rscadd, "Dionae now don't expire after harvesting, should they not be possessed in the given time.".into()));
}
}
changelog.rs
16 KB
5 changes: 4 additions & 1 deletion config/example/main.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ password = "secret" #COMMLOOP PASSWORD
type="gamenudge"
"server_status" = [[692441846739238952, 1160976587844501586]] #server id, channel id
"ick" = [[692441846739238952, 1160976569435688970]]
"adminhelp" = [[692441846739238952, 1160976627078017175]]
"adminhelp" = [[692441846739238952, 1160976627078017175]]

[commloop.route.changelog]
"" = [[SERVER_ID, CHANGELOG_CHANNEL_ID]] #leading quotes are empty