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)]
12pub struct DeployOptions {
13 fork_url: String,
14}
15
16struct SshUrl(String);
17
18impl std::ops::Deref for SshUrl {
20 type Target = String;
21
22 fn deref(&self) -> &Self::Target {
23 &self.0
24 }
25}
26
27impl TryFrom<String> for SshUrl {
36 type Error = Box<dyn Error>;
37
38 fn try_from(value: String) -> Result<Self, Self::Error> {
39 let suffix = value
43 .strip_prefix("https://github.com/")
44 .ok_or("Could not get SSH URL")?;
45
46 Ok(SshUrl(format!(
47 "git@github.com:{}.git",
48 suffix.trim_end_matches('/')
49 )))
50 }
51}
52
53pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
62 let tmp_dir = tempfile::tempdir()?;
63 let repo_path = tmp_dir.path();
64
65 let ssh_url: SshUrl = opts.fork_url.try_into()?;
66
67 git(
68 repo_path,
69 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
70 )?;
71
72 println!("Running git with sparse checkout");
73 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
74 git(repo_path, &["sparse-checkout", "set", "courses"])?;
75 git(repo_path, &["checkout"])?;
76
77 let course_name = get_current_dir_name()?;
78 let new_course_dir = repo_path.join("courses").join(&course_name);
79
80 copy_dir_all(env::current_dir()?, &new_course_dir)?;
81
82 let conf = config::read_and_validate_config()?;
86
87 config::update_time(conf)?;
88 println!("Committing files to remote...");
89 git(repo_path, &["add", "."])?;
90 git(repo_path, &["commit", "-m", "Add new course"])?;
91 git(repo_path, &["push"])?;
92
93 println!("Project successfully pushed to remote.");
94
95 match github::get_config_from_user_git("user.name") {
98 Some(github_username) => {
99 let pull_request_url = format!(
100 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
101 github_username
102 );
103
104 println!("Add your presentation to oseda.net by making a Pull Request at:");
105 println!();
106 println!("{}", pull_request_url);
107 }
108 None => {
109 println!("Error: could not get github username");
110 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
111 }
112 }
113
114 Ok(())
115}
116
117fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
123 let cwd = env::current_dir()?;
128 let name = cwd
129 .file_name()
130 .ok_or("couldn't get directory name")?
131 .to_string_lossy()
132 .to_string();
133 Ok(name)
134}
135
136fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
139 let src = src.as_ref();
140 let dst = dst.as_ref();
141
142 fs::create_dir_all(dst)?;
143
144 for entry in fs::read_dir(src)? {
145 let entry = entry?;
146 let entry_path = entry.path();
147
148 if entry_path.ends_with(".git") {
150 continue;
151 }
152
153 let ty = entry.file_type()?;
154
155 if ty.is_dir() {
156 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
157 } else {
158 fs::copy(&entry_path, dst.join(entry.file_name()))?;
159 }
160 }
161
162 Ok(())
163}