Skip to content

Commit

Permalink
Merge pull request #100 from kitsuyui/refresh-all
Browse files Browse the repository at this point in the history
WIP: `mure refresh --all` updates all repositories
  • Loading branch information
kitsuyui authored Jan 14, 2023
2 parents 35db9da + d8ee0ba commit 9f722ca
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 110 deletions.
10 changes: 5 additions & 5 deletions src/app/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ pub fn list(config: &Config, path: bool, full: bool) -> Result<(), Error> {
Ok(())
}

struct MureRepo {
relative_path: PathBuf,
absolute_path: PathBuf,
repo: RepoInfo,
pub struct MureRepo {
pub relative_path: PathBuf,
pub absolute_path: PathBuf,
pub repo: RepoInfo,
}

fn search_mure_repo(config: &Config) -> Vec<Result<MureRepo, Error>> {
pub fn search_mure_repo(config: &Config) -> Vec<Result<MureRepo, Error>> {
let mut repos = vec![];
match config.base_path().read_dir() {
Ok(dir) => {
Expand Down
93 changes: 93 additions & 0 deletions src/app/refresh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ use std::path::PathBuf;

use git2::Repository;

use crate::config::Config;
use crate::gh::get_default_branch;
use crate::git::{PullFastForwardStatus, RepositorySupport};
use crate::mure_error::Error;

use super::list::search_mure_repo;

#[derive(Debug)]
pub enum RefreshStatus {
DoNothing(Reason),
Expand All @@ -21,6 +24,56 @@ pub enum Reason {
NoRemote,
}

pub fn refresh_all(config: &Config) -> Result<(), Error> {
let repos = search_mure_repo(config);
if repos.is_empty() {
println!("No repositories found");
return Ok(());
}
for repo in repos {
match repo {
Ok(mure_repo) => {
println!("> Refreshing {}", mure_repo.repo.repo);
let result = refresh(
#[allow(clippy::expect_used)]
mure_repo
.absolute_path
.to_str()
.expect("failed to convert to str"),
);
match result {
Ok(status) => match status {
RefreshStatus::DoNothing(reason) => match reason {
Reason::NotGitRepository => {
println!("{} is not a git repository", mure_repo.repo.repo)
}
Reason::NoRemote => {
println!("{} has no remote", mure_repo.repo.repo)
}
},
RefreshStatus::Update {
switch_to_default,
message,
} => {
if switch_to_default {
println!("Switched to {}", mure_repo.repo.repo)
}
println!("{}", message)
}
},
Err(e) => {
println!("{}", e.message());
}
}
}
Err(e) => {
println!("{}", e.message());
}
}
}
Ok(())
}

pub fn refresh(repo_path: &str) -> Result<RefreshStatus, Error> {
let mut messages = vec![];
if !PathBuf::from(repo_path).join(".git").exists() {
Expand Down Expand Up @@ -139,4 +192,44 @@ mod tests {
_ => unreachable!(),
}
}

#[test]
fn test_no_remote() {
let fixture = Fixture::create().unwrap();
let path = fixture.repo.path().parent().unwrap();

let result = refresh(path.to_str().unwrap()).unwrap();
match result {
RefreshStatus::DoNothing(Reason::NoRemote) => {}
_ => unreachable!(),
}
}

#[test]
fn test_refresh_all() {
let temp_dir = Temp::new_dir().expect("failed to create temp dir");

let config: Config = toml::from_str(
format!(
r#"
[core]
base_dir = "{}"
[github]
username = "kitsuyui"
[shell]
cd_shims = "mcd"
"#,
temp_dir.to_str().unwrap()
)
.as_str(),
)
.unwrap();
let repos = search_mure_repo(&config);
assert_eq!(repos.len(), 0);
crate::app::clone::clone(&config, "https://github.com/kitsuyui/mure").unwrap();

refresh_all(&config).unwrap();
}
}
94 changes: 12 additions & 82 deletions src/git.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use git2::{BranchType, Repository};
use std::{
path::Path,
process::{Command, Output},
string::FromUtf8Error,
};

use crate::misc::command_wrapper::{CommandOutput as GitCommandOutput, Error, RawCommandOutput};
use crate::mure_error;
use git2::{BranchType, Repository};
use std::{path::Path, process::Command, string::FromUtf8Error};

#[derive(Debug, PartialEq, Eq)]
pub enum PullFastForwardStatus {
Expand All @@ -14,75 +10,6 @@ pub enum PullFastForwardStatus {
Abort,
}

#[derive(Debug)]
pub struct RawGitCommandOutput {
pub status: i32,
pub stdout: String,
pub stderr: String,
}

impl RawGitCommandOutput {
pub fn success(&self) -> bool {
self.status == 0
}
}

impl From<std::process::Output> for RawGitCommandOutput {
fn from(output: Output) -> Self {
let status = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8(output.stdout).unwrap_or_default();
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
RawGitCommandOutput {
status,
stdout,
stderr,
}
}
}

impl TryFrom<RawGitCommandOutput> for GitCommandOutput<()> {
type Error = Error;

fn try_from(raw: RawGitCommandOutput) -> Result<Self, Self::Error> {
match raw.success() {
true => raw.interpret_to(()),
false => Err(Error::Raw(raw)),
}
}
}

impl RawGitCommandOutput {
pub fn interpret_to<T>(self, item: T) -> Result<GitCommandOutput<T>, Error> {
Ok(GitCommandOutput {
raw: self,
interpreted_to: item,
})
}
}

impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Raw(raw) => write!(f, "{}", raw.stderr),
Error::FailedToExecute(err) => write!(f, "Failed to execute git command: {}", err),
Error::CommandNotFound => write!(f, "git command not found"),
}
}
}

#[derive(Debug)]
pub enum Error {
Raw(RawGitCommandOutput),
FailedToExecute(std::io::Error),
CommandNotFound,
}

#[derive(Debug)]
pub struct GitCommandOutput<T> {
pub raw: RawGitCommandOutput,
pub interpreted_to: T,
}

pub trait RepositorySupport {
fn merged_branches(&self) -> Result<GitCommandOutput<Vec<String>>, Error>;
fn is_clean(&self) -> Result<bool, mure_error::Error>;
Expand All @@ -98,8 +25,8 @@ pub trait RepositorySupport {
fn fetch_prune(&self) -> Result<GitCommandOutput<()>, Error>;
fn switch(&self, branch: &str) -> Result<GitCommandOutput<()>, Error>;
fn delete_branch(&self, branch: &str) -> Result<GitCommandOutput<()>, Error>;
fn command(&self, args: &[&str]) -> Result<RawGitCommandOutput, Error>;
fn git_command_on_dir(args: &[&str], workdir: &Path) -> Result<RawGitCommandOutput, Error>;
fn command(&self, args: &[&str]) -> Result<RawCommandOutput, Error>;
fn git_command_on_dir(args: &[&str], workdir: &Path) -> Result<RawCommandOutput, Error>;
}

impl RepositorySupport for Repository {
Expand Down Expand Up @@ -195,17 +122,20 @@ impl RepositorySupport for Repository {
self.command(&["branch", "-d", branch])?.try_into()
}

fn git_command_on_dir(args: &[&str], workdir: &Path) -> Result<RawGitCommandOutput, Error> {
fn git_command_on_dir(args: &[&str], workdir: &Path) -> Result<RawCommandOutput, Error> {
let output = Command::new("git").current_dir(workdir).args(args).output();
match output {
Ok(out) => Ok(RawGitCommandOutput::from(out)),
Ok(out) => Ok(RawCommandOutput::from(out)),
Err(err) => Err(Error::FailedToExecute(err)),
}
}

fn command(&self, args: &[&str]) -> Result<RawGitCommandOutput, Error> {
fn command(&self, args: &[&str]) -> Result<RawCommandOutput, Error> {
let Some(workdir) = self.workdir() else {
return Err(Error::CommandNotFound);
return Err(Error::FailedToExecute(std::io::Error::new(
std::io::ErrorKind::Other,
"workdir is not found",
)));
};
Self::git_command_on_dir(args, workdir)
}
Expand Down
61 changes: 45 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod config;
mod gh;
mod git;
mod github;
mod misc;
mod mure_error;

#[cfg(test)]
Expand All @@ -26,22 +27,26 @@ fn main() -> Result<(), mure_error::Error> {
println!("{}", e);
}
},
Refresh { repository } => {
let current_dir = std::env::current_dir()?;
let Some(current_dir) = current_dir.to_str() else {
Refresh { repository, all } => {
if all {
app::refresh::refresh_all(&config)?;
} else {
let current_dir = std::env::current_dir()?;
let Some(current_dir) = current_dir.to_str() else {
return Err(mure_error::Error::from_str("failed to get current dir"));
};
let repo_path = match repository {
Some(repo) => repo,
None => current_dir.to_string(),
};
match app::refresh::refresh(&repo_path) {
Ok(r) => {
if let app::refresh::RefreshStatus::Update { message, .. } = r {
println!("{}", message);
let repo_path = match repository {
Some(repo) => repo,
None => current_dir.to_string(),
};
match app::refresh::refresh(&repo_path) {
Ok(r) => {
if let app::refresh::RefreshStatus::Update { message, .. } = r {
println!("{}", message);
}
}
Err(e) => println!("{}", e),
}
Err(e) => println!("{}", e),
}
}
Issues { query } => {
Expand Down Expand Up @@ -93,6 +98,13 @@ enum Commands {
help = "repository to refresh. if not specified, current directory is used"
)]
repository: Option<String>,
#[arg(
short,
long,
help = "refresh all repositories",
default_value = "false"
)]
all: bool,
},
#[command(about = "show issues")]
Issues {
Expand Down Expand Up @@ -136,20 +148,37 @@ fn test_parser() {

match Cli::parse_from(vec!["mure", "refresh"]) {
Cli {
command: Commands::Refresh { repository: None },
command:
Commands::Refresh {
repository: None,
all: false,
},
} => (),
_ => panic!("failed to parse"),
}

match Cli::parse_from(vec!["mure", "refresh", "react"]) {
Cli {
command: Commands::Refresh {
repository: Some(repo),
},
command:
Commands::Refresh {
repository: Some(repo),
all: false,
},
} => assert_eq!(repo, "react"),
_ => panic!("failed to parse"),
}

match Cli::parse_from(vec!["mure", "refresh", "--all"]) {
Cli {
command:
Commands::Refresh {
repository: None,
all: true,
},
} => (),
_ => panic!("failed to parse"),
}

match Cli::parse_from(vec!["mure", "issues"]) {
Cli {
command: Commands::Issues { query: None },
Expand Down
1 change: 1 addition & 0 deletions src/misc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod command_wrapper;
Loading

0 comments on commit 9f722ca

Please sign in to comment.