├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── publish.yaml │ └── pull_request.yaml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── docs ├── gitweb.gif └── gitweb.mp4 └── src ├── git.rs ├── lib.rs ├── main.rs └── options.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | indent_size = 1 14 | 15 | [*.yaml] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: yoannfleurydev 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://paypal.me/yoannfleurydev'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" # Location of package manifests 8 | schedule: 9 | interval: "daily" 10 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish_on_crates_io: 10 | name: Publish on crates.io 11 | runs-on: ubuntu-latest 12 | outputs: 13 | upload_url: ${{ steps.create_release.outputs.upload_url }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Login to crates.io 17 | uses: actions-rs/cargo@v1 18 | with: 19 | command: login 20 | args: ${{ secrets.CRATES_IO_TOKEN_GITWEB }} 21 | - name: Package the crate 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: package 25 | - name: Publish the crate 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: publish 29 | - name: Create Release 30 | id: create_release 31 | uses: actions/create-release@v1 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | with: 35 | tag_name: ${{ github.ref }} 36 | release_name: Release ${{ github.ref }} 37 | 38 | release_linux: 39 | needs: publish_on_crates_io 40 | name: Linux Release 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - name: Linux Build 45 | uses: actions-rs/cargo@v1 46 | with: 47 | command: build 48 | args: --release 49 | - name: Upload Release Asset Linux 50 | id: upload-release-asset-linux 51 | uses: actions/upload-release-asset@v1 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | upload_url: ${{ needs.publish_on_crates_io.outputs.upload_url }} 56 | asset_path: ./target/release/gitweb 57 | asset_name: gitweb-linux 58 | asset_content_type: application/x-sharedlib 59 | 60 | release_windows: 61 | needs: publish_on_crates_io 62 | name: Windows Release 63 | runs-on: windows-latest 64 | steps: 65 | - uses: actions/checkout@v2 66 | - name: Windows Build 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: build 70 | args: --release 71 | - name: Upload Release Asset Windows 72 | id: upload-release-asset-windows 73 | uses: actions/upload-release-asset@v1 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | with: 77 | upload_url: ${{ needs.publish_on_crates_io.outputs.upload_url }} 78 | asset_path: ./target/release/gitweb.exe 79 | asset_name: gitweb-windows.exe 80 | asset_content_type: application/x-dosexec 81 | 82 | release_macos: 83 | needs: publish_on_crates_io 84 | name: MacOS Release 85 | runs-on: macos-latest 86 | steps: 87 | - uses: actions/checkout@v2 88 | - name: MacOS Build 89 | uses: actions-rs/cargo@v1 90 | with: 91 | command: build 92 | args: --release 93 | - name: Upload Release Asset MacOS 94 | id: upload-release-asset-macos 95 | uses: actions/upload-release-asset@v1 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | with: 99 | upload_url: ${{ needs.publish_on_crates_io.outputs.upload_url }} 100 | asset_path: ./target/release/gitweb 101 | asset_name: gitweb-macos 102 | asset_content_type: application/x-mach-binary 103 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | tests: 7 | name: Tests 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | 12 | runs-on: ${{ matrix.os }} 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Run tests 17 | run: cargo test --verbose 18 | - uses: actions-rs/clippy-check@v1 19 | with: 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | args: --all-features 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .vscode/ 3 | tags 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.3.5] - 2022-03-09 9 | 10 | ### Changed 11 | 12 | - upgrade dependencies 13 | 14 | ## [0.3.4] - 2022-01-16 15 | 16 | ### Changed 17 | 18 | - upgrade dependencies 19 | 20 | ## [0.3.3] - 2021-10-01 21 | 22 | ### Changed 23 | 24 | - upgrade dependencies 25 | 26 | ## [0.3.2] - 2021-06-24 27 | 28 | ### Changed 29 | 30 | - update README to add the homebrew installation instructions 31 | - upgrade dependencies 32 | 33 | ## [0.3.1] - 2021-04-17 34 | 35 | ### Changed 36 | 37 | - update the code to get the remote parts 38 | - remove the quircky regex 39 | 40 | ## [0.3.0] - 2021-01-30 41 | 42 | ### Added 43 | 44 | - new merge request option [#47](https://github.com/yoannfleurydev/gitweb/pull/47) [@antoinecarton](https://github.com/antoinecarton) 45 | - update dependencies 46 | 47 | ## [0.2.5] - 2020-10-17 48 | 49 | ### Changed 50 | 51 | - fix: map output for jobs steps 52 | 53 | ## [0.2.4] - 2020-10-17 54 | 55 | ### Changed 56 | 57 | - ci: update the way assets are uploaded 58 | 59 | ## [0.2.3] - 2020-10-14 60 | 61 | ### Changed 62 | 63 | - fix: remove tag refs in assets 64 | 65 | ## [0.2.2] - 2020-10-14 66 | 67 | ### Changed 68 | 69 | - build(deps): bump regex from 1.4.0 to 1.4.1 70 | - build(deps): bump git2 from 0.13.11 to 0.13.12 71 | - improve CI/CD with windows and macos binaries 72 | 73 | ## [0.2.1] - 2020-10-12 74 | 75 | ### Changed 76 | 77 | - bump flexi_logger from 0.15.12 to 0.16.1 78 | - bump anyhow from 1.0.32 to 1.0.33 79 | - bump thiserror from 1.0.20 to 1.0.21 80 | - update other deps 81 | 82 | ## [0.2.0] - 2020-10-05 83 | 84 | ### Added 85 | 86 | - add support for remote url without `.git` extension 87 | 88 | ### Changed 89 | 90 | - complete refactor of the code 91 | - remove custom code for BROWSER environment variable 92 | 93 | ## [0.1.13] - 2020-06-20 94 | 95 | ### Added 96 | 97 | - add support for `--commit` to open a specific commit by [@rubenrua](https://github.com/rubenrua) 98 | - add alias `--tag` for `--branch` as they are the same from git host provider perspective 99 | 100 | ## [0.1.12] - 2020-05-16 101 | 102 | ### Changed 103 | 104 | - rework regex to get domain and project path 105 | 106 | ## [0.1.11] - 2020-04-26 107 | 108 | ### Fixed 109 | 110 | - the remove port function was a bit to restrictive (it removed the first part of the path like _/path_removed/the/rest/of/the/path_) 111 | 112 | ### Security 113 | 114 | - upgrade dependencies 115 | 116 | ## [0.1.8] - 2019-07-19 117 | 118 | ### Added 119 | 120 | - add empty string option to --browser so the user can only have the output in the console 121 | 122 | ## [0.1.7] - 2019-07-08 123 | 124 | ### Fixed 125 | 126 | - remove the port from the url to fix the 404 (#3) 127 | 128 | ## [0.1.6] - 2019-07-02 129 | 130 | ### Added 131 | 132 | - no more panic 💥, now the program exit smoothly on errors 133 | - each error has its own code 134 | - [yoannfleurydev.github.io/gitweb](https://yoannfleurydev.github.io/gitweb) 135 | 136 | ### Changed 137 | 138 | - add more comment for the `--help` option. 139 | - add `print` function to output easily when the program is in error 140 | - renamed the old `print` method to `verbose_print` so the logger write onlyon verbose run 141 | - improve browser openning readability by removing ifs 142 | 143 | ### Security 144 | 145 | - fix all dependency on their minor release to have the latest ones 146 | 147 | ## [0.1.5] - 2019-07-01 148 | 149 | ### Fixed 150 | 151 | - the program will give back shell prompt when the browser is not already running (#2) 152 | 153 | ## [0.1.4] - 2019-03-04 154 | 155 | ### Fixed 156 | 157 | - now able to use gitweb in git repository subdirectories 158 | 159 | ## [0.1.3] - 2019-02-18 160 | 161 | ### Added 162 | 163 | - working CI 164 | 165 | ## [0.1.2] - 2019-02-17 166 | 167 | ### Added 168 | 169 | - editorconfig 170 | - git2 library to use git wrapper instead of system command 171 | 172 | ### Removed 173 | 174 | - custom commands to get git information 175 | 176 | ## [0.1.1] - 2019-02-16 177 | 178 | ### Added 179 | 180 | - this changelog 181 | - build status 182 | 183 | ### Changed 184 | 185 | - set default browser as the first to be open 186 | - allow `$BROWSER` to override the default browser of the system 187 | - allow `--browser` to override the `$BROWSER` environment variable and the default browser 188 | 189 | ## [0.1.0] - 2019-02-13 190 | 191 | ### Added 192 | 193 | - default behavior of the command is to open the current repository in the browser 194 | - add `--branch` option to open a custom branch (default behavior is the current branch of the repo) 195 | - add `--browser` to open a custom browser 196 | 197 | [0.1.4]: https://github.com/yoannfleurydev/gitweb/compare/v0.1.3...v0.1.4 198 | [0.1.3]: https://github.com/yoannfleurydev/gitweb/compare/v0.1.2...v0.1.3 199 | [0.1.2]: https://github.com/yoannfleurydev/gitweb/compare/v0.1.1...v0.1.2 200 | [0.1.1]: https://github.com/yoannfleurydev/gitweb/compare/v0.1.0...v0.1.1 201 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at yoann.fleury@yahoo.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | # Pull Request 4 | 5 | Just do the changes, we'll discuss about it on the PR. 6 | 7 | ## PUBLISHING 8 | 9 | - [ ] Change the code you want to update. 10 | - [ ] Update the version in [Cargo.toml](./Cargo.toml) 11 | - [ ] Update the [CHANGELOG.md](./CHANGELOG.md) with the changes you made 12 | - [ ] Commit (`git commit`) 13 | - [ ] Tag (`git tag -a v -m "Version "`) 14 | - [ ] Push (`git push --follow-tags`) 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "0.7.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "ansi_term" 31 | version = "0.12.1" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 34 | dependencies = [ 35 | "winapi", 36 | ] 37 | 38 | [[package]] 39 | name = "anyhow" 40 | version = "1.0.68" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" 43 | 44 | [[package]] 45 | name = "atty" 46 | version = "0.2.14" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 49 | dependencies = [ 50 | "hermit-abi", 51 | "libc", 52 | "winapi", 53 | ] 54 | 55 | [[package]] 56 | name = "autocfg" 57 | version = "1.1.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 60 | 61 | [[package]] 62 | name = "backtrace" 63 | version = "0.3.65" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" 66 | dependencies = [ 67 | "addr2line", 68 | "cc", 69 | "cfg-if", 70 | "libc", 71 | "miniz_oxide", 72 | "object", 73 | "rustc-demangle", 74 | ] 75 | 76 | [[package]] 77 | name = "bitflags" 78 | version = "1.3.2" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 81 | 82 | [[package]] 83 | name = "cc" 84 | version = "1.0.73" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 87 | dependencies = [ 88 | "jobserver", 89 | ] 90 | 91 | [[package]] 92 | name = "cfg-if" 93 | version = "1.0.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 96 | 97 | [[package]] 98 | name = "chrono" 99 | version = "0.4.19" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 102 | dependencies = [ 103 | "libc", 104 | "num-integer", 105 | "num-traits", 106 | "winapi", 107 | ] 108 | 109 | [[package]] 110 | name = "clap" 111 | version = "2.34.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" 114 | dependencies = [ 115 | "ansi_term", 116 | "atty", 117 | "bitflags", 118 | "strsim", 119 | "textwrap", 120 | "unicode-width", 121 | "vec_map", 122 | ] 123 | 124 | [[package]] 125 | name = "color-eyre" 126 | version = "0.6.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" 129 | dependencies = [ 130 | "backtrace", 131 | "color-spantrace", 132 | "eyre", 133 | "indenter", 134 | "once_cell", 135 | "owo-colors", 136 | "tracing-error", 137 | ] 138 | 139 | [[package]] 140 | name = "color-spantrace" 141 | version = "0.2.0" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" 144 | dependencies = [ 145 | "once_cell", 146 | "owo-colors", 147 | "tracing-core", 148 | "tracing-error", 149 | ] 150 | 151 | [[package]] 152 | name = "eyre" 153 | version = "0.6.8" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" 156 | dependencies = [ 157 | "indenter", 158 | "once_cell", 159 | ] 160 | 161 | [[package]] 162 | name = "flexi_logger" 163 | version = "0.22.3" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "969940c39bc718475391e53a3a59b0157e64929c80cf83ad5dde5f770ecdc423" 166 | dependencies = [ 167 | "ansi_term", 168 | "atty", 169 | "chrono", 170 | "glob", 171 | "lazy_static", 172 | "log", 173 | "regex", 174 | "rustversion", 175 | "thiserror", 176 | "time", 177 | ] 178 | 179 | [[package]] 180 | name = "form_urlencoded" 181 | version = "1.0.1" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 184 | dependencies = [ 185 | "matches", 186 | "percent-encoding", 187 | ] 188 | 189 | [[package]] 190 | name = "gimli" 191 | version = "0.26.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 194 | 195 | [[package]] 196 | name = "git-url-parse" 197 | version = "0.4.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b037f7449dd4a8b711e660301ff1ff28aa00eea09698421fb2d78db51a7b7a72" 200 | dependencies = [ 201 | "color-eyre", 202 | "regex", 203 | "strum", 204 | "strum_macros", 205 | "tracing", 206 | "url", 207 | ] 208 | 209 | [[package]] 210 | name = "git2" 211 | version = "0.14.3" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "5e77a14ffc6ba4ad5188d6cf428894c4fcfda725326b37558f35bb677e712cec" 214 | dependencies = [ 215 | "bitflags", 216 | "libc", 217 | "libgit2-sys", 218 | "log", 219 | "openssl-probe", 220 | "openssl-sys", 221 | "url", 222 | ] 223 | 224 | [[package]] 225 | name = "gitweb" 226 | version = "0.3.5" 227 | dependencies = [ 228 | "anyhow", 229 | "flexi_logger", 230 | "git-url-parse", 231 | "git2", 232 | "log", 233 | "open", 234 | "structopt", 235 | "thiserror", 236 | ] 237 | 238 | [[package]] 239 | name = "glob" 240 | version = "0.3.0" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 243 | 244 | [[package]] 245 | name = "heck" 246 | version = "0.3.3" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 249 | dependencies = [ 250 | "unicode-segmentation", 251 | ] 252 | 253 | [[package]] 254 | name = "heck" 255 | version = "0.4.0" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 258 | 259 | [[package]] 260 | name = "hermit-abi" 261 | version = "0.1.19" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 264 | dependencies = [ 265 | "libc", 266 | ] 267 | 268 | [[package]] 269 | name = "idna" 270 | version = "0.2.3" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 273 | dependencies = [ 274 | "matches", 275 | "unicode-bidi", 276 | "unicode-normalization", 277 | ] 278 | 279 | [[package]] 280 | name = "indenter" 281 | version = "0.3.3" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 284 | 285 | [[package]] 286 | name = "itoa" 287 | version = "1.0.1" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 290 | 291 | [[package]] 292 | name = "jobserver" 293 | version = "0.1.24" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 296 | dependencies = [ 297 | "libc", 298 | ] 299 | 300 | [[package]] 301 | name = "lazy_static" 302 | version = "1.4.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 305 | 306 | [[package]] 307 | name = "libc" 308 | version = "0.2.125" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 311 | 312 | [[package]] 313 | name = "libgit2-sys" 314 | version = "0.13.3+1.4.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" 317 | dependencies = [ 318 | "cc", 319 | "libc", 320 | "libssh2-sys", 321 | "libz-sys", 322 | "openssl-sys", 323 | "pkg-config", 324 | ] 325 | 326 | [[package]] 327 | name = "libssh2-sys" 328 | version = "0.2.23" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "b094a36eb4b8b8c8a7b4b8ae43b2944502be3e59cd87687595cf6b0a71b3f4ca" 331 | dependencies = [ 332 | "cc", 333 | "libc", 334 | "libz-sys", 335 | "openssl-sys", 336 | "pkg-config", 337 | "vcpkg", 338 | ] 339 | 340 | [[package]] 341 | name = "libz-sys" 342 | version = "1.1.6" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "92e7e15d7610cce1d9752e137625f14e61a28cd45929b6e12e47b50fe154ee2e" 345 | dependencies = [ 346 | "cc", 347 | "libc", 348 | "pkg-config", 349 | "vcpkg", 350 | ] 351 | 352 | [[package]] 353 | name = "log" 354 | version = "0.4.17" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 357 | dependencies = [ 358 | "cfg-if", 359 | ] 360 | 361 | [[package]] 362 | name = "matches" 363 | version = "0.1.9" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 366 | 367 | [[package]] 368 | name = "memchr" 369 | version = "2.5.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 372 | 373 | [[package]] 374 | name = "miniz_oxide" 375 | version = "0.5.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" 378 | dependencies = [ 379 | "adler", 380 | ] 381 | 382 | [[package]] 383 | name = "num-integer" 384 | version = "0.1.45" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 387 | dependencies = [ 388 | "autocfg", 389 | "num-traits", 390 | ] 391 | 392 | [[package]] 393 | name = "num-traits" 394 | version = "0.2.15" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 397 | dependencies = [ 398 | "autocfg", 399 | ] 400 | 401 | [[package]] 402 | name = "num_threads" 403 | version = "0.1.6" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 406 | dependencies = [ 407 | "libc", 408 | ] 409 | 410 | [[package]] 411 | name = "object" 412 | version = "0.28.3" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" 415 | dependencies = [ 416 | "memchr", 417 | ] 418 | 419 | [[package]] 420 | name = "once_cell" 421 | version = "1.10.0" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 424 | 425 | [[package]] 426 | name = "open" 427 | version = "2.1.2" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "e0524af9508f9b5c4eb41dce095860456727748f63b478d625f119a70e0d764a" 430 | dependencies = [ 431 | "pathdiff", 432 | "winapi", 433 | ] 434 | 435 | [[package]] 436 | name = "openssl-probe" 437 | version = "0.1.5" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 440 | 441 | [[package]] 442 | name = "openssl-sys" 443 | version = "0.9.73" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" 446 | dependencies = [ 447 | "autocfg", 448 | "cc", 449 | "libc", 450 | "pkg-config", 451 | "vcpkg", 452 | ] 453 | 454 | [[package]] 455 | name = "owo-colors" 456 | version = "3.5.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" 459 | 460 | [[package]] 461 | name = "pathdiff" 462 | version = "0.2.1" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" 465 | 466 | [[package]] 467 | name = "percent-encoding" 468 | version = "2.1.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 471 | 472 | [[package]] 473 | name = "pin-project-lite" 474 | version = "0.2.9" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 477 | 478 | [[package]] 479 | name = "pkg-config" 480 | version = "0.3.25" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" 483 | 484 | [[package]] 485 | name = "proc-macro-error" 486 | version = "1.0.4" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 489 | dependencies = [ 490 | "proc-macro-error-attr", 491 | "proc-macro2", 492 | "quote", 493 | "syn", 494 | "version_check", 495 | ] 496 | 497 | [[package]] 498 | name = "proc-macro-error-attr" 499 | version = "1.0.4" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 502 | dependencies = [ 503 | "proc-macro2", 504 | "quote", 505 | "version_check", 506 | ] 507 | 508 | [[package]] 509 | name = "proc-macro2" 510 | version = "1.0.38" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" 513 | dependencies = [ 514 | "unicode-xid", 515 | ] 516 | 517 | [[package]] 518 | name = "quote" 519 | version = "1.0.18" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 522 | dependencies = [ 523 | "proc-macro2", 524 | ] 525 | 526 | [[package]] 527 | name = "regex" 528 | version = "1.5.5" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 531 | dependencies = [ 532 | "aho-corasick", 533 | "memchr", 534 | "regex-syntax", 535 | ] 536 | 537 | [[package]] 538 | name = "regex-syntax" 539 | version = "0.6.25" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 542 | 543 | [[package]] 544 | name = "rustc-demangle" 545 | version = "0.1.21" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 548 | 549 | [[package]] 550 | name = "rustversion" 551 | version = "1.0.6" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 554 | 555 | [[package]] 556 | name = "sharded-slab" 557 | version = "0.1.4" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 560 | dependencies = [ 561 | "lazy_static", 562 | ] 563 | 564 | [[package]] 565 | name = "strsim" 566 | version = "0.8.0" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 569 | 570 | [[package]] 571 | name = "structopt" 572 | version = "0.3.26" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" 575 | dependencies = [ 576 | "clap", 577 | "lazy_static", 578 | "structopt-derive", 579 | ] 580 | 581 | [[package]] 582 | name = "structopt-derive" 583 | version = "0.4.18" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" 586 | dependencies = [ 587 | "heck 0.3.3", 588 | "proc-macro-error", 589 | "proc-macro2", 590 | "quote", 591 | "syn", 592 | ] 593 | 594 | [[package]] 595 | name = "strum" 596 | version = "0.24.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 599 | 600 | [[package]] 601 | name = "strum_macros" 602 | version = "0.24.3" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 605 | dependencies = [ 606 | "heck 0.4.0", 607 | "proc-macro2", 608 | "quote", 609 | "rustversion", 610 | "syn", 611 | ] 612 | 613 | [[package]] 614 | name = "syn" 615 | version = "1.0.92" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" 618 | dependencies = [ 619 | "proc-macro2", 620 | "quote", 621 | "unicode-xid", 622 | ] 623 | 624 | [[package]] 625 | name = "textwrap" 626 | version = "0.11.0" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 629 | dependencies = [ 630 | "unicode-width", 631 | ] 632 | 633 | [[package]] 634 | name = "thiserror" 635 | version = "1.0.31" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 638 | dependencies = [ 639 | "thiserror-impl", 640 | ] 641 | 642 | [[package]] 643 | name = "thiserror-impl" 644 | version = "1.0.31" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 647 | dependencies = [ 648 | "proc-macro2", 649 | "quote", 650 | "syn", 651 | ] 652 | 653 | [[package]] 654 | name = "thread_local" 655 | version = "1.1.4" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 658 | dependencies = [ 659 | "once_cell", 660 | ] 661 | 662 | [[package]] 663 | name = "time" 664 | version = "0.3.9" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 667 | dependencies = [ 668 | "itoa", 669 | "libc", 670 | "num_threads", 671 | "time-macros", 672 | ] 673 | 674 | [[package]] 675 | name = "time-macros" 676 | version = "0.2.4" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 679 | 680 | [[package]] 681 | name = "tinyvec" 682 | version = "1.6.0" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 685 | dependencies = [ 686 | "tinyvec_macros", 687 | ] 688 | 689 | [[package]] 690 | name = "tinyvec_macros" 691 | version = "0.1.0" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 694 | 695 | [[package]] 696 | name = "tracing" 697 | version = "0.1.34" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" 700 | dependencies = [ 701 | "cfg-if", 702 | "pin-project-lite", 703 | "tracing-attributes", 704 | "tracing-core", 705 | ] 706 | 707 | [[package]] 708 | name = "tracing-attributes" 709 | version = "0.1.21" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" 712 | dependencies = [ 713 | "proc-macro2", 714 | "quote", 715 | "syn", 716 | ] 717 | 718 | [[package]] 719 | name = "tracing-core" 720 | version = "0.1.26" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" 723 | dependencies = [ 724 | "lazy_static", 725 | "valuable", 726 | ] 727 | 728 | [[package]] 729 | name = "tracing-error" 730 | version = "0.2.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" 733 | dependencies = [ 734 | "tracing", 735 | "tracing-subscriber", 736 | ] 737 | 738 | [[package]] 739 | name = "tracing-subscriber" 740 | version = "0.3.11" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" 743 | dependencies = [ 744 | "sharded-slab", 745 | "thread_local", 746 | "tracing-core", 747 | ] 748 | 749 | [[package]] 750 | name = "unicode-bidi" 751 | version = "0.3.8" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 754 | 755 | [[package]] 756 | name = "unicode-normalization" 757 | version = "0.1.19" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 760 | dependencies = [ 761 | "tinyvec", 762 | ] 763 | 764 | [[package]] 765 | name = "unicode-segmentation" 766 | version = "1.9.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" 769 | 770 | [[package]] 771 | name = "unicode-width" 772 | version = "0.1.9" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 775 | 776 | [[package]] 777 | name = "unicode-xid" 778 | version = "0.2.3" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" 781 | 782 | [[package]] 783 | name = "url" 784 | version = "2.2.2" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 787 | dependencies = [ 788 | "form_urlencoded", 789 | "idna", 790 | "matches", 791 | "percent-encoding", 792 | ] 793 | 794 | [[package]] 795 | name = "valuable" 796 | version = "0.1.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 799 | 800 | [[package]] 801 | name = "vcpkg" 802 | version = "0.2.15" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 805 | 806 | [[package]] 807 | name = "vec_map" 808 | version = "0.8.2" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 811 | 812 | [[package]] 813 | name = "version_check" 814 | version = "0.9.4" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 817 | 818 | [[package]] 819 | name = "winapi" 820 | version = "0.3.9" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 823 | dependencies = [ 824 | "winapi-i686-pc-windows-gnu", 825 | "winapi-x86_64-pc-windows-gnu", 826 | ] 827 | 828 | [[package]] 829 | name = "winapi-i686-pc-windows-gnu" 830 | version = "0.4.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 833 | 834 | [[package]] 835 | name = "winapi-x86_64-pc-windows-gnu" 836 | version = "0.4.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 839 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Yoann Fleury "] 3 | categories = ["command-line-utilities"] 4 | description = "Open the current remote repository in your browser" 5 | edition = "2018" 6 | exclude = [ 7 | "docs/", 8 | ".editorconfig", 9 | ".github/", 10 | "*.md", 11 | ] 12 | homepage = "https://github.com/yoannfleurydev/gitweb" 13 | keywords = ["git", "browser"] 14 | license = "Apache-2.0" 15 | name = "gitweb" 16 | readme = "README.md" 17 | repository = "https://github.com/yoannfleurydev/gitweb" 18 | version = "0.3.5" 19 | 20 | [dependencies] 21 | anyhow = "1.0" 22 | flexi_logger = {version = "0.22", features = ["use_chrono_for_offset"]} 23 | git-url-parse = "0.4" 24 | git2 = "0.14" 25 | log = "0.4" 26 | open = "2.1" 27 | structopt = "0.3" 28 | thiserror = "1.0" 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitweb 2 | 3 | ![Publish](https://github.com/yoannfleurydev/gitweb/workflows/Publish/badge.svg) 4 | 5 | > Some of the flags and options are subject to change in the future. 6 | > Ideas are welcome. Ideas are bulletproof (V). 7 | 8 | `gitweb` is a command line interface I created mainly to learn Rust. 9 | 10 | ![preview](./docs/gitweb.gif) 11 | 12 | ## Intallation 13 | 14 | ### 🍺 The homebrew way 15 | 16 | ```sh 17 | brew install yoannfleurydev/gitweb/gitweb 18 | # or 19 | brew tap yoannfleurydev/gitweb 20 | brew install gitweb 21 | ``` 22 | 23 | ### 📦 The Cargo way 24 | 25 | ```sh 26 | cargo install gitweb 27 | ``` 28 | 29 | ### ⚙️ The binary way 30 | 31 | Download the binary from the [latest release](https://github.com/yoannfleurydev/gitweb/releases/latest) and put it in your PATH. 32 | 33 | ### 🖥 The MacPorts way 34 | 35 | ``` 36 | sudo port selfupdate 37 | sudo port install gitweb 38 | ``` 39 | 40 | ## Usage 41 | 42 | `gitweb` will by default open the remote in the browser of the current 43 | repository. 44 | 45 | ``` 46 | gitweb 0.3.1 47 | 48 | USAGE: 49 | gitweb [FLAGS] [OPTIONS] 50 | 51 | FLAGS: 52 | -h, --help Prints help information 53 | -M, --merge-request Set the merge request flag 54 | -V, --version Prints version information 55 | -v, --verbose Set the verbosity of the command 56 | 57 | OPTIONS: 58 | -b, --branch Set the branch (alias for --tag) 59 | -B, --browser Set the browser [env: BROWSER=] 60 | -c, --commit Set a commit 61 | -r, --remote Set the remote 62 | -t, --tag Set the tag (alias for --branch) 63 | ``` 64 | 65 | ## --branch, --tag 66 | 67 | `gitweb` will open the current branch or tag on the remote repository. You can 68 | override the behavior by giving either `--branch` or `--tag` flag with the 69 | custom branch or tag you want to open in the browser. 70 | 71 | ## --browser 72 | 73 | `gitweb` tries to start one of the following browser (in that order of priority): 74 | 75 | - `--browser` option given in the command line 76 | - `$BROWSER` on Linux 🐧 or `%BROWSER%` on Windows 🏁 (this is a non standard variable) 77 | - the default web browser on the system 78 | 79 | ## --commit 80 | 81 | `gitweb` will open the commit given as a parameter on the remote repository. 82 | 83 | ## --remote 84 | 85 | `gitweb` will open the origin remote if it exists. You can override the behavior 86 | by giving the `--remote` flag with the custom remote you want to open. 87 | -------------------------------------------------------------------------------- /docs/gitweb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoannfleurydev/gitweb/ac90af42d03a1e5de8f803307dbf1d4868f8baf6/docs/gitweb.gif -------------------------------------------------------------------------------- /docs/gitweb.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yoannfleurydev/gitweb/ac90af42d03a1e5de8f803307dbf1d4868f8baf6/docs/gitweb.mp4 -------------------------------------------------------------------------------- /src/git.rs: -------------------------------------------------------------------------------- 1 | use crate::Issue; 2 | use anyhow::Result; 3 | use git2::{ErrorCode, Repository}; 4 | 5 | /// Get the current repository. 6 | // Will check that the user is in a git repository. 7 | pub fn get_repo() -> Result { 8 | const CURRENT_WORKING_DIRECTORY: &str = "."; 9 | 10 | let repo = 11 | Repository::discover(CURRENT_WORKING_DIRECTORY).map_err(|_| Issue::NotInAGitRepository)?; 12 | 13 | Ok(repo) 14 | } 15 | 16 | // Get the current branch or return master. 17 | pub fn get_branch(repo: &Repository) -> String { 18 | let head = match repo.head() { 19 | Ok(head) => Some(head), 20 | Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => { 21 | None 22 | } 23 | Err(e) => { 24 | error!("failed to get head ref '{}'", e); 25 | None 26 | } 27 | }; 28 | 29 | let head = head.as_ref().and_then(|h| h.shorthand()); 30 | debug!( 31 | "On branch '{}'", 32 | head.unwrap_or("Not currently on any branch") 33 | ); 34 | 35 | String::from(head.unwrap_or("master")) 36 | } 37 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use git_url_parse::GitUrl; 2 | use thiserror::Error; 3 | 4 | use crate::options::Opt; 5 | 6 | #[macro_use] 7 | extern crate log; 8 | 9 | mod git; 10 | pub mod options; 11 | 12 | const BITBUCKET_HOSTNAME: &str = "bitbucket.org"; 13 | const GITHUB_HOSTNAME: &str = "github.com"; 14 | const GITLAB_HOSTNAME: &str = "gitlab.com"; 15 | const GITEA_HOSTNAME: &str = "gitea.io"; 16 | 17 | #[derive(Debug, Eq, Error, PartialEq, Clone)] 18 | pub enum Issue { 19 | #[error("Command failed, please run command inside a git directory")] 20 | NotInAGitRepository, 21 | #[error("No matching remote url found for '{0}' remote name")] 22 | NoRemoteMatching(String), 23 | #[error("No remote available")] 24 | NoRemoteAvailable, 25 | #[error("Not able to open system browser")] 26 | NotAbleToOpenSystemBrowser, 27 | #[error("Unable to open browser '{0}'")] 28 | BrowserNotAvailable(String), 29 | #[error("Unable to get remote parts, please open an issue as it might come from the code")] 30 | UnableToGetRemoteParts, 31 | #[error("Unknown provider")] 32 | UnknownProvider, 33 | } 34 | 35 | pub struct Success; 36 | type Result = core::result::Result; 37 | 38 | impl Issue { 39 | pub fn exit_code(&self) -> i32 { 40 | match self { 41 | Self::NotInAGitRepository => 1, 42 | Self::NoRemoteMatching(..) => 2, 43 | Self::NoRemoteAvailable => 3, 44 | Self::NotAbleToOpenSystemBrowser => 4, 45 | Self::BrowserNotAvailable(..) => 5, 46 | Self::UnableToGetRemoteParts => 6, 47 | Self::UnknownProvider => 7, 48 | } 49 | } 50 | } 51 | 52 | enum GitProvider { 53 | GitHub, 54 | GitLab, 55 | Bitbucket, 56 | Gitea, 57 | } 58 | 59 | impl Default for GitProvider { 60 | fn default() -> Self { 61 | Self::GitHub 62 | } 63 | } 64 | 65 | impl GitProvider { 66 | fn hostname(&self) -> String { 67 | match self { 68 | Self::GitHub => GITHUB_HOSTNAME, 69 | Self::GitLab => GITLAB_HOSTNAME, 70 | Self::Bitbucket => BITBUCKET_HOSTNAME, 71 | Self::Gitea => GITEA_HOSTNAME, 72 | } 73 | .to_string() 74 | } 75 | } 76 | 77 | pub struct RemoteParts { 78 | domain: String, 79 | repository: String, 80 | } 81 | 82 | struct MergeRequestParts { 83 | path: String, 84 | tail: String, 85 | } 86 | 87 | const DEFAULT_REMOTE_ORIGIN: &str = "origin"; 88 | 89 | fn get_remote_parts(url: &str) -> anyhow::Result { 90 | let giturl = GitUrl::parse(url).map_err(|_| Issue::UnableToGetRemoteParts)?; 91 | 92 | let domain = giturl 93 | .host 94 | .map_or(GitProvider::GitHub.hostname(), |m| m.as_str().to_string()); 95 | 96 | let repository = giturl 97 | .path 98 | .replace(".git", "") // don't want the .git part 99 | .split('/') 100 | .filter(|s| !s.is_empty()) 101 | .collect::>() 102 | .join("/"); 103 | 104 | Ok(RemoteParts { domain, repository }) 105 | } 106 | 107 | fn get_merge_request_parts(domain: &str) -> anyhow::Result { 108 | match domain { 109 | GITHUB_HOSTNAME => Ok(MergeRequestParts { 110 | path: "pulls".to_string(), 111 | tail: "".to_string(), 112 | }), 113 | GITLAB_HOSTNAME => Ok(MergeRequestParts { 114 | path: "-/merge_requests".to_string(), 115 | tail: "".to_string(), 116 | }), 117 | BITBUCKET_HOSTNAME => Ok(MergeRequestParts { 118 | path: "pull-requests".to_string(), 119 | tail: "".to_string(), 120 | }), 121 | GITEA_HOSTNAME => Ok(MergeRequestParts { 122 | path: "pulls".to_string(), 123 | tail: "".to_string(), 124 | }), 125 | _ => Err(Issue::UnknownProvider), 126 | } 127 | } 128 | 129 | pub fn run(opt: Opt) -> Result { 130 | // let logger = logger::Logger::new(opt.verbose); 131 | debug!("Verbose mode is active"); 132 | 133 | let repo = git::get_repo()?; 134 | 135 | // Get the tag to show in the browser. If the option is given, then the value 136 | // will be used as it is an alias for branch. 137 | let reference = if let Some(tag) = opt.tag { 138 | tag 139 | } else { 140 | // Get the branch to show in the browser. If the option is given, then, the 141 | // value will be used, else, the current branch is given, or master if 142 | // something went wrong. 143 | opt.branch.unwrap_or_else(|| { 144 | debug!("No branch given, getting current one"); 145 | git::get_branch(&repo) 146 | }) 147 | }; 148 | 149 | let remote_name = &opt 150 | .remote 151 | .unwrap_or_else(|| String::from(DEFAULT_REMOTE_ORIGIN)); 152 | 153 | debug!("Getting remote url for '{}' remote name", remote_name); 154 | 155 | let optional_remote = repo 156 | .find_remote(remote_name) 157 | .map_err(|_| Issue::NoRemoteMatching(remote_name.clone()))?; 158 | 159 | let remote_url = optional_remote 160 | .url() 161 | .ok_or(()) 162 | .map_err(|_| Issue::NoRemoteAvailable)?; 163 | 164 | let RemoteParts { domain, repository } = get_remote_parts(remote_url).unwrap(); 165 | 166 | let (path, tail) = if let Some(commit) = opt.commit { 167 | let path = if domain == GitProvider::Bitbucket.hostname() { 168 | "commits" 169 | } else { 170 | "commit" 171 | }; 172 | 173 | (path, commit) 174 | } else { 175 | let path = if domain == GitProvider::Bitbucket.hostname() { 176 | "src" 177 | } else { 178 | "tree" 179 | }; 180 | 181 | (path, reference) 182 | }; 183 | 184 | let (path, tail) = if opt.merge_request { 185 | debug!("Getting merge request parts for domain '{}'", domain); 186 | let MergeRequestParts { path, tail } = get_merge_request_parts(&domain).unwrap(); 187 | (path, tail) 188 | } else { 189 | (path.to_owned(), tail) 190 | }; 191 | 192 | // Generate the requested url that has to be opened in the browser 193 | let url = generate_url(&domain, &repository, &path, &tail); 194 | 195 | // If the option is available through the command line, open the given one 196 | match opt.browser { 197 | Some(option_browser) => { 198 | debug!("Browser '{}' given as option", option_browser); 199 | 200 | if option_browser == *"" { 201 | println!("{}", url); 202 | } 203 | 204 | open::with(&url, &option_browser) 205 | .map_err(|_| Issue::BrowserNotAvailable(option_browser))?; 206 | 207 | Ok(Success) 208 | } 209 | None => { 210 | // Open the default web browser on the current system. 211 | match open::that(&url) { 212 | Ok(_) => { 213 | debug!("Default browser is now opened"); 214 | Ok(Success) 215 | } 216 | Err(_) => Err(Issue::NotAbleToOpenSystemBrowser), 217 | } 218 | } 219 | } 220 | } 221 | 222 | fn generate_url(domain: &str, repository: &str, path: &str, tail: &str) -> String { 223 | format!( 224 | "https://{domain}/{repository}/{path}/{tail}", 225 | domain = domain, 226 | path = path, 227 | repository = repository, 228 | tail = tail 229 | ) 230 | } 231 | 232 | #[cfg(test)] 233 | mod tests { 234 | // Note this useful idiom: importing names from outer (for mod tests) scope. 235 | use super::*; 236 | 237 | #[test] 238 | fn test_without_ssh_git_and_without_extension_url_parts() { 239 | let RemoteParts { domain, repository } = 240 | get_remote_parts("git@github.com:yoannfleurydev/gitweb").unwrap(); 241 | 242 | assert_eq!(domain, "github.com"); 243 | assert_eq!(repository, "yoannfleurydev/gitweb"); 244 | } 245 | 246 | #[test] 247 | fn test_without_ssh_git_url_parts() { 248 | let RemoteParts { domain, repository } = 249 | get_remote_parts("git@github.com:yoannfleurydev/gitweb.git").unwrap(); 250 | 251 | assert_eq!(domain, "github.com"); 252 | assert_eq!(repository, "yoannfleurydev/gitweb"); 253 | } 254 | 255 | #[test] 256 | fn test_with_ssh_and_multiple_subgroups_git_url_parts() { 257 | let RemoteParts { domain, repository } = 258 | get_remote_parts("ssh://git@gitlab.com/group/subgroup/subsubgroup/design-system.git") 259 | .unwrap(); 260 | 261 | assert_eq!(domain, "gitlab.com"); 262 | assert_eq!(repository, "group/subgroup/subsubgroup/design-system"); 263 | } 264 | 265 | #[test] 266 | fn test_with_ssh_and_port_git_url_parts() { 267 | let RemoteParts { domain, repository } = 268 | get_remote_parts("ssh://user@host.xz:22/path/to/repo.git/").unwrap(); 269 | 270 | assert_eq!(domain, "host.xz"); 271 | assert_eq!(repository, "path/to/repo"); 272 | } 273 | 274 | #[test] 275 | fn test_with_http_and_port_git_url_parts() { 276 | let RemoteParts { domain, repository } = 277 | get_remote_parts("http://host.xz:80/path/to/repo.git/").unwrap(); 278 | 279 | assert_eq!(domain, "host.xz"); 280 | assert_eq!(repository, "path/to/repo"); 281 | } 282 | 283 | #[test] 284 | fn test_with_http_dash_and_port_git_url_parts() { 285 | let RemoteParts { domain, repository } = 286 | get_remote_parts("http://host-dash.xz:80/path/to/repo.git/").unwrap(); 287 | 288 | assert_eq!(domain, "host-dash.xz"); 289 | assert_eq!(repository, "path/to/repo"); 290 | } 291 | 292 | #[test] 293 | fn test_with_http_git_url_parts() { 294 | let RemoteParts { domain, repository } = 295 | get_remote_parts("https://host.xz/path/to/repo.git/").unwrap(); 296 | 297 | assert_eq!(domain, "host.xz"); 298 | assert_eq!(repository, "path/to/repo"); 299 | } 300 | 301 | #[test] 302 | fn test_get_merge_request_parts_with_github() { 303 | let MergeRequestParts { path, tail } = get_merge_request_parts(GITHUB_HOSTNAME).unwrap(); 304 | 305 | assert_eq!(path, "pulls"); 306 | assert_eq!(tail, ""); 307 | } 308 | 309 | #[test] 310 | fn test_get_merge_request_parts_with_gitlab() { 311 | let MergeRequestParts { path, tail } = get_merge_request_parts(GITLAB_HOSTNAME).unwrap(); 312 | 313 | assert_eq!(path, "-/merge_requests"); 314 | assert_eq!(tail, ""); 315 | } 316 | 317 | #[test] 318 | fn test_get_merge_request_parts_with_bitbucket() { 319 | let MergeRequestParts { path, tail } = get_merge_request_parts(BITBUCKET_HOSTNAME).unwrap(); 320 | 321 | assert_eq!(path, "pull-requests"); 322 | assert_eq!(tail, ""); 323 | } 324 | 325 | #[test] 326 | fn test_get_merge_request_parts_with_gitea() { 327 | let MergeRequestParts { path, tail } = get_merge_request_parts(GITEA_HOSTNAME).unwrap(); 328 | 329 | assert_eq!(path, "pulls"); 330 | assert_eq!(tail, ""); 331 | } 332 | 333 | #[test] 334 | fn test_get_merge_request_parts_with_unknown_provider() { 335 | let result = get_merge_request_parts("host.xz"); 336 | 337 | assert_eq!(result.err(), Some(Issue::UnknownProvider)); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use flexi_logger::Logger; 2 | use gitweb::options::Opt; 3 | use gitweb::run; 4 | use std::process::exit; 5 | use structopt::StructOpt; 6 | 7 | #[macro_use] 8 | extern crate log; 9 | 10 | fn main() { 11 | // Get the command line options. 12 | let opt = Opt::from_args(); 13 | 14 | // Enable the logger with the output mode based on the given option. 15 | Logger::try_with_str(if opt.verbose { "debug" } else { "info" }) 16 | .unwrap() 17 | .start() 18 | .unwrap_or_else(|e| panic!("Logger initialization failed with {}", e)); 19 | 20 | // Run the program using the given options. 21 | match run(opt) { 22 | Ok(_) => (), 23 | Err(err) => { 24 | info!("{}", err); 25 | exit(err.exit_code()); 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | extern crate structopt; 2 | 3 | use structopt::StructOpt; 4 | 5 | #[derive(Debug, StructOpt)] 6 | // Rename all will use the name of the field 7 | #[structopt(rename_all = "kebab-case")] 8 | pub struct Opt { 9 | /// Set the branch (alias for --tag) 10 | /// 11 | /// By setting the branch, you can override the default behavior that will 12 | /// set the branch to the current one in the repository. If something went 13 | /// wrong with the current one, it will set the value to master. 14 | #[structopt(short, long)] 15 | pub branch: Option, 16 | 17 | /// Set the tag (alias for --branch). 18 | /// 19 | /// By setting the tag, you can override the default behavior that will set 20 | /// the branch to the current one in the repository and will instead take 21 | /// the reference tag (or branch) given as parameter. 22 | #[structopt(short, long)] 23 | pub tag: Option, 24 | 25 | /// Set the merge request flag. 26 | /// 27 | /// By setting the merge_request flag, you can override the default behavior that will 28 | /// open up the merge requests listing page. 29 | #[structopt(short = "-M", long = "--merge-request", conflicts_with_all = &["commit", "tag", "branch"])] 30 | pub merge_request: bool, 31 | 32 | /// Set a commit 33 | /// 34 | /// By setting a commit, you can override the default behavior that will 35 | /// set the branch to the current one in the repository. 36 | #[structopt(short, long)] 37 | pub commit: Option, 38 | 39 | /// Set the browser 40 | /// 41 | /// If you set the browser option, it will override the other configuration. 42 | /// Here is the list by order of overrides: the --browser option given in 43 | /// the command line, then the environment variable $BROWSER on Linux or 44 | /// %BROWSER% on Windows (this is a non standard variable), then the default 45 | /// web browser on the system 46 | /// If you give an empty string to browser option, the program will only 47 | /// print the remote URL into the stdout. 48 | #[structopt(short = "-B", long, env)] 49 | pub browser: Option, 50 | 51 | /// Set the remote 52 | /// 53 | /// By default, the selected remote will be origin by convention. You can 54 | /// override this setting by using this option. 55 | #[structopt(short, long)] 56 | pub remote: Option, 57 | 58 | /// Set the verbosity of the command 59 | /// 60 | /// By settings this option, you will have more feedback on the output. 61 | #[structopt(short, long)] 62 | pub verbose: bool, 63 | } 64 | --------------------------------------------------------------------------------