|
|
|
@ -1,25 +1,24 @@
|
|
|
|
|
mod error;
|
|
|
|
|
mod git;
|
|
|
|
|
|
|
|
|
|
use std::process::Command;
|
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
|
|
|
|
use error::{AppError, ErrorType};
|
|
|
|
|
use git::{Git, GitCommand, GitOutput};
|
|
|
|
|
use url::Url;
|
|
|
|
|
use git::{Domain, Git, GitCommand, GitOutput, Url};
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct GitView {
|
|
|
|
|
remote: Option<String>,
|
|
|
|
|
branch: Option<String>,
|
|
|
|
|
commit: Option<String>,
|
|
|
|
|
pub struct GitView<'a> {
|
|
|
|
|
remote: Option<&'a str>,
|
|
|
|
|
branch: Option<&'a str>,
|
|
|
|
|
commit: Option<&'a str>,
|
|
|
|
|
is_print: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GitView {
|
|
|
|
|
impl<'a> GitView<'a> {
|
|
|
|
|
pub fn new(
|
|
|
|
|
branch: Option<String>,
|
|
|
|
|
remote: Option<String>,
|
|
|
|
|
commit: Option<String>,
|
|
|
|
|
branch: Option<&'a str>,
|
|
|
|
|
remote: Option<&'a str>,
|
|
|
|
|
commit: Option<&'a str>,
|
|
|
|
|
is_print: bool,
|
|
|
|
|
) -> Self {
|
|
|
|
|
Self {
|
|
|
|
@ -34,12 +33,9 @@ impl GitView {
|
|
|
|
|
// Exit out if we're not inside a git repository
|
|
|
|
|
self.is_valid_repository(&Git::IsValidRepository)?;
|
|
|
|
|
// Retrieve the current branch
|
|
|
|
|
self.populate_branch(&Git::LocalBranch)?;
|
|
|
|
|
let branch = self.populate_branch(&Git::LocalBranch)?;
|
|
|
|
|
// Retrieve the remote
|
|
|
|
|
self.remote = Some(self.populate_remote(
|
|
|
|
|
&Git::DefaultRemote,
|
|
|
|
|
&Git::TrackedRemote(self.branch.as_ref().unwrap()),
|
|
|
|
|
)?);
|
|
|
|
|
let remote = self.populate_remote(&Git::DefaultRemote, &Git::TrackedRemote(&branch))?;
|
|
|
|
|
|
|
|
|
|
// TODO: Figure out how to default to 'master' or 'main' if branch doesn't exist on remote
|
|
|
|
|
//
|
|
|
|
@ -49,19 +45,22 @@ impl GitView {
|
|
|
|
|
// - Although, I think that this command isn't foolproof, it might be the best option though without trying to use some command line parsers
|
|
|
|
|
|
|
|
|
|
// Retrieve the remote reference
|
|
|
|
|
let remote_ref =
|
|
|
|
|
self.get_remote_reference(&Git::UpstreamBranch(self.branch.as_ref().unwrap()))?;
|
|
|
|
|
let remote_ref = self.get_remote_reference(&branch, &Git::UpstreamBranch(&branch))?;
|
|
|
|
|
// Retrieve the full git_url
|
|
|
|
|
// e.g https://github.com/sgoudham/git-view.git
|
|
|
|
|
let git_url = self.get_git_url(&Git::IsValidRemote(self.remote.as_ref().unwrap()))?;
|
|
|
|
|
let git_url = self.get_git_url(&remote, &Git::IsValidRemote(&remote))?;
|
|
|
|
|
// Extract protocol, domain and urlpath
|
|
|
|
|
let (protocol, domain, urlpath) = self.parse_git_url(&git_url)?;
|
|
|
|
|
let url = self.parse_git_url(&git_url)?;
|
|
|
|
|
// Generate final url to open in the web browser
|
|
|
|
|
// let final_url = self.generate_final_url(protocol, domain, urlpath);
|
|
|
|
|
|
|
|
|
|
// Open the URL
|
|
|
|
|
webbrowser::open(
|
|
|
|
|
format!("{}://{}/{}/tree/{}", protocol, domain, urlpath, remote_ref).as_str(),
|
|
|
|
|
format!(
|
|
|
|
|
"{}://{}/{}/tree/{}",
|
|
|
|
|
url.protocol, url.domain, url.path, remote_ref
|
|
|
|
|
)
|
|
|
|
|
.as_str(),
|
|
|
|
|
)?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
@ -77,17 +76,14 @@ impl GitView {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn populate_branch(&mut self, command: &impl GitCommand) -> Result<(), AppError> {
|
|
|
|
|
fn populate_branch(&self, command: &impl GitCommand) -> Result<Cow<'_, str>, AppError> {
|
|
|
|
|
if self.branch.is_none() {
|
|
|
|
|
match command.execute()? {
|
|
|
|
|
GitOutput::Ok(output) => {
|
|
|
|
|
self.branch = Some(output);
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
GitOutput::Ok(output) => Ok(Cow::Owned(output)),
|
|
|
|
|
GitOutput::Err(err) => Err(AppError::new(ErrorType::CommandFailed, err)),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
Ok(Cow::Borrowed(self.branch.as_ref().unwrap()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -97,43 +93,46 @@ impl GitView {
|
|
|
|
|
&self,
|
|
|
|
|
default_remote: &impl GitCommand,
|
|
|
|
|
tracked_remote: &impl GitCommand,
|
|
|
|
|
) -> Result<String, AppError> {
|
|
|
|
|
) -> Result<Cow<'_, str>, AppError> {
|
|
|
|
|
// Priority goes to user given remote
|
|
|
|
|
if self.remote.is_none() {
|
|
|
|
|
// Priority then goes to the default remote
|
|
|
|
|
match default_remote.execute()? {
|
|
|
|
|
GitOutput::Ok(def) => Ok(def),
|
|
|
|
|
GitOutput::Ok(def) => Ok(Cow::Owned(def)),
|
|
|
|
|
// Priority then goes to the tracked remote
|
|
|
|
|
GitOutput::Err(_) => match tracked_remote.execute()? {
|
|
|
|
|
GitOutput::Ok(tracked) => Ok(tracked),
|
|
|
|
|
GitOutput::Ok(tracked) => Ok(Cow::Owned(tracked)),
|
|
|
|
|
// Default to the 'origin' remote
|
|
|
|
|
GitOutput::Err(_) => Ok("origin".to_string()),
|
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Owned("origin".into())),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Ok(self.remote.as_ref().unwrap().to_string())
|
|
|
|
|
Ok(Cow::Borrowed(self.remote.as_ref().unwrap()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_remote_reference(&self, command: &impl GitCommand) -> Result<String, AppError> {
|
|
|
|
|
fn get_remote_reference(
|
|
|
|
|
&self,
|
|
|
|
|
branch: &'a str,
|
|
|
|
|
command: &impl GitCommand,
|
|
|
|
|
) -> Result<Cow<'a, str>, AppError> {
|
|
|
|
|
match command.execute()? {
|
|
|
|
|
GitOutput::Ok(output) => Ok(output.trim_start_matches("refs/heads/").to_string()),
|
|
|
|
|
GitOutput::Err(_) => Ok(self.branch.as_ref().unwrap().to_string()),
|
|
|
|
|
GitOutput::Ok(output) => Ok(Cow::Owned(
|
|
|
|
|
output.trim_start_matches("refs/heads/").to_string(),
|
|
|
|
|
)),
|
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Borrowed(branch)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_git_url(&self, command: &impl GitCommand) -> Result<String, AppError> {
|
|
|
|
|
fn get_git_url(&self, remote: &'a str, command: &impl GitCommand) -> Result<String, AppError> {
|
|
|
|
|
match command.execute()? {
|
|
|
|
|
GitOutput::Ok(output) => {
|
|
|
|
|
if &output != self.remote.as_ref().unwrap() {
|
|
|
|
|
if output != remote {
|
|
|
|
|
Ok(output)
|
|
|
|
|
} else {
|
|
|
|
|
Err(AppError::new(
|
|
|
|
|
ErrorType::MissingGitRemote,
|
|
|
|
|
format!(
|
|
|
|
|
"Looks like your git remote isn't set for '{}'",
|
|
|
|
|
self.remote.as_ref().unwrap()
|
|
|
|
|
),
|
|
|
|
|
format!("Looks like your git remote isn't set for '{}'", remote),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -149,22 +148,19 @@ impl GitView {
|
|
|
|
|
* - ftp[s]://host.xz[:port]/path/to/repo.git/
|
|
|
|
|
* - [user@]host.xz:path/to/repo.git/
|
|
|
|
|
*/
|
|
|
|
|
fn parse_git_url(&self, git_url: &str) -> Result<(String, String, String), AppError> {
|
|
|
|
|
fn parse_git_url(&self, git_url: &str) -> Result<Url, AppError> {
|
|
|
|
|
// rust-url cannot parse 'scp-like' urls -> https://github.com/servo/rust-url/issues/220
|
|
|
|
|
// Manually parse the url ourselves
|
|
|
|
|
|
|
|
|
|
if git_url.contains("://") {
|
|
|
|
|
match Url::parse(git_url) {
|
|
|
|
|
Ok(url) => Ok((
|
|
|
|
|
url.scheme().to_string(),
|
|
|
|
|
url.host_str()
|
|
|
|
|
.map_or_else(|| "github.com", |host| host)
|
|
|
|
|
.to_string(),
|
|
|
|
|
match url::Url::parse(git_url) {
|
|
|
|
|
Ok(url) => Ok(Url::new(
|
|
|
|
|
url.scheme(),
|
|
|
|
|
Domain::from_str(url.host_str().map_or_else(|| "github.com", |host| host)),
|
|
|
|
|
url.path()
|
|
|
|
|
.trim_start_matches('/')
|
|
|
|
|
.trim_end_matches('/')
|
|
|
|
|
.trim_end_matches(".git")
|
|
|
|
|
.to_string(),
|
|
|
|
|
.trim_end_matches(".git"),
|
|
|
|
|
)),
|
|
|
|
|
Err(_) => Err(AppError::new(
|
|
|
|
|
ErrorType::InvalidGitUrl,
|
|
|
|
@ -181,11 +177,7 @@ impl GitView {
|
|
|
|
|
None => domain,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
|
protocol.to_string(),
|
|
|
|
|
split_domain.to_string(),
|
|
|
|
|
path.to_string(),
|
|
|
|
|
))
|
|
|
|
|
Ok(Url::new(protocol, Domain::from_str(split_domain), path))
|
|
|
|
|
}
|
|
|
|
|
None => Err(AppError::new(
|
|
|
|
|
ErrorType::InvalidGitUrl,
|
|
|
|
@ -201,55 +193,58 @@ impl GitView {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod is_valid_repository {
|
|
|
|
|
use std::process::{ExitStatus, Output};
|
|
|
|
|
|
|
|
|
|
use crate::{git::MockGitCommand, GitView};
|
|
|
|
|
mod lib_tests {
|
|
|
|
|
use crate::GitView;
|
|
|
|
|
|
|
|
|
|
fn instantiate_handler() -> GitView {
|
|
|
|
|
GitView::new(
|
|
|
|
|
Some(String::from("main")),
|
|
|
|
|
Some(String::from("origin")),
|
|
|
|
|
Some(String::from("latest")),
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
fn instantiate_handler() -> GitView<'static> {
|
|
|
|
|
GitView::new(Some("main"), Some("origin"), Some("latest"), false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
|
mod is_valid_repository {
|
|
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
|
error::ErrorType,
|
|
|
|
|
git::{GitOutput, MockGitCommand},
|
|
|
|
|
lib_tests::instantiate_handler,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn yes() {
|
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
|
|
|
|
|
|
let mut mock = MockGitCommand::new();
|
|
|
|
|
mock.expect_execute()
|
|
|
|
|
.returning(|| Ok(GitOutput::Ok("Valid".to_owned())));
|
|
|
|
|
|
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
|
|
|
|
|
|
|
assert!(is_valid_repository.is_ok());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
|
#[test]
|
|
|
|
|
fn no() {
|
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
|
let mut mock = MockGitCommand::new();
|
|
|
|
|
mock.expect_execute().never();
|
|
|
|
|
mock.expect_execute()
|
|
|
|
|
.returning(|| Ok(GitOutput::Err("Error".to_owned())));
|
|
|
|
|
|
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
|
|
|
|
|
|
|
assert!(is_valid_repository.is_err());
|
|
|
|
|
|
|
|
|
|
let error = is_valid_repository.as_ref().unwrap_err();
|
|
|
|
|
assert_eq!(error.error_type, ErrorType::MissingGitRepository);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
error.error_str,
|
|
|
|
|
"Looks like you're not in a valid git repository!"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod parse_git_url {
|
|
|
|
|
use crate::{error::AppError, GitView};
|
|
|
|
|
use crate::{error::AppError, lib_tests::instantiate_handler};
|
|
|
|
|
use test_case::test_case;
|
|
|
|
|
|
|
|
|
|
fn instantiate_handler() -> GitView {
|
|
|
|
|
GitView::new(
|
|
|
|
|
Some(String::from("main")),
|
|
|
|
|
Some(String::from("origin")),
|
|
|
|
|
Some(String::from("latest")),
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// http[s]://host.xz[:port]/path/to/repo.git/
|
|
|
|
|
#[test_case("https://github.com:8080/sgoudham/git-view.git" ; "with port")]
|
|
|
|
|
#[test_case("https://github.com/sgoudham/git-view.git" ; "normal")]
|
|
|
|
@ -257,11 +252,11 @@ mod parse_git_url {
|
|
|
|
|
fn https(git_url: &str) -> Result<(), AppError> {
|
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
|
|
|
|
|
|
let (protocol, domain, urlpath) = handler.parse_git_url(git_url)?;
|
|
|
|
|
let url = handler.parse_git_url(git_url)?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(protocol, "https");
|
|
|
|
|
assert_eq!(domain, "github.com");
|
|
|
|
|
assert_eq!(urlpath, "sgoudham/git-view");
|
|
|
|
|
assert_eq!(url.protocol, "https");
|
|
|
|
|
assert_eq!(url.domain.to_string(), "github.com");
|
|
|
|
|
assert_eq!(url.path, "sgoudham/git-view");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -273,11 +268,11 @@ mod parse_git_url {
|
|
|
|
|
fn ssh(git_url: &str) -> Result<(), AppError> {
|
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
|
|
|
|
|
|
let (protocol, domain, urlpath) = handler.parse_git_url(git_url)?;
|
|
|
|
|
let url = handler.parse_git_url(git_url)?;
|
|
|
|
|
|
|
|
|
|
assert_eq!(protocol, "https");
|
|
|
|
|
assert_eq!(domain, "github.com");
|
|
|
|
|
assert_eq!(urlpath, "sgoudham/git-view");
|
|
|
|
|
assert_eq!(url.protocol, "https");
|
|
|
|
|
assert_eq!(url.domain.to_string(), "github.com");
|
|
|
|
|
assert_eq!(url.path, "sgoudham/git-view");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
@ -296,3 +291,4 @@ mod parse_git_url {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|