feat: Parse URL & (naively) open the url in the browser

pull/1/head
sgoudham 2 years ago
parent ce4138712e
commit 26d9e5bd09
Signed by: hammy
GPG Key ID: 44E818FD5457EEA4

@ -1,4 +1,9 @@
use std::{fmt::format, process::Command};
mod error;
use std::process::Command;
use error::AppError;
use url::Url;
#[derive(Debug)]
pub struct GitView {
@ -23,97 +28,104 @@ impl GitView {
}
}
pub fn open_upstream_repository(&mut self) -> Result<(), String> {
pub fn open_upstream_repository(&mut self) -> Result<(), AppError> {
// Exit out if we're not inside a git repository
self.is_inside_git_repository()?;
// Retrieve the current branch
self.populate_branch()?;
// Retrieve the remote
self.populate_remote()?;
// Retrieve the upstream branch
let upstream_branch = self.get_upstream_branch()?;
// TODO: Figure out how to default to 'master' or 'main' if branch doesn't exist on remote
// Retrieve the remote reference
let remote_ref = self.get_remote_reference()?;
// Retrieve the full git_url
// e.g https://github.com/sgoudham/git-view.git
let git_url = self.get_git_url()?;
// Extract protocol, domain and urlpath
let (protocol, domain, urlpath) = self.extract_args_from_git_url(&git_url);
let (protocol, domain, urlpath) = self.parse_git_url(&git_url)?;
webbrowser::open(&git_url).expect("Sorry, I couldn't find the default browser!");
// Open the URL
webbrowser::open(
format!("{}://{}{}/tree/{}", protocol, domain, urlpath, remote_ref).as_str(),
)?;
Ok(())
}
fn is_inside_git_repository(&self) -> Result<(), String> {
fn is_inside_git_repository(&self) -> Result<(), AppError> {
let output = Command::new("git")
.arg("rev-parse")
.arg("--is-inside-work-tree")
.output()
.expect("`git rev-parse --is-inside-work-tree`");
.output()?;
if output.status.success() {
Ok(())
} else {
Err(String::from(
Err(AppError::MissingGitRepository(String::from(
"Looks like you're not in a valid git repository!",
))
)))
}
}
fn populate_branch(&mut self) -> Result<(), String> {
fn populate_branch(&mut self) -> Result<(), AppError> {
if self.branch.is_none() {
let branch = Command::new("git")
.arg("symbolic-ref")
.arg("-q")
.arg("--short")
.arg("HEAD")
.output()
.expect("`git symbolic-ref -q --short HEAD`");
.output()?;
if branch.status.success() {
match stdout_to_string(&branch.stdout) {
Ok(str) => self.branch = Some(str),
Err(_) => return Err(String::from("Git branch is not valid UTF-8!")),
Err(_) => {
return Err(AppError::InvalidUtf8(String::from(
"Git branch is not valid UTF-8!",
)))
}
}
} else {
return Err(String::from_utf8_lossy(&branch.stderr).to_string());
return Err(AppError::CommandError(
String::from_utf8_lossy(&branch.stderr).to_string(),
));
}
}
Ok(())
}
fn get_upstream_branch(&mut self) -> Result<String, String> {
fn get_remote_reference(&mut self) -> Result<String, AppError> {
let absolute_upstream_branch = Command::new("git")
.arg("config")
.arg(format!("branch.{}.merge", self.branch.as_ref().unwrap()))
.output()
.expect("`git config branch.<branch>.merge`");
.output()?;
if absolute_upstream_branch.status.success() {
match stdout_to_string(&absolute_upstream_branch.stdout) {
Ok(str) => Ok(str.trim_start_matches("refs/heads/").to_string()),
Err(_) => Err(String::from("Git upstream branch is not valid UTF-8!")),
Err(_) => Err(AppError::InvalidUtf8(String::from(
"Git upstream branch is not valid UTF-8!",
))),
}
} else {
Err(format!(
"Looks like the branch '{}' doesn't exist upstream!",
self.branch.as_ref().unwrap()
))
Ok(self.branch.as_ref().unwrap().to_string())
}
}
/// Populates the remote variable within [`GitView`]
/// User Given Remote -> Default Remote in Config -> Tracked Remote -> 'origin'
fn populate_remote(&mut self) -> Result<(), String> {
fn populate_remote(&mut self) -> Result<(), AppError> {
// Priority goes to user given remote
if self.remote.is_none() {
// Priority then goes to the default remote
let default_remote = Command::new("git")
.arg("config")
.arg("open.default.remote")
.output()
.expect("`git config open.default.remote`");
.output()?;
if default_remote.status.success() {
return match stdout_to_string(&default_remote.stdout) {
@ -121,7 +133,9 @@ impl GitView {
self.remote = Some(str);
Ok(())
}
Err(_) => Err(String::from("Git default remote is not valid UTF-8!")),
Err(_) => Err(AppError::InvalidUtf8(String::from(
"Git default remote is not valid UTF-8!",
))),
};
}
@ -129,8 +143,7 @@ impl GitView {
let tracked_remote = Command::new("git")
.arg("config")
.arg(format!("branch.{}.remote", self.branch.as_ref().unwrap()))
.output()
.expect("`git config branch.<branch>.remote`");
.output()?;
if tracked_remote.status.success() {
return match stdout_to_string(&tracked_remote.stdout) {
@ -138,7 +151,9 @@ impl GitView {
self.remote = Some(str);
Ok(())
}
Err(_) => Err(String::from("Git tracked remote is not valid UTF-8!")),
Err(_) => Err(AppError::InvalidUtf8(String::from(
"Git tracked remote is not valid UTF-8!",
))),
};
}
@ -149,13 +164,12 @@ impl GitView {
Ok(())
}
fn get_git_url(&self) -> Result<String, String> {
fn get_git_url(&self) -> Result<String, AppError> {
let is_valid_remote = Command::new("git")
.arg("ls-remote")
.arg("--get-url")
.arg(self.remote.as_ref().unwrap())
.output()
.expect("`git ls-remote --get-url <remote>`");
.output()?;
if is_valid_remote.status.success() {
match stdout_to_string(&is_valid_remote.stdout) {
@ -163,61 +177,52 @@ impl GitView {
if &str != self.remote.as_ref().unwrap() {
Ok(str)
} else {
Err(format!(
Err(AppError::MissingGitRemote(format!(
"Looks like your git remote isn't set for '{}'",
self.remote.as_ref().unwrap(),
))
)))
}
}
Err(_) => Err(String::from("Git URL is not valid UTF-8!")),
Err(_) => Err(AppError::InvalidUtf8(String::from(
"Git URL is not valid UTF-8!",
))),
}
} else {
Err(String::from_utf8_lossy(&is_valid_remote.stderr).to_string())
Err(AppError::CommandError(
String::from_utf8_lossy(&is_valid_remote.stderr).to_string(),
))
}
}
fn extract_args_from_git_url(&self, git_url: &str) -> (String, String, String) {
let mut protocol = String::from("https");
let mut domain = String::new();
let mut url_path = String::new();
/*
* To enter this if block, the 'git_url' must be in the following formats:
* - ssh://[user@]host.xz[:port]/path/to/repo.git/
* - git://host.xz[:port]/path/to/repo.git/
* - http[s]://host.xz[:port]/path/to/repo.git/
* - ftp[s]://host.xz[:port]/path/to/repo.git/
*/
if let Some((git_protocol, uri)) = git_url.split_once("://") {
// If the given URL is 'http', keep it that way
if git_protocol == "http" {
protocol = String::from("http");
}
// Trim potential username from URI
let uri = match uri.split_once('@').map(|(_username, url)| url) {
Some(path) => path,
None => uri,
};
// Retrieve domain & url_path
match uri.split_once('/') {
Some((dom, url)) => {
domain.push_str(dom);
url_path.push_str(url);
}
None => todo!(),
}
} else {
todo!()
/*
* Potential formats:
* - ssh://[user@]host.xz[:port]/path/to/repo.git/
* - git://host.xz[:port]/path/to/repo.git/
* - http[s]://host.xz[:port]/path/to/repo.git/
* - 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> {
match Url::parse(git_url) {
Ok(url) => Ok((
url.scheme().to_string(),
url.host_str()
.map_or_else(|| "github.com", |host| host)
.to_string(),
url.path()
.trim_end_matches('/')
.trim_end_matches(".git")
.to_string(),
)),
Err(_) => Err(AppError::InvalidGitUrl(format!(
"Sorry, couldn't parse git url '{}'",
git_url
))),
}
(protocol, domain, url_path)
}
}
fn stdout_to_string(bytes: &[u8]) -> Result<String, std::str::Utf8Error> {
fn stdout_to_string(bytes: &[u8]) -> Result<String, AppError> {
let mut utf_8_string = String::from(std::str::from_utf8(bytes)?.trim());
if utf_8_string.ends_with('\n') {
@ -231,23 +236,59 @@ fn stdout_to_string(bytes: &[u8]) -> Result<String, std::str::Utf8Error> {
}
#[cfg(test)]
mod lib_tests {
use crate::GitView;
mod parse_git_url {
use crate::{error::AppError, GitView};
#[test]
fn test_extract_args_from_git_url() {
let handler = GitView::new(
fn instantiate_handler() -> GitView {
GitView::new(
Some(String::from("main")),
Some(String::from("origin")),
Some(String::from("latest")),
false,
);
let git_url = "https://github.com/sgoudham/git-view.git";
)
}
#[test]
fn with_dot_git() -> Result<(), AppError> {
let handler = instantiate_handler();
let (protocol, domain, urlpath) = handler.extract_args_from_git_url(git_url);
let git_url_normal = "https://github.com/sgoudham/git-view.git";
let (protocol, domain, urlpath) = handler.parse_git_url(git_url_normal)?;
assert_eq!(protocol, "https");
assert_eq!(domain, "github.com");
assert_eq!(urlpath, "sgoudham/git-view.git")
assert_eq!(urlpath, "/sgoudham/git-view");
Ok(())
}
#[test]
fn with_dot_git_and_trailing_slash() -> Result<(), AppError> {
let handler = instantiate_handler();
let git_url_normal = "https://github.com/sgoudham/git-view.git/";
let (protocol, domain, urlpath) = handler.parse_git_url(git_url_normal)?;
assert_eq!(protocol, "https");
assert_eq!(domain, "github.com");
assert_eq!(urlpath, "/sgoudham/git-view");
Ok(())
}
#[test]
fn invalid_git_url() {
let handler = instantiate_handler();
let git_url_normal = "This isn't a git url";
let error = handler.parse_git_url(git_url_normal);
assert!(error.is_err());
assert_eq!(
error.unwrap_err().print(),
"Sorry, couldn't parse git url 'This isn't a git url'"
);
}
}

Loading…
Cancel
Save