Skip to content

Commit

Permalink
Track git repo if given in config (#114)
Browse files Browse the repository at this point in the history
* Adds initial git checkout, and task

* Adds checkout task
  • Loading branch information
Jerboa-app authored May 30, 2024
1 parent b6743b7 commit c669f74
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 43 deletions.
1 change: 1 addition & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub struct GitConfig
{
pub remote: String,
pub branch: String,
pub checkout_schedule: Option<String>,
pub auth: Option<GitAuthConfig>
}

Expand Down
29 changes: 0 additions & 29 deletions src/filesystem/folder.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,7 @@
use core::fmt;
use std::fs::DirEntry;

use regex::Regex;

#[derive(Debug, Clone)]
pub struct ListDirError
{
pub why: String
}

impl fmt::Display for ListDirError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.why)
}
}

/// List all files in path
pub fn list_dir(path: String) -> Result<std::fs::ReadDir, ListDirError>
{
match std::fs::read_dir(path)
{
Ok(files) =>
{
Ok(files)
},
Err(why) =>
{
Err(ListDirError { why: format!("{}", why)})
}
}
}

/// Return parsed [std::ffi::OsString] from [DirEntry]
pub fn dir_entry_to_path(d: DirEntry) -> Option<String>
{
Expand Down
25 changes: 24 additions & 1 deletion src/filesystem/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,25 @@
pub mod file;
pub mod folder;
pub mod folder;

/// Set all entries in a dir to permissions().set_readonly(readonly)
pub fn set_dir_readonly(dir: &str, readonly: bool) -> Result<(), std::io::Error>
{
let paths = std::fs::read_dir(dir.to_owned())?;

for path in paths
{
if path.is_err()
{
return Err(path.err().unwrap())
}

let path = path.unwrap();

match path.metadata()
{
Ok(p) => p.permissions().set_readonly(readonly),
Err(e) => return Err(e)
}
}
Ok(())
}
107 changes: 102 additions & 5 deletions src/integrations/git/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
use core::fmt;
use std::path::Path;

use git2::{Cred, RemoteCallbacks, Repository};

use crate::config::GitConfig;
use crate::{config::GitConfig, filesystem::{folder::list_sub_dirs, set_dir_readonly}};

pub mod refresh;

#[derive(Debug, Clone)]
pub struct GitError
{
pub why: String
}

impl fmt::Display for GitError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.why)
}
}

impl From<git2::Error> for GitError
{
fn from(value: git2::Error) -> Self
{
GitError
{
why: format!("git2::Error {}", value)
}
}
}

impl From<std::io::Error> for GitError
{
fn from(value: std::io::Error) -> Self
{
GitError
{
why: format!("std::io::Error {}", value)
}
}
}

/// Attempt to clone a remote repo from a [crate::config::GitConfig]
pub fn from_clone(path: String, config: &GitConfig) -> Result<Repository, git2::Error>
pub fn from_clone(path: &str, config: &GitConfig) -> Result<Repository, GitError>
{
if let GitConfig{auth: Some(_), remote: _, branch: _} = config
if let GitConfig{auth: Some(_), remote: _, checkout_schedule: _, branch: _} = config
{
let auth = config.auth.clone().unwrap();
let result = match &auth.key_path
Expand Down Expand Up @@ -51,7 +88,7 @@ pub fn from_clone(path: String, config: &GitConfig) -> Result<Repository, git2::
Err(e) =>
{
crate::debug(format!("Error {} while cloning (authenticated) repo at {}", e, config.remote), None);
Err(e)
Err(GitError::from(e))
}
}
}
Expand All @@ -63,8 +100,68 @@ pub fn from_clone(path: String, config: &GitConfig) -> Result<Repository, git2::
Err(e) =>
{
crate::debug(format!("Error {} while cloning (pub) repo at {}", e, config.remote), None);
Err(e)
Err(GitError::from(e))
}
}
}
}

pub fn remove_repository(dir: &str) -> Result<(), std::io::Error>
{
for dir in list_sub_dirs(dir.to_owned())
{
set_dir_readonly(&dir, false)?;
}
set_dir_readonly(dir, false)?;

std::fs::remove_dir_all(dir)?;

Ok(())
}

/// Make a fresh clone if [crate::config::Config::git] is present
/// deleting any file/dir called [crate::config::ContentConfig::path]
pub fn clean_and_clone(dir: &str, config: GitConfig) -> Result<Repository, GitError>
{
remove_repository(dir)?;
match from_clone(dir, &config)
{
Ok(repo) =>
{
Ok(repo)
},
Err(e) =>
{
Err(GitError{why: format!("Could not clone, {}", e)})
}
}
}

/// Fast forward pull from the repository, makes no attempt to resolve
/// if a fast foward is not possible
pub fn fast_forward_pull(repo: Repository, branch: &str) -> Result<(), GitError>
{
// modified from https://stackoverflow.com/questions/58768910/how-to-perform-git-pull-with-the-rust-git2-crate
repo.find_remote("origin")?.fetch(&[branch], None, None)?;

let fetch_head = repo.find_reference("FETCH_HEAD")?;
let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?;
let (analysis, _pref) = repo.merge_analysis(&[&fetch_commit])?;

if analysis.is_up_to_date()
{
Ok(())
}
else if analysis.is_fast_forward()
{
let refname = format!("refs/heads/{}", branch);
let mut reference = repo.find_reference(&refname)?;
reference.set_target(fetch_commit.id(), "Fast-Forward")?;
repo.set_head(&refname)?;
Ok(repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?)
}
else
{
Err(GitError{why: "Cannot fastforward".to_owned()})
}
}
103 changes: 103 additions & 0 deletions src/integrations/git/refresh.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::{path::Path, sync::Arc};

use axum::async_trait;
use chrono::{DateTime, Utc};
use cron::Schedule;
use git2::Repository;
use tokio::sync::Mutex;

use crate::{config::{Config, CONFIG_PATH}, task::{next_job_time, schedule_from_option, Task}};

use super::{clean_and_clone, fast_forward_pull};

pub struct GitRefreshTask
{
pub lock: Arc<Mutex<()>>,
pub last_run: DateTime<Utc>,
pub next_run: Option<DateTime<Utc>>,
pub schedule: Option<Schedule>
}

impl GitRefreshTask
{
pub fn new
(
lock: Arc<Mutex<()>>,
schedule: Option<Schedule>
) -> GitRefreshTask
{
GitRefreshTask
{
lock,
last_run: chrono::offset::Utc::now(),
next_run: if schedule.is_none() { None } else { next_job_time(schedule.clone().unwrap()) },
schedule
}
}
}

#[async_trait]
impl Task for GitRefreshTask
{
async fn run(&mut self) -> Result<(), crate::task::TaskError>
{
let _ = self.lock.lock().await;
let config = Config::load_or_default(CONFIG_PATH);
if config.git.is_some()
{
let git = config.git.unwrap();
let path = Path::new(&config.content.path);
if path.is_dir()
{
let result = match Repository::open(path)
{
Ok(repo) => fast_forward_pull(repo, &git.branch),
Err(e) =>
{
crate::debug(format!("{}, {:?} is not a git repo", e, path), None);
match clean_and_clone(&config.content.path, git.clone())
{
Ok(repo) => fast_forward_pull(repo, &git.branch),
Err(e) => Err(e)
}
}
};

if result.is_err()
{
crate::debug(format!("{:?}", result.err()), None);
}
}
}

self.schedule = schedule_from_option(config.stats.save_schedule.clone());

self.next_run = match &self.schedule
{
Some(s) => next_job_time(s.clone()),
None => None
};

self.last_run = chrono::offset::Utc::now();
Ok(())
}

fn next(&mut self) -> Option<chrono::prelude::DateTime<chrono::prelude::Utc>>
{
self.next_run
}

fn runnable(&self) -> bool
{
match self.next_run
{
Some(t) => chrono::offset::Utc::now() > t,
None => false
}
}

fn info(&self) -> String
{
"Git refresh".to_string()
}
}
4 changes: 0 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR");
const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR");
const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH");

const RESOURCE_REGEX: &str = r"(\.\S+)";
const HTML_REGEX: &str = r"(\.html)$";
const NO_EXTENSION_REGEX: &str = r"^(?!.*\.).*";

const CRAB: [u8; 4] = [0xF0, 0x9F, 0xA6, 0x80];
const BLAZING: [u8; 4] = [0xF0, 0x9F, 0x94, 0xA5];

Expand Down
27 changes: 26 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::time::Duration;

use busser::config::{Config, CONFIG_PATH};
use busser::config::{read_config, Config, CONFIG_PATH};
use busser::content::sitemap::SiteMap;
use busser::integrations::discord::post::try_post;
use busser::integrations::git::clean_and_clone;
use busser::server::http::ServerHttp;
use busser::server::https::Server;
use busser::util::formatted_differences;
Expand Down Expand Up @@ -42,6 +43,30 @@ async fn main() {
let http_server = ServerHttp::new(0,0,0,0);
let _http_redirect = spawn(http_server.serve());

match read_config(CONFIG_PATH)
{
Some(c) =>
{
if c.git.is_some()
{
match clean_and_clone(&c.content.path, c.git.unwrap())
{
Ok(_) => (),
Err(e) =>
{
busser::debug(format!("{}", e), None);
std::process::exit(1);
}
}
}
},
None =>
{
println!("No config found at {}", CONFIG_PATH);
std::process::exit(1);
}
};

if args.iter().any(|x| x == "--static-sitemap")
{
busser::debug(format!("Serving with static sitemap"), None);
Expand Down
Loading

0 comments on commit c669f74

Please sign in to comment.