oseda_cli/cmd/
deploy.rs

1use std::{env, error::Error, fs, path::Path};
2
3use clap::Args;
4
5use crate::{
6    config,
7    github::{self, git},
8};
9
10#[derive(Args, Debug)]
11pub struct DeployOptions {
12    fork_url: String,
13}
14
15struct SSH_URL(String);
16
17impl std::ops::Deref for SSH_URL {
18    type Target = String;
19
20    fn deref(&self) -> &Self::Target {
21        &self.0
22    }
23}
24
25impl TryFrom<String> for SSH_URL {
26    type Error = Box<dyn Error>;
27
28    fn try_from(value: String) -> Result<Self, Self::Error> {
29        // https://github.com/ReeseHatfield/oseda-lib-testing/
30        // into
31        // git@github.com:ReeseHatfield/oseda-lib-testing.git
32        let suffix = value
33            .strip_prefix("https://github.com/")
34            .ok_or("Could not get SSH URL")?;
35
36        Ok(SSH_URL(format!(
37            "git@github.com:{}.git",
38            suffix.trim_end_matches('/')
39        )))
40    }
41}
42
43pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
44    let tmp_dir = tempfile::tempdir()?;
45    let repo_path = tmp_dir.path();
46
47    let ssh_url: SSH_URL = opts.fork_url.try_into()?;
48
49    git(
50        repo_path,
51        &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
52    )?;
53
54    println!("Running git with sparse checkout");
55    git(repo_path, &["sparse-checkout", "init", "--cone"])?;
56    git(repo_path, &["sparse-checkout", "set", "courses"])?;
57    git(repo_path, &["checkout"])?;
58
59    let course_name = get_current_dir_name()?;
60    let new_course_dir = repo_path.join("courses").join(&course_name);
61
62    copy_dir_all(env::current_dir()?, &new_course_dir)?;
63
64    // bails if config is bad
65    //
66    // force a no-skip-git
67    let conf = config::read_and_validate_config(false)?;
68
69    config::update_time(conf)?;
70    println!("Committing files to remote...");
71    git(repo_path, &["add", "."])?;
72    git(repo_path, &["commit", "-m", "Add new course"])?;
73    git(repo_path, &["push"])?;
74
75    println!("Project sucessfully pushed to remote.");
76
77    Ok(())
78}
79
80// this is like really stupid to have this, since
81// this logic is basically already used in `check`
82// but really most of that logic should be moved to a config.rs file
83// but until then, I am just reading the cwd with this
84fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
85    let cwd = env::current_dir()?;
86    let name = cwd
87        .file_name()
88        .ok_or("couldn't get directory name")?
89        .to_string_lossy()
90        .to_string();
91    Ok(name)
92}
93
94// https://stackoverflow.com/questions/26958489/how-to-copy-a-folder-recursively-in-rust
95fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
96    fs::create_dir_all(&dst)?;
97    for entry in fs::read_dir(src)? {
98        let entry = entry?;
99        let ty = entry.file_type()?;
100        if ty.is_dir() {
101            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
102        } else {
103            fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
104        }
105    }
106    Ok(())
107}