use crate::command::{CommandExecutor, CommandOutput, GitCommand};
use crate::error::Result;
use async_trait::async_trait;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub enum NotesAction {
Add {
object: Option<String>,
message: Option<String>,
message_file: Option<PathBuf>,
force: bool,
allow_empty: bool,
no_stripspace: bool,
},
Append {
object: Option<String>,
message: Option<String>,
message_file: Option<PathBuf>,
allow_empty: bool,
no_stripspace: bool,
},
Copy {
from: String,
to: String,
force: bool,
},
Show {
object: Option<String>,
},
List {
object: Option<String>,
},
Remove {
object: Option<String>,
ignore_missing: bool,
},
Prune {
dry_run: bool,
verbose: bool,
},
}
#[derive(Debug, Clone)]
pub struct NotesCommand {
pub executor: CommandExecutor,
pub ref_namespace: Option<String>,
pub action: NotesAction,
}
impl NotesCommand {
#[must_use]
pub fn add() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Add {
object: None,
message: None,
message_file: None,
force: false,
allow_empty: false,
no_stripspace: false,
},
}
}
#[must_use]
pub fn append() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Append {
object: None,
message: None,
message_file: None,
allow_empty: false,
no_stripspace: false,
},
}
}
pub fn copy(from: impl Into<String>, to: impl Into<String>) -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Copy {
from: from.into(),
to: to.into(),
force: false,
},
}
}
#[must_use]
pub fn show() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Show { object: None },
}
}
#[must_use]
pub fn list() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::List { object: None },
}
}
#[must_use]
pub fn remove() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Remove {
object: None,
ignore_missing: false,
},
}
}
#[must_use]
pub fn prune() -> Self {
Self {
executor: CommandExecutor::default(),
ref_namespace: None,
action: NotesAction::Prune {
dry_run: false,
verbose: false,
},
}
}
pub fn ref_namespace(&mut self, ns: impl Into<String>) -> &mut Self {
self.ref_namespace = Some(ns.into());
self
}
pub fn object(&mut self, o: impl Into<String>) -> &mut Self {
let o = o.into();
match &mut self.action {
NotesAction::Add { object, .. }
| NotesAction::Append { object, .. }
| NotesAction::Show { object, .. }
| NotesAction::List { object, .. }
| NotesAction::Remove { object, .. } => *object = Some(o),
NotesAction::Copy { .. } | NotesAction::Prune { .. } => {}
}
self
}
pub fn message(&mut self, m: impl Into<String>) -> &mut Self {
let m = m.into();
match &mut self.action {
NotesAction::Add { message, .. } | NotesAction::Append { message, .. } => {
*message = Some(m);
}
_ => {}
}
self
}
pub fn message_file(&mut self, p: impl Into<PathBuf>) -> &mut Self {
let p = p.into();
match &mut self.action {
NotesAction::Add { message_file, .. } | NotesAction::Append { message_file, .. } => {
*message_file = Some(p);
}
_ => {}
}
self
}
pub fn force(&mut self) -> &mut Self {
match &mut self.action {
NotesAction::Add { force, .. } | NotesAction::Copy { force, .. } => *force = true,
_ => {}
}
self
}
pub fn allow_empty(&mut self) -> &mut Self {
match &mut self.action {
NotesAction::Add { allow_empty, .. } | NotesAction::Append { allow_empty, .. } => {
*allow_empty = true;
}
_ => {}
}
self
}
pub fn no_stripspace(&mut self) -> &mut Self {
match &mut self.action {
NotesAction::Add { no_stripspace, .. } | NotesAction::Append { no_stripspace, .. } => {
*no_stripspace = true
}
_ => {}
}
self
}
pub fn ignore_missing(&mut self) -> &mut Self {
if let NotesAction::Remove { ignore_missing, .. } = &mut self.action {
*ignore_missing = true;
}
self
}
pub fn dry_run(&mut self) -> &mut Self {
if let NotesAction::Prune { dry_run, .. } = &mut self.action {
*dry_run = true;
}
self
}
pub fn verbose(&mut self) -> &mut Self {
if let NotesAction::Prune { verbose, .. } = &mut self.action {
*verbose = true;
}
self
}
}
#[async_trait]
impl GitCommand for NotesCommand {
type Output = CommandOutput;
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
fn build_command_args(&self) -> Vec<String> {
let mut args = vec!["notes".to_string()];
if let Some(ns) = &self.ref_namespace {
args.push("--ref".into());
args.push(ns.clone());
}
match &self.action {
NotesAction::Add {
object,
message,
message_file,
force,
allow_empty,
no_stripspace,
} => {
args.push("add".into());
if *force {
args.push("-f".into());
}
if *allow_empty {
args.push("--allow-empty".into());
}
if *no_stripspace {
args.push("--no-stripspace".into());
}
if let Some(m) = message {
args.push("-m".into());
args.push(m.clone());
}
if let Some(f) = message_file {
args.push("-F".into());
args.push(f.display().to_string());
}
if let Some(o) = object {
args.push(o.clone());
}
}
NotesAction::Append {
object,
message,
message_file,
allow_empty,
no_stripspace,
} => {
args.push("append".into());
if *allow_empty {
args.push("--allow-empty".into());
}
if *no_stripspace {
args.push("--no-stripspace".into());
}
if let Some(m) = message {
args.push("-m".into());
args.push(m.clone());
}
if let Some(f) = message_file {
args.push("-F".into());
args.push(f.display().to_string());
}
if let Some(o) = object {
args.push(o.clone());
}
}
NotesAction::Copy { from, to, force } => {
args.push("copy".into());
if *force {
args.push("-f".into());
}
args.push(from.clone());
args.push(to.clone());
}
NotesAction::Show { object } => {
args.push("show".into());
if let Some(o) = object {
args.push(o.clone());
}
}
NotesAction::List { object } => {
args.push("list".into());
if let Some(o) = object {
args.push(o.clone());
}
}
NotesAction::Remove {
object,
ignore_missing,
} => {
args.push("remove".into());
if *ignore_missing {
args.push("--ignore-missing".into());
}
if let Some(o) = object {
args.push(o.clone());
}
}
NotesAction::Prune { dry_run, verbose } => {
args.push("prune".into());
if *dry_run {
args.push("-n".into());
}
if *verbose {
args.push("-v".into());
}
}
}
args
}
async fn execute(&self) -> Result<CommandOutput> {
self.execute_raw().await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_with_namespace_and_message() {
let mut c = NotesCommand::add();
c.ref_namespace("refs/notes/test")
.object("HEAD")
.message("hi")
.force();
assert_eq!(
c.build_command_args(),
vec![
"notes",
"--ref",
"refs/notes/test",
"add",
"-f",
"-m",
"hi",
"HEAD"
]
);
}
#[test]
fn add_with_file_and_no_stripspace() {
let mut c = NotesCommand::add();
c.object("HEAD")
.message_file("/tmp/payload.bin")
.no_stripspace();
assert_eq!(
c.build_command_args(),
vec![
"notes",
"add",
"--no-stripspace",
"-F",
"/tmp/payload.bin",
"HEAD"
]
);
}
#[test]
fn append_payload() {
let mut c = NotesCommand::append();
c.object("HEAD").message("more");
assert_eq!(
c.build_command_args(),
vec!["notes", "append", "-m", "more", "HEAD"]
);
}
#[test]
fn copy_force() {
let mut c = NotesCommand::copy("a", "b");
c.force();
assert_eq!(
c.build_command_args(),
vec!["notes", "copy", "-f", "a", "b"]
);
}
#[test]
fn show_list_remove_prune() {
let mut s = NotesCommand::show();
s.ref_namespace("build").object("HEAD");
assert_eq!(
s.build_command_args(),
vec!["notes", "--ref", "build", "show", "HEAD"]
);
let mut l = NotesCommand::list();
l.object("HEAD");
assert_eq!(l.build_command_args(), vec!["notes", "list", "HEAD"]);
let mut r = NotesCommand::remove();
r.object("HEAD").ignore_missing();
assert_eq!(
r.build_command_args(),
vec!["notes", "remove", "--ignore-missing", "HEAD"]
);
let mut p = NotesCommand::prune();
p.dry_run().verbose();
assert_eq!(p.build_command_args(), vec!["notes", "prune", "-n", "-v"]);
}
}