|
|
@ -4,7 +4,14 @@ mod git;
|
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::borrow::Cow;
|
|
|
|
|
|
|
|
|
|
|
|
use error::{AppError, ErrorType};
|
|
|
|
use error::{AppError, ErrorType};
|
|
|
|
use git::{Domain, Git, GitCommand, GitOutput, Url};
|
|
|
|
use git::{Domain, GitOutput, GitTrait, Url};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub use git::Git;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum Local<'a> {
|
|
|
|
|
|
|
|
Branch(Cow<'a, str>),
|
|
|
|
|
|
|
|
NotBranch,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct GitView<'a> {
|
|
|
|
pub struct GitView<'a> {
|
|
|
@ -32,26 +39,18 @@ impl<'a> GitView<'a> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn view_repository(&mut self) -> Result<(), AppError> {
|
|
|
|
pub fn view_repository(&mut self, git: impl GitTrait) -> Result<(), AppError> {
|
|
|
|
// Exit out if we're not inside a git repository
|
|
|
|
// Exit out if we're not inside a git repository
|
|
|
|
self.is_valid_repository(&Git::IsValidRepository)?;
|
|
|
|
self.is_valid_repository(&git)?;
|
|
|
|
// Retrieve the current branch
|
|
|
|
// Retrieve the current local ref (branch or not branch)
|
|
|
|
let branch = self.populate_branch(&Git::LocalBranch)?;
|
|
|
|
let local = self.get_local_ref(&git)?;
|
|
|
|
// Retrieve the remote
|
|
|
|
// Retrieve the remote
|
|
|
|
let remote = self.populate_remote(&Git::DefaultRemote, &Git::TrackedRemote(&branch))?;
|
|
|
|
let remote = self.populate_remote(&local, &git)?;
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Figure out how to default to 'master' or 'main' if branch doesn't exist on remote
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Current theory on how to get it working:
|
|
|
|
|
|
|
|
// 1. Run "git checkout" to check if there's a remote branch for your current local one (there should be no output if remote branch doesn't exist)
|
|
|
|
|
|
|
|
// 2. Then run "git rev-parse --abbrev-ref <remote>/HEAD" and split on the first '/' to get the current default branch
|
|
|
|
|
|
|
|
// - 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
|
|
|
|
// Retrieve the remote reference
|
|
|
|
let remote_ref = self.get_remote_reference(&branch, &Git::UpstreamBranch(&branch))?;
|
|
|
|
let remote_ref = self.get_remote_reference(&local, &git)?;
|
|
|
|
// Retrieve the full git_url
|
|
|
|
// Retrieve the full git_url
|
|
|
|
// e.g https://github.com/sgoudham/git-view.git
|
|
|
|
// e.g https://github.com/sgoudham/git-view.git
|
|
|
|
let git_url = self.get_git_url(&remote, &Git::IsValidRemote(&remote))?;
|
|
|
|
let git_url = self.get_git_url(&remote, &git)?;
|
|
|
|
// Extract protocol, domain and urlpath
|
|
|
|
// Extract protocol, domain and urlpath
|
|
|
|
let url = self.parse_git_url(&git_url)?;
|
|
|
|
let url = self.parse_git_url(&git_url)?;
|
|
|
|
// Generate final url to open in the web browser
|
|
|
|
// Generate final url to open in the web browser
|
|
|
@ -67,8 +66,8 @@ impl<'a> GitView<'a> {
|
|
|
|
Ok(())
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn is_valid_repository(&self, command: &impl GitCommand) -> Result<(), AppError> {
|
|
|
|
fn is_valid_repository(&self, git: &impl GitTrait) -> Result<(), AppError> {
|
|
|
|
match command.execute()? {
|
|
|
|
match git.is_valid_repository()? {
|
|
|
|
GitOutput::Ok(_) => Ok(()),
|
|
|
|
GitOutput::Ok(_) => Ok(()),
|
|
|
|
GitOutput::Err(_) => Err(AppError::new(
|
|
|
|
GitOutput::Err(_) => Err(AppError::new(
|
|
|
|
ErrorType::MissingGitRepository,
|
|
|
|
ErrorType::MissingGitRepository,
|
|
|
@ -77,16 +76,14 @@ impl<'a> GitView<'a> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn populate_branch(&self, command: &impl GitCommand) -> Result<Cow<'_, str>, AppError> {
|
|
|
|
fn get_local_ref(&self, git: &impl GitTrait) -> Result<Local, AppError> {
|
|
|
|
if self.branch.is_none() {
|
|
|
|
if self.branch.is_none() {
|
|
|
|
match command.execute()? {
|
|
|
|
match git.get_local_branch()? {
|
|
|
|
GitOutput::Ok(output) => Ok(Cow::Owned(output)),
|
|
|
|
GitOutput::Ok(output) => Ok(Local::Branch(Cow::Owned(output))),
|
|
|
|
// Don't error on this, instead make a new enum Branch/NotBranch and match on it
|
|
|
|
GitOutput::Err(_) => Ok(Local::NotBranch),
|
|
|
|
// later
|
|
|
|
|
|
|
|
GitOutput::Err(err) => Err(AppError::new(ErrorType::CommandFailed, err)),
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
Ok(Cow::Borrowed(self.branch.as_ref().unwrap()))
|
|
|
|
Ok(Local::Branch(Cow::Borrowed(self.branch.as_ref().unwrap())))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -94,21 +91,26 @@ impl<'a> GitView<'a> {
|
|
|
|
/// User Given Remote -> Default Remote in Config -> Tracked Remote -> 'origin'
|
|
|
|
/// User Given Remote -> Default Remote in Config -> Tracked Remote -> 'origin'
|
|
|
|
fn populate_remote(
|
|
|
|
fn populate_remote(
|
|
|
|
&self,
|
|
|
|
&self,
|
|
|
|
default_remote: &impl GitCommand,
|
|
|
|
local: &Local,
|
|
|
|
tracked_remote: &impl GitCommand,
|
|
|
|
git: &impl GitTrait,
|
|
|
|
) -> Result<Cow<'_, str>, AppError> {
|
|
|
|
) -> Result<Cow<'_, str>, AppError> {
|
|
|
|
// Priority goes to user given remote
|
|
|
|
// Priority goes to user given remote
|
|
|
|
if self.remote.is_none() {
|
|
|
|
if self.remote.is_none() {
|
|
|
|
|
|
|
|
match local {
|
|
|
|
|
|
|
|
Local::Branch(branch) => {
|
|
|
|
// Priority then goes to the default remote
|
|
|
|
// Priority then goes to the default remote
|
|
|
|
match default_remote.execute()? {
|
|
|
|
match git.get_default_remote()? {
|
|
|
|
GitOutput::Ok(def) => Ok(Cow::Owned(def)),
|
|
|
|
GitOutput::Ok(def) => Ok(Cow::Owned(def)),
|
|
|
|
// Priority then goes to the tracked remote
|
|
|
|
// Priority then goes to the tracked remote
|
|
|
|
GitOutput::Err(_) => match tracked_remote.execute()? {
|
|
|
|
GitOutput::Err(_) => match git.get_tracked_remote(branch)? {
|
|
|
|
GitOutput::Ok(tracked) => Ok(Cow::Owned(tracked)),
|
|
|
|
GitOutput::Ok(tracked) => Ok(Cow::Owned(tracked)),
|
|
|
|
// Default to the 'origin' remote
|
|
|
|
// Default to the 'origin' remote
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Owned("origin".into())),
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Owned("origin".into())),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Local::NotBranch => Ok(Cow::Owned("origin".into())),
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
Ok(Cow::Borrowed(self.remote.as_ref().unwrap()))
|
|
|
|
Ok(Cow::Borrowed(self.remote.as_ref().unwrap()))
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -116,28 +118,49 @@ impl<'a> GitView<'a> {
|
|
|
|
|
|
|
|
|
|
|
|
fn get_remote_reference(
|
|
|
|
fn get_remote_reference(
|
|
|
|
&self,
|
|
|
|
&self,
|
|
|
|
branch: &'a str,
|
|
|
|
local: &'a Local,
|
|
|
|
command: &impl GitCommand,
|
|
|
|
git: &impl GitTrait,
|
|
|
|
) -> Result<Cow<'a, str>, AppError> {
|
|
|
|
) -> Result<Cow<'a, str>, AppError> {
|
|
|
|
match command.execute()? {
|
|
|
|
match local {
|
|
|
|
|
|
|
|
Local::Branch(branch) => {
|
|
|
|
|
|
|
|
match git.get_upstream_branch(branch)? {
|
|
|
|
GitOutput::Ok(output) => Ok(Cow::Owned(
|
|
|
|
GitOutput::Ok(output) => Ok(Cow::Owned(
|
|
|
|
output.trim_start_matches("refs/heads/").to_string(),
|
|
|
|
output.trim_start_matches("refs/heads/").to_string(),
|
|
|
|
)),
|
|
|
|
)),
|
|
|
|
// So as explained above, branch might not exist either
|
|
|
|
// If retrieving the upstream_branch fails, that means that there is no valid
|
|
|
|
// Instead of returning branch this early, match on the aforementioned enum above
|
|
|
|
// upstream branch (surprise surprise)
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Branch:
|
|
|
|
// When there's no valid remote branch, we should default to the repository's
|
|
|
|
// Just do what you're doing already and `Cow::Borrowed(branch)`
|
|
|
|
// default branch, for the vast majority of cases, this will be either "main"
|
|
|
|
|
|
|
|
// or "master" but it could be different for whatever INSANE person has changed
|
|
|
|
|
|
|
|
// their default to differ from those two terms.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// NotBranch:
|
|
|
|
// Thankfully, we have a command 'git rev-parse --abbrev-ref <remote>/HEAD'
|
|
|
|
// Check to see if the user is on a tag or a specific commit hash. If all
|
|
|
|
// to let us retrieve the default branch (we also need to split on the first /
|
|
|
|
// else fails THEN error out
|
|
|
|
// encountered and take the second split part)
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// However, it's a bit dodgy so we can't guarantee it will work everytime. If
|
|
|
|
|
|
|
|
// the command 'git rev-parse --abbrev-ref <remote>/HEAD' fails, we should just
|
|
|
|
|
|
|
|
// default to the local branch and the user will just have to suck it up and
|
|
|
|
|
|
|
|
// deal with the 404 error that they will probably get.
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Borrowed(branch)),
|
|
|
|
GitOutput::Err(_) => Ok(Cow::Borrowed(branch)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Priority is given to the current tag
|
|
|
|
|
|
|
|
Local::NotBranch => match git.get_current_tag()? {
|
|
|
|
|
|
|
|
GitOutput::Ok(tag) => Ok(Cow::Owned(tag)),
|
|
|
|
|
|
|
|
// Priority is then given the current commit
|
|
|
|
|
|
|
|
GitOutput::Err(_) => match git.get_current_commit()? {
|
|
|
|
|
|
|
|
GitOutput::Ok(commit_hash) => Ok(Cow::Owned(commit_hash)),
|
|
|
|
|
|
|
|
// Error out if even the current commit could not be found
|
|
|
|
|
|
|
|
GitOutput::Err(err) => Err(AppError::new(ErrorType::CommandFailed, err)),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn get_git_url(&self, remote: &str, command: &impl GitCommand) -> Result<String, AppError> {
|
|
|
|
fn get_git_url(&self, remote: &str, git: &impl GitTrait) -> Result<String, AppError> {
|
|
|
|
match command.execute()? {
|
|
|
|
match git.is_valid_remote(remote)? {
|
|
|
|
GitOutput::Ok(output) => {
|
|
|
|
GitOutput::Ok(output) => {
|
|
|
|
if output != remote {
|
|
|
|
if output != remote {
|
|
|
|
Ok(output)
|
|
|
|
Ok(output)
|
|
|
@ -200,6 +223,12 @@ impl<'a> GitView<'a> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn generate_final_url(&self, remote_ref: &str, url: &Url) -> String {
|
|
|
|
fn generate_final_url(&self, remote_ref: &str, url: &Url) -> String {
|
|
|
|
|
|
|
|
// if self.commit.unwrap() == "latest" {
|
|
|
|
|
|
|
|
// ()
|
|
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
|
|
// ()
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
let branch_ref = match &url.domain {
|
|
|
|
let branch_ref = match &url.domain {
|
|
|
|
Domain::GitHub => {
|
|
|
|
Domain::GitHub => {
|
|
|
|
if self.is_issue {
|
|
|
|
if self.is_issue {
|
|
|
@ -213,12 +242,6 @@ impl<'a> GitView<'a> {
|
|
|
|
|
|
|
|
|
|
|
|
let mut open_url = format!("{}://{}/{}", url.protocol, url.domain, url.path);
|
|
|
|
let mut open_url = format!("{}://{}/{}", url.protocol, url.domain, url.path);
|
|
|
|
|
|
|
|
|
|
|
|
// if self.commit.unwrap() == "latest" {
|
|
|
|
|
|
|
|
// ()
|
|
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
|
|
// ()
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if remote_ref == "master" || remote_ref == "main" {
|
|
|
|
if remote_ref == "master" || remote_ref == "main" {
|
|
|
|
open_url
|
|
|
|
open_url
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
@ -264,7 +287,7 @@ mod lib_tests {
|
|
|
|
mod is_valid_repository {
|
|
|
|
mod is_valid_repository {
|
|
|
|
use crate::{
|
|
|
|
use crate::{
|
|
|
|
error::ErrorType,
|
|
|
|
error::ErrorType,
|
|
|
|
git::{GitOutput, MockGitCommand},
|
|
|
|
git::{GitOutput, MockGitTrait},
|
|
|
|
lib_tests::instantiate_handler,
|
|
|
|
lib_tests::instantiate_handler,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
@ -272,8 +295,8 @@ mod lib_tests {
|
|
|
|
fn yes() {
|
|
|
|
fn yes() {
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
|
|
|
|
|
|
|
|
let mut mock = MockGitCommand::new();
|
|
|
|
let mut mock = MockGitTrait::default();
|
|
|
|
mock.expect_execute()
|
|
|
|
mock.expect_is_valid_repository()
|
|
|
|
.returning(|| Ok(GitOutput::Ok("Valid".to_owned())));
|
|
|
|
.returning(|| Ok(GitOutput::Ok("Valid".to_owned())));
|
|
|
|
|
|
|
|
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
@ -284,8 +307,8 @@ mod lib_tests {
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn no() {
|
|
|
|
fn no() {
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
let handler = instantiate_handler();
|
|
|
|
let mut mock = MockGitCommand::new();
|
|
|
|
let mut mock = MockGitTrait::default();
|
|
|
|
mock.expect_execute()
|
|
|
|
mock.expect_is_valid_repository()
|
|
|
|
.returning(|| Ok(GitOutput::Err("Error".to_owned())));
|
|
|
|
.returning(|| Ok(GitOutput::Err("Error".to_owned())));
|
|
|
|
|
|
|
|
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
|
let is_valid_repository = handler.is_valid_repository(&mock);
|
|
|
@ -301,6 +324,18 @@ mod lib_tests {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mod get_local_ref {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn user_given_branch() {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn is_branch() {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn is_not_branch() {}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
mod parse_git_url {
|
|
|
|
mod parse_git_url {
|
|
|
|
use crate::{error::AppError, lib_tests::instantiate_handler};
|
|
|
|
use crate::{error::AppError, lib_tests::instantiate_handler};
|
|
|
|
use test_case::test_case;
|
|
|
|
use test_case::test_case;
|
|
|
|