Merge pull request #1 from sgoudham/v0.1.0

pull/6/head
Hamothy 2 years ago committed by GitHub
commit 70bc6ff544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,21 @@
name: build
on:
push:
paths-ignore:
- 'src/**'
- 'tests/**'
- '.github/**'
branches:
- '**'
pull_request:
paths-ignore:
- 'src/**'
- 'tests/**'
- '.github/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: 'echo "No build required"'

@ -2,9 +2,17 @@ name: build
on:
push:
paths:
- 'src/**'
- 'tests/**'
- '.github/**'
branches:
- '**'
pull_request:
paths:
- 'src/**'
- 'tests/**'
- '.github/**'
jobs:
install-cross:
@ -104,4 +112,4 @@ jobs:
channel: [ stable, beta ]
target:
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- x86_64-unknown-linux-musl

@ -0,0 +1,18 @@
name: Generate CHANGELOG
on:
release:
types: [created, edited]
jobs:
generate-changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: BobAnkh/auto-generate-changelog@master
with:
ACCESS_TOKEN: ${{secrets.CHANGELOG}}
PATH: 'CHANGELOG.md'
COMMIT_MESSAGE: 'docs(CHANGELOG): Update release notes'
TYPE: 'feat:Feature,fix:Bug Fixes,docs:Documentation,refactor:Refactor,perf:Performance Improvements,tests:Tests'

@ -140,4 +140,4 @@ jobs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.BIN }}.tar.gz
asset_name: ${{ env.BIN }}-${{ matrix.target }}.tar.gz
asset_content_type: application/gzip
asset_content_type: application/gzip

2
.gitignore vendored

@ -8,3 +8,5 @@
# Leave for libraries
/Cargo.lock
.idea

@ -0,0 +1,12 @@
# Contributing to mdbook-template
Thank you for your interest in contributing to this project! I really appreciate it!
# Got a Question?
Please raise an issue with the `question` label. This ensures that bugs are separated from genuine questions.
# Found an Issue or Bug?
Please raise an issue on if you think you have found any issues or bugs. Even better, it would be great
to submit a Pull Request including the fix.

@ -19,4 +19,9 @@ path = "src/lib.rs"
[dependencies]
clap = { version = '3.1.18', features = ["cargo"] }
url = { version = '2.2.2' }
webbrowser = { version = '0.7.1' }
[dev-dependencies]
test-case = { version = '2.1.0' }
mockall = { version = '0.11.1' }

@ -1,3 +1,186 @@
# git-browser
# git-view
A git sub-command to open your git repository in the web browser... written in Rust :D
[![build](https://github.com/sgoudham/git-view/actions/workflows/build.yml/badge.svg)](https://github.com/sgoudham/git-view/actions/workflows/build.yml)
[![crates.io](https://img.shields.io/crates/v/git-view)](https://crates.io/crates/git-view)
[![downloads](https://img.shields.io/crates/d/git-view)](https://crates.io/crates/git-view)
[![license](https://img.shields.io/github/license/sgoudham/git-view)](LICENSE)
> A git sub-command to view your git repository in the web browser!
## About
Are you _also_ frustrated from moving your hands away from the keyboard to view your git repository in the browser?
> Me too!
`git-view` alleviates that pain by allowing you to chuck away your mouse!
> (n)vim users rejoice :P
**_Important Note: You should always use `git view -h` instead of `git view --help` as the manpage/html files are NOT included._**
## Features
- [x] GitHub & BitBucket
- [x] View Branches, Commits & Issues
- [x] Custom Suffix
- [x] Custom Remote
- [ ] View Profile
- [ ] View Current Directory
Feel free to raise any issues or pull requests (after having read the [CONTRIBUTING.md](./CONTRIBUTING.md)!) for any additional features
that you want!
## Usage
![Usage](./docs/images/usage.png "Displays different usages of `git-view`")
## Installation
### Cargo
The _preferred_ way of installation is to manually install the provided binaries and update your `$PATH` variable to enable
the usage as `git view` globally. However, that being said, it also available on [crates.io](https://crates.io/crates/git-view) to allow installation
through the use of Rust's build tool and package manager `cargo`.
> If you do not have `cargo` available on your machine, you can download it [here](https://www.rust-lang.org/tools/install)
```shell
$ cargo install git-view
```
Refresh terminal & verify installation
```shell
$ git view --version
git-view 0.1.0
```
### Homebrew
For `macOS` users, installation through [Homebrew](https://brew.sh/) is recommended.
```shell
$ brew tap sgoudham/tap
$ brew install git-view
```
Refresh terminal & verify installation
```shell
$ git view --version
git-view 0.1.0
```
### Binaries
Pre-compiled binaries are _always_ available with every single [release](https://github.com/sgoudham/git-view/releases) for **Windows**, **macOS** and **Linux**.
The examples shown below will showcase the installation of the binaries living within the local `git` directory but realistically, any path will
work if updated correctly within `$PATH`.
#### Windows
1. Download either `git-view-x86_64-pc-windows-msvc.zip` or `git-view-x86_64-pc-windows-gnu.zip`
2. Find local `git` directory
```shell
# CMD
$ where git
C:\Program Files\Git\cmd\git.exe
# PowerShell
$ (Get-Command git.exe).Path
C:\Program Files\Git\cmd\git.exe
```
3. `cd` into above path & extract downloaded binary zip
```shell
$ cd 'C:\Program Files\Git\cmd'
$ tar -xf git-view-x86_64-pc-windows-msvc.zip
# OR
$ tar -xf git-view-x86_64-pc-windows-gnu.zip
```
4. Ensure `%PATH%` is updated
```shell
# Only required if git-view exists within a path not already included within %PATH%
$ setx path "%path%;C:\your\path\here\bin"
```
5. Refresh terminal and verify installation
```shell
$ git view --version
git-view 0.1.0
```
#### Linux / macOS
1. Download `git-view-x86_64-unknown-linux-gnu.tar.gz` or `git-view-x86_64-unknown-linux-musl.tar.gz`
or `git-view-x86_64-apple-darwin.tar.gz`
2. Extract into your local directory
```shell
# Linux
$ tar -xf git-view-x86_64-unknown-linux-gnu.tar.gz
$ tar -xf git-view-x86_64-unknown-linux-musl.tar.gz
# macOS
$ tar -xf git-view-x86_64-apple-darwin.tar.gz
```
3. Move into `~/bin`
```shell
# Create ~/bin if it does not exist
$ mkdir -p ~/bin
$ mv git-view ~/bin
```
4. Set permissions for executable
```shell
$ chmod 755 ~/bin/git-view
```
5. Ensure `$PATH` is updated
```shell
# Only required if git-view exists within a path not already included within $PATH
# Linux
$ echo 'export PATH=~/bin:$PATH' >> ~/.bashrc
$ source ~/.bashrc
# macOS
$ echo 'export PATH=~/bin:$PATH' >> ~/.bash_profile
$ source ~/.bash_profile
```
6. Verify installation
```shell
$ git view --version
git-view 0.1.0
```
## Help
![help](./docs/images/help.png "Contents displayed when running `git view -h`")
## Contributing
First, thanks for your interest in contributing to this project! Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) before contributing!
## License
[MIT License](LICENSE)
## Acknowledgement
The idea for this project came about from an existing project [git-open](https://github.com/paulirish/git-open/blob/master/git-open)

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

@ -1,6 +1,7 @@
use std::panic::set_hook;
use clap::{command, crate_authors, crate_description, crate_version, Arg, Command, ErrorKind};
use clap::{command, crate_authors, crate_version, Arg, Command, ErrorKind};
use git_view::Git;
use git_view::GitView;
macro_rules! clap_panic {
@ -15,53 +16,77 @@ fn main() {
let matches = Command::new("git-view")
.version(crate_version!())
.author(crate_authors!())
.about(crate_description!())
.long_about(None)
.arg(
Arg::new("branch")
.help("The branch to view git repository on")
.short('b')
.long("branch")
.value_name("name")
.takes_value(true)
.display_order(2),
.about(
"A git sub-command to view your git repository in the browser.
This currently supports the following URLs:
- github.com
- bitbucket.org
",
)
.arg(
Arg::new("remote")
.help("The remote to view git repository on")
.long_help("The remote to view git repository on\n[default: default remote]")
.short('r')
.long("remote")
.value_name("name")
.takes_value(true)
.display_order(1),
)
.arg(
Arg::new("branch")
.long_help("The branch to view git repository on\n[default: current branch]")
.short('b')
.long("branch")
.value_name("name")
.takes_value(true)
.display_order(2),
)
.arg(
Arg::new("commit")
.help("The commit to view git repository on")
.long_help("The commit to view git repository on\n[default: current commit]")
.short('c')
.long("commit")
.value_name("hash")
.default_missing_value("latest")
.default_missing_value("current")
.conflicts_with_all(&["remote", "branch"])
.display_order(3),
)
.arg(
Arg::new("suffix")
.long_help("A suffix to append onto the git repository URL")
.short('s')
.long("suffix")
.value_name("suffix")
.takes_value(true)
.display_order(4),
)
.arg(
Arg::new("issue")
.long_help("Attempt to parse issue number and open issue link")
.short('i')
.long("issue")
.display_order(5),
)
.arg(
Arg::new("print")
.help("Print the URL (doesn't open browser)")
.long_help("Don't open browser and print the URL")
.short('p')
.long("print")
.display_order(4),
.display_order(6),
);
let matches = matches.get_matches();
let mut git_view = GitView::new(
matches.value_of("branch").map(str::to_string),
matches.value_of("remote").map(str::to_string),
matches.value_of("commit").map(str::to_string),
matches.value_of("branch"),
matches.value_of("remote"),
matches.value_of("commit"),
matches.value_of("suffix"),
matches.is_present("issue"),
matches.is_present("print"),
);
if let Err(err) = git_view.open_upstream_repository() {
clap_panic!(err);
if let Err(app_error) = git_view.view_repository(Git) {
clap_panic!(app_error.error_str);
}
}

@ -0,0 +1,37 @@
#[derive(Debug, PartialEq)]
pub enum ErrorType {
CommandFailed,
CommandError,
MissingGitRepository,
MissingGitRemote,
InvalidGitUrl,
InvalidUtf8,
IOError,
}
#[derive(Debug, PartialEq)]
pub struct AppError {
pub error_type: ErrorType,
pub error_str: String,
}
impl From<std::io::Error> for AppError {
fn from(error: std::io::Error) -> Self {
AppError::new(ErrorType::IOError, error.to_string())
}
}
impl From<std::str::Utf8Error> for AppError {
fn from(error: std::str::Utf8Error) -> Self {
AppError::new(ErrorType::InvalidUtf8, error.to_string())
}
}
impl AppError {
pub fn new(error_type: ErrorType, error_str: String) -> Self {
Self {
error_type,
error_str,
}
}
}

@ -0,0 +1,198 @@
use core::fmt;
use std::{
borrow::Cow,
process::{Command, Output},
};
use crate::error::AppError;
#[cfg(test)]
use mockall::automock;
#[derive(Debug, PartialEq)]
pub(crate) enum Local<'a> {
Branch(Cow<'a, str>),
NotBranch,
}
#[derive(Debug)]
pub(crate) enum Domain {
GitHub,
BitBucket,
}
#[derive(Debug)]
pub(crate) struct Url {
pub(crate) protocol: String,
pub(crate) domain: Domain,
pub(crate) path: String,
}
#[derive(Default)]
pub struct Git;
pub(crate) enum GitCommand<'a> {
IsValidRepository,
LocalBranch,
DefaultRemote,
TrackedRemote(&'a str),
UpstreamBranch(&'a str),
DefaultBranch(&'a str),
IsValidRemote(&'a str),
CurrentTag,
CurrentCommit,
}
pub enum GitOutput {
Ok(String),
Err(String),
}
#[cfg_attr(test, automock)]
pub trait GitTrait {
fn is_valid_repository(&self) -> Result<GitOutput, AppError>;
fn get_local_branch(&self) -> Result<GitOutput, AppError>;
fn get_default_remote(&self) -> Result<GitOutput, AppError>;
fn get_tracked_remote(&self, tracked: &str) -> Result<GitOutput, AppError>;
fn get_upstream_branch(&self, branch: &str) -> Result<GitOutput, AppError>;
fn get_default_branch(&self, remote: &str) -> Result<GitOutput, AppError>;
fn is_valid_remote(&self, remote: &str) -> Result<GitOutput, AppError>;
fn get_current_tag(&self) -> Result<GitOutput, AppError>;
fn get_current_commit(&self) -> Result<GitOutput, AppError>;
}
impl GitTrait for Git {
fn is_valid_repository(&self) -> Result<GitOutput, AppError> {
execute(command(GitCommand::IsValidRepository)?)
}
fn get_local_branch(&self) -> Result<GitOutput, AppError> {
execute(command(GitCommand::LocalBranch)?)
}
fn get_default_remote(&self) -> Result<GitOutput, AppError> {
execute(command(GitCommand::DefaultRemote)?)
}
fn get_tracked_remote(&self, tracked: &str) -> Result<GitOutput, AppError> {
execute(command(GitCommand::TrackedRemote(tracked))?)
}
fn get_upstream_branch(&self, branch: &str) -> Result<GitOutput, AppError> {
execute(command(GitCommand::UpstreamBranch(branch))?)
}
fn get_default_branch(&self, remote: &str) -> Result<GitOutput, AppError> {
execute(command(GitCommand::DefaultBranch(remote))?)
}
fn is_valid_remote(&self, remote: &str) -> Result<GitOutput, AppError> {
execute(command(GitCommand::IsValidRemote(remote))?)
}
fn get_current_tag(&self) -> Result<GitOutput, AppError> {
execute(command(GitCommand::CurrentTag)?)
}
fn get_current_commit(&self) -> Result<GitOutput, AppError> {
execute(command(GitCommand::CurrentCommit)?)
}
}
fn command(git_command: GitCommand) -> Result<Output, std::io::Error> {
match git_command {
GitCommand::IsValidRepository => Command::new("git")
.arg("rev-parse")
.arg("--is-inside-work-tree")
.output(),
GitCommand::LocalBranch => Command::new("git")
.arg("symbolic-ref")
.arg("-q")
.arg("--short")
.arg("HEAD")
.output(),
GitCommand::DefaultRemote => Command::new("git")
.arg("config")
.arg("open.default.remote")
.output(),
GitCommand::TrackedRemote(branch) => Command::new("git")
.arg("config")
.arg(format!("branch.{}.remote", branch))
.output(),
GitCommand::UpstreamBranch(branch) => Command::new("git")
.arg("config")
.arg(format!("branch.{}.merge", branch))
.output(),
GitCommand::DefaultBranch(remote) => Command::new("git")
.arg("rev-parse")
.arg("--abbrev-ref")
.arg(format!("{}/HEAD", remote))
.output(),
GitCommand::IsValidRemote(remote) => Command::new("git")
.arg("ls-remote")
.arg("--get-url")
.arg(remote)
.output(),
GitCommand::CurrentTag => Command::new("git")
.arg("describe")
.arg("--tags")
.arg("--exact-match")
.output(),
GitCommand::CurrentCommit => Command::new("git").arg("rev-parse").arg("HEAD").output(),
}
}
fn execute(output: Output) -> Result<GitOutput, AppError> {
if output.status.success() {
Ok(GitOutput::Ok(trim(&output.stdout)?))
} else {
Ok(GitOutput::Err(trim(&output.stderr)?))
}
}
fn trim(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') {
utf_8_string.pop();
if utf_8_string.ends_with('\r') {
utf_8_string.pop();
}
}
Ok(utf_8_string)
}
impl Url {
pub(crate) fn new(protocol: &str, domain: Domain, path: &str) -> Self {
Self {
protocol: protocol.into(),
domain,
path: path.into(),
}
}
}
impl Domain {
pub(crate) fn from_str(s: &str) -> Self {
if s == "bitbucket.org" {
Domain::BitBucket
} else {
Domain::GitHub
}
}
}
impl PartialEq for Domain {
fn eq(&self, other: &Self) -> bool {
self.to_string() == other.to_string()
}
}
impl fmt::Display for Domain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Domain::GitHub => write!(f, "github.com"),
Domain::BitBucket => write!(f, "bitbucket.org"),
}
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save