Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mure refresh --all updates all repositories #100

Merged
merged 3 commits into from
Jan 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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