use std::{env, error::Error, fs, path::Path};
use clap::Args;
use crate::{
config,
github::{self, git},
};
#[derive(Args, Debug)]
pub struct DeployOptions {
fork_url: String,
}
struct SshUrl(String);
impl std::ops::Deref for SshUrl {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TryFrom<String> for SshUrl {
type Error = Box<dyn Error>;
fn try_from(value: String) -> Result<Self, Self::Error> {
let suffix = value
.strip_prefix("https://github.com/")
.ok_or("Could not get SSH URL")?;
Ok(SshUrl(format!(
"git@github.com:{}.git",
suffix.trim_end_matches('/')
)))
}
}
pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
let tmp_dir = tempfile::tempdir()?;
let repo_path = tmp_dir.path();
let ssh_url: SshUrl = opts.fork_url.try_into()?;
git(
repo_path,
&["clone", "--no-checkout", ssh_url.0.as_str(), "."],
)?;
println!("Running git with sparse checkout");
git(repo_path, &["sparse-checkout", "init", "--cone"])?;
git(repo_path, &["sparse-checkout", "set", "courses"])?;
git(repo_path, &["checkout"])?;
let course_name = get_current_dir_name()?;
let new_course_dir = repo_path.join("courses").join(&course_name);
copy_dir_all(env::current_dir()?, &new_course_dir)?;
let conf = config::read_and_validate_config()?;
config::update_time(conf)?;
println!("Committing files to remote...");
git(repo_path, &["add", "."])?;
git(repo_path, &["commit", "-m", "Add new course"])?;
git(repo_path, &["push"])?;
println!("Project successfully pushed to remote.");
match github::get_config_from_user_git("user.name") {
Some(github_username) => {
let pull_request_url = format!(
"https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
github_username
);
println!("Add your presentation to oseda.net by making a Pull Request at:");
println!();
println!("{}", pull_request_url);
}
None => {
println!("Error: could not get github username");
return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
}
}
Ok(())
}
fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
let cwd = env::current_dir()?;
let name = cwd
.file_name()
.ok_or("couldn't get directory name")?
.to_string_lossy()
.to_string();
Ok(name)
}
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
let src = src.as_ref();
let dst = dst.as_ref();
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let entry_path = entry.path();
if entry_path.ends_with(".git") {
continue;
}
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
} else {
fs::copy(&entry_path, dst.join(entry.file_name()))?;
}
}
Ok(())
}