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 if open::that(pull_request_url.clone()).is_err() {
109 return Err(format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand").into());
110 };
111 }
112 None => {
113 println!("Error: could not get github username");
114 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
115 }
116 }
117
118 Ok(())
119}
120
121fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
127 let cwd = env::current_dir()?;
132 let name = cwd
133 .file_name()
134 .ok_or("couldn't get directory name")?
135 .to_string_lossy()
136 .to_string();
137 Ok(name)
138}
139
140fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
143 let src = src.as_ref();
144 let dst = dst.as_ref();
145
146 fs::create_dir_all(dst)?;
147
148 for entry in fs::read_dir(src)? {
149 let entry = entry?;
150 let entry_path = entry.path();
151
152 if entry_path.ends_with(".git") {
154 continue;
155 }
156
157 let ty = entry.file_type()?;
158
159 if ty.is_dir() {
160 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
161 } else {
162 fs::copy(&entry_path, dst.join(entry.file_name()))?;
163 }
164 }
165
166 Ok(())
167}