1use std::{env, error::Error, fs, path::Path};
2
3use clap::Args;
4
5use crate::{
6 cmd::is_cwd_oseda_project,
7 config,
8 github::{self, git},
9};
10
11#[derive(Args, Debug)]
13pub struct DeployOptions {
14 #[arg(value_name = "FORK_URL")]
16 fork_url: String,
17 #[arg(long, value_name = "QUIET")]
19 quiet: bool,
20}
21
22struct SshUrl(String);
23
24impl std::ops::Deref for SshUrl {
26 type Target = String;
27
28 fn deref(&self) -> &Self::Target {
29 &self.0
30 }
31}
32
33impl TryFrom<String> for SshUrl {
42 type Error = Box<dyn Error>;
43
44 fn try_from(value: String) -> Result<Self, Self::Error> {
45 let suffix = value
49 .strip_prefix("https://github.com/")
50 .ok_or("Could not get SSH URL")?;
51
52 Ok(SshUrl(format!(
53 "git@github.com:{}.git",
54 suffix.trim_end_matches('/')
55 )))
56 }
57}
58
59pub fn deploy(opts: DeployOptions) -> Result<(), Box<dyn Error>> {
68 if !is_cwd_oseda_project() {
69 return Err("Current working directory is not an Oseda project".into());
70 }
71
72 let tmp_dir = tempfile::tempdir()?;
73 let repo_path = tmp_dir.path();
74
75 let ssh_url: SshUrl = opts.fork_url.try_into()?;
76
77 git(
78 repo_path,
79 &["clone", "--no-checkout", ssh_url.0.as_str(), "."],
80 )?;
81
82 println!("Running git with sparse checkout");
83 git(repo_path, &["sparse-checkout", "init", "--cone"])?;
84 git(repo_path, &["sparse-checkout", "set", "courses"])?;
85 git(repo_path, &["checkout"])?;
86
87 let course_name = get_current_dir_name()?;
88 let new_course_dir = repo_path.join("courses").join(&course_name);
89
90 copy_dir_all(env::current_dir()?, &new_course_dir)?;
91
92 let conf = config::read_and_validate_config()?;
96
97 println!("Committing files to remote...");
98 git(repo_path, &["add", "."])?;
99 git(
100 repo_path,
101 &["commit", "-m", &format!("Add course: {}", conf.title)],
102 )?;
103 git(repo_path, &["push"])?;
104
105 config::update_time(conf)?;
106
107 println!("Project successfully pushed to remote.");
108
109 match github::get_config_from_user_git("user.name") {
112 Some(github_username) => {
113 let pull_request_url = format!(
114 "https://github.com/oseda-dev/oseda-lib/compare/main...{}:oseda-lib:main?expand=1",
115 github_username
116 );
117
118 println!("Add your presentation to oseda.net by making a Pull Request at:");
119 println!();
120 println!("{}", pull_request_url);
121
122 if !opts.quiet {
123 open::that(pull_request_url.clone()).map_err(|_| {
124 format!("Please visit {pull_request_url} in a browser and submit a pull-request by hand")
125 })?;
126 }
127 }
128 None => {
129 println!("Error: could not get github username");
130 return Err("Deployment failed due to missing github credential. Pleas ensure user.name matches your github username".into());
131 }
132 }
133
134 Ok(())
135}
136
137fn get_current_dir_name() -> Result<String, Box<dyn Error>> {
143 let cwd = env::current_dir()?;
148 let name = cwd
149 .file_name()
150 .ok_or("couldn't get directory name")?
151 .to_string_lossy()
152 .to_string();
153 Ok(name)
154}
155
156fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), Box<dyn Error>> {
159 let src = src.as_ref();
160 let dst = dst.as_ref();
161
162 fs::create_dir_all(dst)?;
163
164 for entry in fs::read_dir(src)? {
165 let entry = entry?;
166 let entry_path = entry.path();
167
168 if entry_path.ends_with(".git") {
170 continue;
171 }
172
173 let ty = entry.file_type()?;
174
175 if ty.is_dir() {
176 copy_dir_all(&entry_path, dst.join(entry.file_name()))?;
177 } else {
178 fs::copy(&entry_path, dst.join(entry.file_name()))?;
179 }
180 }
181
182 Ok(())
183}