Split implementation per struct
This commit is contained in:
parent
4ad1e3310b
commit
f817634959
1 changed files with 420 additions and 385 deletions
467
src/main.rs
467
src/main.rs
|
|
@ -55,12 +55,106 @@ struct Delete {
|
|||
branch: Option<String>,
|
||||
}
|
||||
|
||||
impl Delete {
|
||||
pub fn run(
|
||||
self,
|
||||
config: GsmConfig,
|
||||
git_cd: impl Fn(&[&str]) -> Result<String>,
|
||||
patch_dir: &Path,
|
||||
) -> Result<()> {
|
||||
let current_branch = git_cd(&["branch", "--show-current"])?;
|
||||
|
||||
let branch = self
|
||||
.branch
|
||||
.as_ref()
|
||||
.try_m_unwrap_or_else(|| Ok(¤t_branch))?;
|
||||
|
||||
let has_remote = git_cd(&["rev-parse", "@{u}"]).is_ok();
|
||||
if has_remote && !self.local_only {
|
||||
println!("Removing branch from remote repository");
|
||||
git_cd(&["push", "-d", "origin", branch])?;
|
||||
}
|
||||
|
||||
if branch == ¤t_branch {
|
||||
println!("Branch {branch} currently checked out, switching to master");
|
||||
git_cd(&[
|
||||
"switch",
|
||||
config.interdiff_base.as_deref().unwrap_or("master"),
|
||||
])?;
|
||||
}
|
||||
|
||||
let branch_delete = match self.force {
|
||||
true => "-D",
|
||||
false => "-d",
|
||||
};
|
||||
|
||||
git_cd(&["branch", branch_delete, branch])?;
|
||||
let branch_dir = patch_dir.join(&branch);
|
||||
std::fs::remove_dir_all(branch_dir).into_diagnostic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct List {
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn run(
|
||||
self,
|
||||
_config: GsmConfig,
|
||||
_git_cd: impl Fn(&[&str]) -> Result<String>,
|
||||
patch_dir: &Path,
|
||||
) -> Result<()> {
|
||||
for entry in patch_dir
|
||||
.read_dir()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patch dir")?
|
||||
{
|
||||
let entry = entry
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patch dir entry")?;
|
||||
|
||||
if entry.file_name() == "config.toml" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_dir = patch_dir.join(entry.file_name());
|
||||
let Some(branch_version) =
|
||||
latest_version(&branch_dir).wrap_err("Could not fetch latest version")?
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
println!(
|
||||
" - {}: v{branch_version}",
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
|
||||
if self.verbose {
|
||||
println!(" Patches:");
|
||||
for entry in branch_dir
|
||||
.join(branch_version.to_string())
|
||||
.read_dir()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patchset dir")?
|
||||
{
|
||||
let entry = entry
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patchset entry")?;
|
||||
|
||||
println!(" - {}", entry.file_name().to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct Send {
|
||||
#[arg(
|
||||
|
|
@ -73,6 +167,51 @@ struct Send {
|
|||
series: Option<String>,
|
||||
}
|
||||
|
||||
impl Send {
|
||||
pub fn run(
|
||||
self,
|
||||
config: GsmConfig,
|
||||
git_cd: impl Fn(&[&str]) -> Result<String>,
|
||||
patch_dir: &Path,
|
||||
) -> Result<()> {
|
||||
let current_branch = git_cd(&["branch", "--show-current"])?;
|
||||
let branch = self
|
||||
.series
|
||||
.as_ref()
|
||||
.try_m_unwrap_or_else(|| Ok(¤t_branch))?;
|
||||
|
||||
let branch_dir = patch_dir.join(&branch);
|
||||
let version = match self.version {
|
||||
Some(v) => v,
|
||||
None => match latest_version(&branch_dir)? {
|
||||
None => return Err(miette!("No patch set for the branch {branch}")),
|
||||
Some(v) => v,
|
||||
},
|
||||
};
|
||||
|
||||
let version_dir = &branch_dir.join(&version.to_string());
|
||||
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
|
||||
cmd.arg("send-email");
|
||||
if let Some(args) = &config.sendmail_args {
|
||||
cmd.args(args.iter());
|
||||
}
|
||||
cmd.arg(version_dir);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not send emails")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(miette!("Could not send emails"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct FormatPatch {
|
||||
#[arg(short, long, help = "Branch to use (defaults to the current branch)")]
|
||||
|
|
@ -102,119 +241,18 @@ struct FormatPatch {
|
|||
extra_args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct GsmConfig {
|
||||
sendmail_args: Option<Vec<String>>,
|
||||
editor: String,
|
||||
repo_url_base: String,
|
||||
component: Option<String>,
|
||||
ci_url: Option<String>,
|
||||
interdiff_base: Option<String>,
|
||||
}
|
||||
|
||||
fn latest_version(branch_dir: &Path) -> Result<Option<u64>> {
|
||||
let mut dir_content = branch_dir
|
||||
.read_dir()
|
||||
.into_diagnostic()
|
||||
.wrap_err("could not read branch dir")?
|
||||
.peekable();
|
||||
|
||||
match dir_content.peek() {
|
||||
Some(_) => Ok(Some(
|
||||
dir_content
|
||||
.filter_map(|e| {
|
||||
let entry = match e.into_diagnostic().wrap_err("Could not read entry") {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
|
||||
let name = entry.file_name();
|
||||
let name = name.to_str().expect("patch set entry is not utf8");
|
||||
|
||||
if name == "cover-letter" {
|
||||
None
|
||||
} else {
|
||||
Some(Ok(name.parse().expect("version is not an int")))
|
||||
}
|
||||
})
|
||||
.try_fold(0, |cur, version| -> Result<_> {
|
||||
let version = version?;
|
||||
|
||||
Ok(std::cmp::max(version, cur))
|
||||
})?,
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn git_bare(args: Vec<&str>) -> Result<String> {
|
||||
let out = duct::cmd("git", args)
|
||||
.stderr_to_stdout()
|
||||
.unchecked()
|
||||
.stdout_capture()
|
||||
.run()
|
||||
.into_diagnostic()
|
||||
.wrap_err("failed to launch git")?;
|
||||
|
||||
let output = String::from_utf8_lossy(&out.stdout);
|
||||
let output = output.trim();
|
||||
|
||||
if !out.status.success() {
|
||||
return Err(miette!("{output}").wrap_err("git command failed"));
|
||||
} else {
|
||||
Ok(output.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Arg::parse();
|
||||
|
||||
let project_dir = ProjectDirs::from("net", "traxys", "git-series-manager")
|
||||
.ok_or(miette!("Could not create project dirs"))?;
|
||||
|
||||
let repo_root = args.repo.try_m_unwrap_or_else(|| {
|
||||
Ok(PathBuf::from(git_bare(vec![
|
||||
"rev-parse",
|
||||
"--show-toplevel",
|
||||
])?))
|
||||
})?;
|
||||
let patch_dir = repo_root.join(".patches");
|
||||
|
||||
let git_cd = |args: &[&str]| {
|
||||
let mut a = vec![
|
||||
"-C",
|
||||
repo_root
|
||||
.to_str()
|
||||
.ok_or(miette!("{repo_root:?} is not a valid string"))?,
|
||||
];
|
||||
a.extend_from_slice(args);
|
||||
|
||||
git_bare(a)
|
||||
};
|
||||
|
||||
let config: GsmConfig = Config::builder()
|
||||
.add_source(
|
||||
config::File::from(project_dir.config_dir().join("config.toml")).required(false),
|
||||
)
|
||||
.add_source(config::File::from(patch_dir.join("config.toml")).required(false))
|
||||
.build()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to read the configuration")?
|
||||
.try_deserialize()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to deserialize the configuration")?;
|
||||
|
||||
std::fs::create_dir_all(&patch_dir)
|
||||
.into_diagnostic()
|
||||
.wrap_err("could not create patch directory")?;
|
||||
|
||||
match args.command {
|
||||
Command::FormatPatch(args) => {
|
||||
if config.ci_url.is_some() && args.ci.is_none() {
|
||||
impl FormatPatch {
|
||||
pub fn run(
|
||||
self,
|
||||
config: GsmConfig,
|
||||
git_cd: impl Fn(&[&str]) -> Result<String>,
|
||||
patch_dir: &Path,
|
||||
) -> Result<()> {
|
||||
if config.ci_url.is_some() && self.ci.is_none() {
|
||||
eprintln!("WARNING: CI was not specified\n");
|
||||
}
|
||||
|
||||
let branch = args
|
||||
let branch = self
|
||||
.branch
|
||||
.try_m_unwrap_or_else(|| Ok(git_cd(&["branch", "--show-current"])?))?;
|
||||
|
||||
|
|
@ -233,7 +271,7 @@ fn main() -> Result<()> {
|
|||
println!("Component: {component}");
|
||||
println!("Branch: {branch}");
|
||||
|
||||
let ci_link = match (config.ci_url, args.ci) {
|
||||
let ci_link = match (config.ci_url, self.ci) {
|
||||
(Some(ci_template), Some(id)) => Some(
|
||||
ci_template
|
||||
.replace("${component}", &component)
|
||||
|
|
@ -253,7 +291,7 @@ fn main() -> Result<()> {
|
|||
.into_diagnostic()
|
||||
.wrap_err("could not create branch dir")?;
|
||||
|
||||
let version = match args.version {
|
||||
let version = match self.version {
|
||||
Some(v) => Some(v),
|
||||
None => latest_version(&branch_dir)
|
||||
.wrap_err("could not get version")?
|
||||
|
|
@ -263,7 +301,7 @@ fn main() -> Result<()> {
|
|||
let version_dir = branch_dir.join(version.unwrap_or(1).to_string());
|
||||
|
||||
if version_dir.exists() {
|
||||
if !args.force {
|
||||
if !self.force {
|
||||
return Err(miette!(
|
||||
"Patch dir {version_dir:?} exists, pass --force to delete it"
|
||||
));
|
||||
|
|
@ -282,8 +320,7 @@ fn main() -> Result<()> {
|
|||
|
||||
impl Drop for VersionDir {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.path)
|
||||
.expect("could not delete version dir on error");
|
||||
std::fs::remove_dir_all(&self.path).expect("could not delete version dir on error");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +336,7 @@ fn main() -> Result<()> {
|
|||
let subject_prefix = format!(r#"--subject-prefix=PATCH {component}"#);
|
||||
format_patch_args.extend_from_slice(&[&subject_prefix, "--cover-letter"]);
|
||||
format_patch_args.extend_from_slice(extra_args);
|
||||
format_patch_args.extend(args.extra_args.iter().map(|s| s.deref()));
|
||||
format_patch_args.extend(self.extra_args.iter().map(|s| s.deref()));
|
||||
|
||||
git_cd(&format_patch_args)?;
|
||||
|
||||
|
|
@ -308,8 +345,8 @@ fn main() -> Result<()> {
|
|||
})
|
||||
};
|
||||
|
||||
let _version_dir = if let Some(interdiff) = args.diff {
|
||||
let base = args
|
||||
let _version_dir = if let Some(interdiff) = self.diff {
|
||||
let base = self
|
||||
.base_diff
|
||||
.or(config.interdiff_base)
|
||||
.unwrap_or_else(|| String::from("origin/master"));
|
||||
|
|
@ -479,120 +516,118 @@ fn main() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
Command::List(list) => {
|
||||
for entry in patch_dir
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct GsmConfig {
|
||||
sendmail_args: Option<Vec<String>>,
|
||||
editor: String,
|
||||
repo_url_base: String,
|
||||
component: Option<String>,
|
||||
ci_url: Option<String>,
|
||||
interdiff_base: Option<String>,
|
||||
}
|
||||
|
||||
fn latest_version(branch_dir: &Path) -> Result<Option<u64>> {
|
||||
let mut dir_content = branch_dir
|
||||
.read_dir()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patch dir")?
|
||||
{
|
||||
let entry = entry
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patch dir entry")?;
|
||||
.wrap_err("could not read branch dir")?
|
||||
.peekable();
|
||||
|
||||
if entry.file_name() == "config.toml" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let branch_dir = patch_dir.join(entry.file_name());
|
||||
let Some(branch_version) =
|
||||
latest_version(&branch_dir).wrap_err("Could not fetch latest version")?
|
||||
else {
|
||||
continue;
|
||||
match dir_content.peek() {
|
||||
Some(_) => Ok(Some(
|
||||
dir_content
|
||||
.filter_map(|e| {
|
||||
let entry = match e.into_diagnostic().wrap_err("Could not read entry") {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
|
||||
println!(
|
||||
" - {}: v{branch_version}",
|
||||
entry.file_name().to_string_lossy()
|
||||
);
|
||||
let name = entry.file_name();
|
||||
let name = name.to_str().expect("patch set entry is not utf8");
|
||||
|
||||
if list.verbose {
|
||||
println!(" Patches:");
|
||||
for entry in branch_dir
|
||||
.join(branch_version.to_string())
|
||||
.read_dir()
|
||||
if name == "cover-letter" {
|
||||
None
|
||||
} else {
|
||||
Some(Ok(name.parse().expect("version is not an int")))
|
||||
}
|
||||
})
|
||||
.try_fold(0, |cur, version| -> Result<_> {
|
||||
let version = version?;
|
||||
|
||||
Ok(std::cmp::max(version, cur))
|
||||
})?,
|
||||
)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn git_bare(args: Vec<&str>) -> Result<String> {
|
||||
let out = duct::cmd("git", args)
|
||||
.stderr_to_stdout()
|
||||
.unchecked()
|
||||
.stdout_capture()
|
||||
.run()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patchset dir")?
|
||||
{
|
||||
let entry = entry
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not read patchset entry")?;
|
||||
.wrap_err("failed to launch git")?;
|
||||
|
||||
println!(" - {}", entry.file_name().to_string_lossy());
|
||||
}
|
||||
let output = String::from_utf8_lossy(&out.stdout);
|
||||
let output = output.trim();
|
||||
|
||||
if !out.status.success() {
|
||||
return Err(miette!("{output}").wrap_err("git command failed"));
|
||||
} else {
|
||||
Ok(output.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Command::Send(send) => {
|
||||
let current_branch = git_cd(&["branch", "--show-current"])?;
|
||||
let branch = send
|
||||
.series
|
||||
.as_ref()
|
||||
.try_m_unwrap_or_else(|| Ok(¤t_branch))?;
|
||||
fn main() -> Result<()> {
|
||||
let args = Arg::parse();
|
||||
|
||||
let branch_dir = patch_dir.join(&branch);
|
||||
let version = match send.version {
|
||||
Some(v) => v,
|
||||
None => match latest_version(&branch_dir)? {
|
||||
None => return Err(miette!("No patch set for the branch {branch}")),
|
||||
Some(v) => v,
|
||||
},
|
||||
let project_dir = ProjectDirs::from("net", "traxys", "git-series-manager")
|
||||
.ok_or(miette!("Could not create project dirs"))?;
|
||||
|
||||
let repo_root = args.repo.try_m_unwrap_or_else(|| {
|
||||
Ok(PathBuf::from(git_bare(vec![
|
||||
"rev-parse",
|
||||
"--show-toplevel",
|
||||
])?))
|
||||
})?;
|
||||
let patch_dir = repo_root.join(".patches");
|
||||
|
||||
let git_cd = |args: &[&str]| {
|
||||
let mut a = vec![
|
||||
"-C",
|
||||
repo_root
|
||||
.to_str()
|
||||
.ok_or(miette!("{repo_root:?} is not a valid string"))?,
|
||||
];
|
||||
a.extend_from_slice(args);
|
||||
|
||||
git_bare(a)
|
||||
};
|
||||
|
||||
let version_dir = &branch_dir.join(&version.to_string());
|
||||
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
|
||||
cmd.arg("send-email");
|
||||
if let Some(args) = &config.sendmail_args {
|
||||
cmd.args(args.iter());
|
||||
}
|
||||
cmd.arg(version_dir);
|
||||
|
||||
let status = cmd
|
||||
.status()
|
||||
let config: GsmConfig = Config::builder()
|
||||
.add_source(
|
||||
config::File::from(project_dir.config_dir().join("config.toml")).required(false),
|
||||
)
|
||||
.add_source(config::File::from(patch_dir.join("config.toml")).required(false))
|
||||
.build()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Could not send emails")?;
|
||||
.wrap_err("Failed to read the configuration")?
|
||||
.try_deserialize()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to deserialize the configuration")?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(miette!("Could not send emails"));
|
||||
}
|
||||
std::fs::create_dir_all(&patch_dir)
|
||||
.into_diagnostic()
|
||||
.wrap_err("could not create patch directory")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Command::Delete(delete) => {
|
||||
let current_branch = git_cd(&["branch", "--show-current"])?;
|
||||
|
||||
let branch = delete
|
||||
.branch
|
||||
.as_ref()
|
||||
.try_m_unwrap_or_else(|| Ok(¤t_branch))?;
|
||||
|
||||
let has_remote = git_cd(&["rev-parse", "@{u}"]).is_ok();
|
||||
if has_remote && !delete.local_only {
|
||||
println!("Removing branch from remote repository");
|
||||
git_cd(&["push", "-d", "origin", branch])?;
|
||||
}
|
||||
|
||||
if branch == ¤t_branch {
|
||||
println!("Branch {branch} currently checked out, switching to master");
|
||||
git_cd(&[
|
||||
"switch",
|
||||
config.interdiff_base.as_deref().unwrap_or("master"),
|
||||
])?;
|
||||
}
|
||||
|
||||
let branch_delete = match delete.force {
|
||||
true => "-D",
|
||||
false => "-d",
|
||||
};
|
||||
|
||||
git_cd(&["branch", branch_delete, branch])?;
|
||||
let branch_dir = patch_dir.join(&branch);
|
||||
std::fs::remove_dir_all(branch_dir).into_diagnostic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
match args.command {
|
||||
Command::FormatPatch(args) => args.run(config, git_cd, &patch_dir),
|
||||
Command::List(list) => list.run(config, git_cd, &patch_dir),
|
||||
Command::Send(send) => send.run(config, git_cd, &patch_dir),
|
||||
Command::Delete(delete) => delete.run(config, git_cd, &patch_dir),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue