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") {
99 Some(github_username) => {
100 let pull_request_url = format!(
101 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
102 github_username
103 );
104
105 println!("Add your presentation to oseda.net by making a Pull Request at:");
106 println!();
107 println!("{}", pull_request_url);
108 }
109 None => {
110 println!("Error: could not get github username");
111 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
112 }
113 }
114
115 Ok(())
116}
117
118fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
124 let cwd = env::current_dir()?;
129 let name = cwd
130 .file_name()
131 .ok_or("couldn't get directory name")?
132 .to_string_lossy()
133 .to_string();
134 Ok(name)
135}
136
137fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
140 let src = src.as_ref();
141 let dst = dst.as_ref();
142
143 fs::create_dir_all(dst)?;
144
145 for entry in fs::read_dir(src)? {
146 let entry = entry?;
147 let entry_path = entry.path();
148
149 if entry_path.ends_with(".git") {
151 continue;
152 }
153
154 let ty = entry.file_type()?;
155
156 if ty.is_dir() {
157 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
158 } else {
159 fs::copy(&entry_path, dst.join(entry.file_name()))?;
160 }
161 }
162
163 Ok(())
164}