From ca990f460e0a42701c4a9d7599b06b08be240a57 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Thu, 11 Sep 2025 17:19:52 -0600 Subject: [PATCH] ci: Preparing the repo to go public --- .actrc | 4 + .cargo-husky/hooks/pre-commit | 13 + .cargo-husky/hooks/pre-push | 13 + .cz.toml | 10 + .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE/blank_issue.md | 4 + .github/ISSUE_TEMPLATE/bug_report.yaml | 69 ++++ .github/ISSUE_TEMPLATE/enhancement.md | 13 + .github/workflows/check.yml | 84 ++++ .github/workflows/release.yml | 548 +++++++++++++++++++++++++ .github/workflows/test.yml | 100 +++++ .pre-commit-config.yaml | 8 + CODE_OF_CONDUCT.md | 128 ++++++ CONTRIBUTING.md | 80 ++++ Cargo.lock | 7 + Cargo.toml | 2 + Makefile | 40 ++ README.md | 10 +- SECURITY.md | 15 + codecov.yml | 14 + package-lock.json | 6 + package.json | 1 + src/bin/gman/cli.rs | 2 +- src/bin/gman/main.rs | 4 +- src/config.rs | 32 +- src/lib.rs | 8 +- src/providers/git_sync.rs | 39 +- src/providers/local.rs | 10 +- src/providers/mod.rs | 2 +- tests/bin/mod.rs | 2 +- tests/tests.rs | 6 +- 31 files changed, 1229 insertions(+), 46 deletions(-) create mode 100644 .actrc create mode 100755 .cargo-husky/hooks/pre-commit create mode 100755 .cargo-husky/hooks/pre-push create mode 100644 .cz.toml create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/blank_issue.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md create mode 100644 .github/workflows/check.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .pre-commit-config.yaml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Makefile create mode 100644 SECURITY.md create mode 100644 codecov.yml create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.actrc b/.actrc new file mode 100644 index 0000000..151033e --- /dev/null +++ b/.actrc @@ -0,0 +1,4 @@ +--artifact-server-path=./.act/artifacts +--cache-server-path=./.act/cache +--container-options --privileged +--env ACT=true diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit new file mode 100755 index 0000000..06a5053 --- /dev/null +++ b/.cargo-husky/hooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +echo "Running pre-push hook:" + +echo "Executing: cargo fmt" +cargo fmt + +echo "Executing: make lint" +make lint + +echo "Executing: cargo test" +cargo test -- --skip prop_ diff --git a/.cargo-husky/hooks/pre-push b/.cargo-husky/hooks/pre-push new file mode 100755 index 0000000..0ef71b8 --- /dev/null +++ b/.cargo-husky/hooks/pre-push @@ -0,0 +1,13 @@ +#!/bin/sh +set -e + +echo "Running pre-push hook:" + +echo "Executing: cargo fmt --check" +cargo fmt --check + +echo "Executing: make lint" +make lint + +echo "Executing: cargo test" +cargo test -- --skip prop_ diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..5141f4b --- /dev/null +++ b/.cz.toml @@ -0,0 +1,10 @@ +[tool.commitizen] +name = "cz_conventional_commits" +tag_format = "v$version" +version_scheme = "semver" +version_provider = "cargo" +update_changelog_on_bump = true +major_version_zero = true + +[tool.commitizen.hooks] +pre-commit = "git add Cargo.toml Cargo.lock" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..95dbf38 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: Dark-Alex-17 diff --git a/.github/ISSUE_TEMPLATE/blank_issue.md b/.github/ISSUE_TEMPLATE/blank_issue.md new file mode 100644 index 0000000..9aef3eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/blank_issue.md @@ -0,0 +1,4 @@ +--- +name: Blank Issue +about: Create a blank issue. +--- diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml new file mode 100644 index 0000000..c5c735e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -0,0 +1,69 @@ +name: Bug Report +description: Create a report to help us improve +labels: bug +body: + - type: markdown + attributes: + value: Thank you for filing a bug report! + - type: textarea + id: problem + attributes: + label: Summary + description: > + Please provide a short summary of the bug, along with any information + you feel relevant to replicate the bug. + validations: + required: true + - type: textarea + id: reproduction-steps + attributes: + label: Reproduction Steps + value: | + I tried this: + + 1. `gman` + + I expected this to happen: + + Instead, this happened: + - type: textarea + id: gman-log + attributes: + label: G-Man log + description: Include the G-Man log file to help diagnose the issue. + value: | + | OS | Log file location | + | ------- | ----------------------------------------------------- | + | Linux | `~/.cache/gman/gman.log` | + | Mac | `~/Library/Logs/gman/gman.log` | + | Windows | `C:\Users\\AppData\Local\gman\gman.log` | + + ``` + please provide a copy of your gman log file here if possible, you may need to redact some of the lines + ``` + + - type: input + id: platform + attributes: + label: Platform + placeholder: Linux / macOS / Windows + validations: + required: true + - type: input + id: terminal-emulator + attributes: + label: Terminal Emulator + placeholder: wezterm 20220101-133340-7edc5b5a + validations: + required: true + - type: input + id: gman-version + attributes: + label: G-Man Version + description: > + G-Man version (`gman --version` if using a release, `git describe` if building + from main). + **Make sure that you are using the [latest gman release](https://github.com/Dark-Alex-17/gman/releases) or a newer main build** + placeholder: "gman 0.1.0" + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..b808e71 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,13 @@ +--- +name: Enhancement +about: Suggest an improvement +title: '' +labels: enhancement +assignees: '' +--- + + diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..4075ce9 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,84 @@ +on: + push: + branches: + - main + pull_request: + +name: Check + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + name: stable / fmt + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + + - name: Run cargo fmt + run: cargo fmt -- --check + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + clippy: + name: ${{ matrix.toolchain }} / clippy + runs-on: ubuntu-latest + permissions: + checks: write + strategy: + fail-fast: false + matrix: + toolchain: [stable, beta] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Run clippy action + uses: clechasseur/rs-clippy-check@v3 + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + doc: + name: nightly / doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust nightly + uses: dtolnay/rust-toolchain@nightly + + - name: Run cargo doc + run: cargo doc --no-deps --all-features + env: + RUSTDOCFLAGS: --cfg docsrs + + msrv: + name: 1.89.0 / check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install 1.89.0 + uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.89.0 + + - name: cargo +1.89.0 check + run: cargo check \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..11768fc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,548 @@ +name: Create release + +permissions: + pull-requests: write + contents: write + +on: + workflow_dispatch: + inputs: + bump_type: + description: 'Specify the type of version bump' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + +jobs: + bump-version: + name: bump-version + runs-on: ubuntu-latest + steps: + - name: Configure SSH for Git + if: env.ACT != 'true' + run: | + mkdir -p ~/.ssh + echo "${{ secrets.RELEASE_BOT_SSH_KEY }}" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan -H github.com >> ~/.ssh/known_hosts + + - name: Checkout repository + if: env.ACT != 'true' + uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.RELEASE_BOT_SSH_KEY }} + fetch-depth: 0 + + - name: Checkout repository + if: env.ACT == 'true' + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install Commitizen + run: | + python -m pip install --upgrade pip + pip install commitizen + npm install -g conventional-changelog-cli + + - name: Configure Git user + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Bump version with Commitizen + run: | + cz bump --yes --increment ${{ github.event.inputs.bump_type }} + + - name: Amend commit message to include '[skip ci]' + run: | + git commit --amend --no-edit -m "$(git log -1 --pretty=%B) [skip ci]" + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Get the new version tag + id: version + run: | + mkdir -p artifacts + NEW_TAG=$(cz version --project) + echo "New version: $NEW_TAG" + echo "version=$NEW_TAG" >> $GITHUB_ENV + echo "$NEW_TAG" > artifacts/release-version + + - name: Get the previous version tag + id: prev_version + run: | + PREV_TAG=$(git describe --tags --abbrev=0 ${GITHUB_SHA}^) + echo "Previous tag: $PREV_TAG" + echo "prev_version=$PREV_TAG" >> $GITHUB_ENV + + - name: Bump Cargo.toml version + shell: bash + working-directory: ${{ github.workspace }} + env: + VERSION: ${{ env.version }} + run: | + set -euo pipefail + : "${VERSION:?env.version is empty}" + + # Ignore Act's local artifact dir noise + echo artifacts/ >> .git/info/exclude || true + + # Edit the version line right after name="gman" + sed -E -i ' + /^[[:space:]]*name[[:space:]]*=[[:space:]]*"gman"[[:space:]]*$/ { + n + s|^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"|version = "'"$VERSION"'"| + } + ' Cargo.toml + + cargo update || true + + # Git config that helps in Act + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git config --global --add safe.directory "$GITHUB_WORKSPACE" + + git status --porcelain + git diff --name-only -- Cargo.toml Cargo.lock || true + + if ! git diff --quiet -- Cargo.toml Cargo.lock; then + git add -u -- Cargo.toml Cargo.lock + git commit -m "chore: bump Cargo.toml to $VERSION" + else + echo "No changes to commit (already at $VERSION)" + fi + + - name: Generate changelog for the version bump + id: changelog + run: | + changelog=$(conventional-changelog -p angular -i CHANGELOG.md -s --from ${{ env.prev_version }} --to ${{ env.version }}) + echo "$changelog" > artifacts/changelog.md + echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV + + - name: Push changes + if: env.ACT != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + git push origin --follow-tags + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: artifacts + + - name: Upload the changed Cargo files (Act) + if: env.ACT == 'true' + uses: actions/upload-artifact@v4 + with: + name: bumped-cargo-files + path: | + Cargo.toml + Cargo.lock + + build-release-artifacts: + name: build-release + needs: [bump-version] + runs-on: ${{ matrix.job.os }} + env: + RUST_BACKTRACE: 1 + strategy: + fail-fast: true + matrix: + # prettier-ignore + job: + - { name: "macOS-arm64", os: "macOS-latest", target: "aarch64-apple-darwin", artifact_suffix: "macos-arm64", use-cross: true } + - { name: "macOS-amd64", os: "macOS-latest", target: "x86_64-apple-darwin", artifact_suffix: "macos" } + - { name: "windows-amd64", os: "windows-latest", target: "x86_64-pc-windows-msvc", artifact_suffix: "windows" } + - { name: "windows-aarch64", os: "windows-latest", target: "aarch64-pc-windows-msvc", artifact_suffix: "windows-aarch64", use-cross: true } + - { name: "linux-gnu", os: "ubuntu-latest", target: "x86_64-unknown-linux-gnu", artifact_suffix: "linux" } + - { name: "linux-musl", os: "ubuntu-latest", target: "x86_64-unknown-linux-musl", artifact_suffix: "linux-musl", use-cross: true, } + - { name: "linux-aarch64-gnu", os: "ubuntu-latest", target: "aarch64-unknown-linux-gnu", artifact_suffix: "aarch64-gnu", use-cross: true, test-bin: "--bin gman" } + - { name: "linux-aarch64-musl", os: "ubuntu-latest", target: "aarch64-unknown-linux-musl", artifact_suffix: "aarch64-musl", use-cross: true, test-bin: "--bin gman" } + - { name: "linux-arm-gnu", os: "ubuntu-latest", target: "arm-unknown-linux-gnueabi", artifact_suffix: "armv6-gnu", use-cross: true, test-bin: "--bin gman" } + - { name: "linux-arm-musl", os: "ubuntu-latest", target: "arm-unknown-linux-musleabihf", artifact_suffix: "armv6-musl", use-cross: true, test-bin: "--bin gman" } + - { name: "linux-armv7-gnu", os: "ubuntu-latest", target: "armv7-unknown-linux-gnueabihf", artifact_suffix: "armv7-gnu", use-cross: true, test-bin: "--bin gman" } + - { name: "linux-armv7-musl", os: "ubuntu-latest", target: "armv7-unknown-linux-musleabihf", artifact_suffix: "armv7-musl", use-cross: true, test-bin: "--bin gman" } + rust: [stable] + + steps: + - name: Check if actor is repository owner + if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }} + run: | + echo "You are not authorized to run this workflow." + exit 1 + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Ensure repository is up-to-date + if: env.ACT != 'true' + run: | + git fetch --all + git pull + + - name: Get bumped Cargo files (Act) + if: env.ACT == 'true' + uses: actions/download-artifact@v4 + with: + name: bumped-cargo-files + path: ${{ github.workspace }} + + - uses: actions/cache@v3 + name: Cache Cargo registry + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} + + - uses: actions/cache@v3 + if: startsWith(matrix.job.name, 'linux-') + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }} + + - uses: dtolnay/rust-toolchain@stable + name: Set Rust toolchain + with: + targets: ${{ matrix.job.target }} + + - uses: taiki-e/setup-cross-toolchain-action@v1 + with: + # NB: sets CARGO_BUILD_TARGET evar - do not need --target flag in build + target: ${{ matrix.job.target }} + + - uses: taiki-e/install-action@cross + if: ${{ matrix.job.use-cross }} + + - name: Installing needed Ubuntu dependencies + if: matrix.job.os == 'ubuntu-latest' + shell: bash + run: | + sudo apt-get -y update + case ${{ matrix.job.target }} in + arm*-linux-*) sudo apt-get -y install gcc-arm-linux-gnueabihf ;; + aarch64-*-linux-*) sudo apt-get -y install gcc-aarch64-linux-gnu ;; + esac + + - name: Build + run: cargo build --release --verbose --target=${{ matrix.job.target }} --locked + + - name: Verify file + shell: bash + run: | + file target/${{ matrix.job.target }}/release/gman + + - name: Test + if: matrix.job.target != 'aarch64-apple-darwin' && matrix.job.target != 'aarch64-pc-windows-msvc' + run: cargo test --release --verbose --target=${{ matrix.job.target }} ${{ matrix.job.test-bin }} + + - name: Packaging final binary (Windows) + if: matrix.job.os == 'windows-latest' + shell: bash + run: | + cd target/${{ matrix.job.target }}/release + BINARY_NAME=gman.exe + if [ "${{ matrix.job.target }}" != "aarch64-pc-windows-msvc" ]; then + # strip the binary + strip $BINARY_NAME + fi + RELEASE_NAME=gman-${{ matrix.job.artifact_suffix }} + mkdir -p artifacts + tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME + # create sha checksum files + certutil -hashfile $RELEASE_NAME.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > $RELEASE_NAME.sha256 + echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV + + - name: Packaging final binary (macOS and Linux) + if: matrix.job.os != 'windows-latest' + shell: bash + run: | + # set the right strip executable + STRIP="strip"; + case ${{ matrix.job.target }} in + arm*-linux-*) STRIP="arm-linux-gnueabihf-strip" ;; + aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;; + esac; + cd target/${{ matrix.job.target }}/release + BINARY_NAME=gman + # strip the binary + "$STRIP" "$BINARY_NAME" + RELEASE_NAME=gman-${{ matrix.job.artifact_suffix }} + tar czvf $RELEASE_NAME.tar.gz $BINARY_NAME + # create sha checksum files + shasum -a 256 $RELEASE_NAME.tar.gz > $RELEASE_NAME.sha256 + echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV + + - name: Add artifacts + run: | + mkdir -p artifacts + cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.tar.gz artifacts/ + cp target/${{ matrix.job.target }}/release/${{ env.RELEASE_NAME }}.sha256 artifacts/ + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ env.RELEASE_NAME }} + path: artifacts + overwrite: true + + publish-github-release: + name: publish-github-release + needs: [build-release-artifacts] + runs-on: ubuntu-latest + steps: + - name: Check if actor is repository owner + if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }} + run: | + echo "You are not authorized to run this workflow." + exit 1 + + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Ensure repository is up-to-date + if: env.ACT != 'true' + run: | + git fetch --all + git pull + + - name: Set environment variables + run: | + release_version="$(cat ./artifacts/release-version)" + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + changelog_body="$(cat ./artifacts/changelog.md)" + echo "changelog_body=$(cat artifacts/changelog.md)" >> $GITHUB_ENV + + - name: Validate release environment variables + run: | + echo "Release version: ${{ env.RELEASE_VERSION }}" + echo "Changelog body: ${{ env.changelog_body }}" + + - name: Create a GitHub Release + if: env.ACT != 'true' + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + artifacts/gman-macos-arm64.tar.gz + artifacts/gman-macos-arm64.sha256 + artifacts/gman-macos.tar.gz + artifacts/gman-macos.sha256 + artifacts/gman-windows.tar.gz + artifacts/gman-windows.sha256 + artifacts/gman-windows-aarch64.tar.gz + artifacts/gman-windows-aarch64.sha256 + artifacts/gman-linux.tar.gz + artifacts/gman-linux.sha256 + artifacts/gman-linux-musl.tar.gz + artifacts/gman-linux-musl.sha256 + artifacts/gman-aarch64-gnu.tar.gz + artifacts/gman-aarch64-gnu.sha256 + artifacts/gman-aarch64-musl.tar.gz + artifacts/gman-aarch64-musl.sha256 + artifacts/gman-armv6-gnu.tar.gz + artifacts/gman-armv6-gnu.sha256 + artifacts/gman-armv6-musl.tar.gz + artifacts/gman-armv6-musl.sha256 + artifacts/gman-armv7-gnu.tar.gz + artifacts/gman-armv7-gnu.sha256 + artifacts/gman-armv7-musl.tar.gz + artifacts/gman-armv7-musl.sha256 + tag_name: v${{ env.RELEASE_VERSION }} + name: 'v${{ env.RELEASE_VERSION }}' + body: ${{ env.changelog_body }} + draft: false + prerelease: false + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + path: artifacts + overwrite: true + + publish-chocolatey-package: + needs: [publish-github-release] + name: Publish Chocolatey Package + runs-on: windows-latest + steps: + - name: Check if actor is repository owner + if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }} + run: | + echo "You are not authorized to run this workflow." + exit 1 + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Get release artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Set release assets and version + shell: pwsh + run: | + # Read the first column from the SHA256 file + $windows_sha = Get-Content ./artifacts/gman-windows.sha256 | ForEach-Object { $_.Split(' ')[0] } + Add-Content -Path $env:GITHUB_ENV -Value "WINDOWS_SHA=$windows_sha" + + # Read the release version from the release-version file + $release_version = Get-Content ./artifacts/release-version + Add-Content -Path $env:GITHUB_ENV -Value "RELEASE_VERSION=$release_version" + + - name: Validate release environment variables + run: | + echo "Release SHA windows: ${{ env.WINDOWS_SHA }}" + echo "Release version: ${{ env.RELEASE_VERSION }}" + + - name: Package and Publish package to Chocolatey + if: env.ACT != 'true' + run: | + mkdir ./deployment/chocolatey/tools + # Run packaging script + python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/gman.nuspec.template" "./deployment/chocolatey/gman.nuspec" ${{ env.WINDOWS_SHA }} + python "./deployment/chocolatey/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/chocolatey/chocolateyinstall.ps1.template" "./deployment/chocolatey/tools/chocolateyinstall.ps1" ${{ env.WINDOWS_SHA }} + + # Publish to Chocolatey + cd ./deployment/chocolatey + choco pack + echo y | choco install gman -dv -s . + $version = gman --version + $version = $version -replace " ", "." + choco push $version.nupkg -s https://push.chocolatey.org/ --api-key ${{ secrets.CHOCOLATEY_API_KEY }}; + + publish-homebrew-formula: + needs: [publish-github-release] + name: Update Homebrew formulas + runs-on: ubuntu-latest + steps: + - name: Check if actor is repository owner + if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }} + run: | + echo "You are not authorized to run this workflow." + exit 1 + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Get release artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Set release assets and version + shell: bash + run: | + # Set environment variables + macos_sha="$(cat ./artifacts/gman-macos.sha256 | awk '{print $1}')" + echo "MACOS_SHA=$macos_sha" >> $GITHUB_ENV + macos_sha_arm="$(cat ./artifacts/gman-macos-arm64.sha256 | awk '{print $1}')" + echo "MACOS_SHA_ARM=$macos_sha_arm" >> $GITHUB_ENV + linux_sha="$(cat ./artifacts/gman-linux-musl.sha256 | awk '{print $1}')" + echo "LINUX_SHA=$linux_sha" >> $GITHUB_ENV + release_version="$(cat ./artifacts/release-version)" + echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV + + - name: Validate release environment variables + run: | + echo "Release SHA macos: ${{ env.MACOS_SHA }}" + echo "Release SHA macos-arm: ${{ env.MACOS_SHA_ARM }}" + echo "Release SHA linux musl: ${{ env.LINUX_SHA }}" + echo "Release version: ${{ env.RELEASE_VERSION }}" + + - name: Execute Homebrew packaging script + if: env.ACT != 'true' + run: | + # run packaging script + python "./deployment/homebrew/packager.py" ${{ env.RELEASE_VERSION }} "./deployment/homebrew/gman.rb.template" "./gman.rb" ${{ env.MACOS_SHA }} ${{ env.MACOS_SHA_ARM }} ${{ env.LINUX_SHA }} + + - name: Push changes to Homebrew tap + if: env.ACT != 'true' + env: + TOKEN: ${{ secrets.GMAN_GITHUB_TOKEN }} + run: | + # push to Git + git config --global user.name "Dark-Alex-17" + git config --global user.email "alex.j.tusa@gmail.com" + git clone https://Dark-Alex-17:${{ secrets.GMAN_GITHUB_TOKEN }}@github.com/Dark-Alex-17/homebrew-gman.git + rm homebrew-gman/Formula/gman.rb + cp gman.rb homebrew-gman/Formula + cd homebrew-gman + git add . + git diff-index --quiet HEAD || git commit -am "Update formula for G-Man release ${{ env.RELEASE_VERSION }}" + git push https://$TOKEN@github.com/Dark-Alex-17/homebrew-gman.git + + publish-crate: + needs: publish-github-release + name: Publish Crate + runs-on: ubuntu-latest + steps: + - name: Check if actor is repository owner + if: ${{ github.actor != github.repository_owner && env.ACT != 'true' }} + run: | + echo "You are not authorized to run this workflow." + exit 1 + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get bumped Cargo files (Act) + if: env.ACT == 'true' + uses: actions/download-artifact@v4 + with: + name: bumped-cargo-files + path: ${{ github.workspace }} + + - name: Ensure repository is up-to-date + if: env.ACT != 'true' + run: | + git fetch --all + git pull + + - uses: actions/cache@v3 + name: Cache Cargo registry + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} + + - uses: actions/cache@v3 + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-cargo-bin-${{ hashFiles('.github/workflows/release.yml') }} + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - uses: katyo/publish-crates@v2 + if: env.ACT != 'true' + with: + registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b362489 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,100 @@ +on: + push: + branches: + - main + pull_request: + +name: Test Suite + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + required: + runs-on: ubuntu-latest + name: ubuntu / ${{ matrix.toolchain }} + strategy: + fail-fast: false + matrix: + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + + - name: cargo test --locked + run: cargo test --locked --all-features --all-targets + + os-check: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} / stable + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest] + steps: + # if your project needs OpenSSL, uncomment this to fix Windows builds. + # it's commented out by default as the install command takes 5-10m. + # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append + # if: runner.os == 'Windows' + # - run: vcpkg install openssl:x64-windows-static-md + # if: runner.os == 'Windows' + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + + - name: cargo test + run: cargo test --locked --all-features --all-targets + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + coverage: + runs-on: ubuntu-latest + name: ubuntu / stable / coverage + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + + - name: cargo install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: cargo generate-lockfile + if: hashFiles('Cargo.lock') == '' + run: cargo generate-lockfile + + - name: cargo llvm-cov + run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info + + - name: Record Rust version + run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV" + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + - name: Upload to codecov.io + if: env.ACT != 'true' + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,RUST diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a66fe3a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: +- hooks: + - id: commitizen + - id: commitizen-branch + stages: + - pre-push + repo: https://github.com/commitizen-tools/commitizen + rev: v3.30.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a29f7e7 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +d4udts@gmail.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bdc9843 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,80 @@ +# Contributing +Contributors are very welcome! **No contribution is too small and all contributions are valued.** + +## Rust +You'll need to have the stable Rust toolchain installed in order to develop GMan. + +The Rust toolchain (stable) can be installed via rustup using the following command: + +```shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +This will install `rustup`, `rustc` and `cargo`. For more information, refer to the [official Rust installation documentation](https://www.rust-lang.org/tools/install). + +## Commitizen +[Commitizen](https://github.com/commitizen-tools/commitizen?tab=readme-ov-file) is a nifty tool that helps us write better commit messages. It ensures that our +commits have a consistent style and makes it easier to generate CHANGELOGS. Additionally, +Commitizen is used to run pre-commit checks to enforce style constraints. + +To install `commitizen` and the `pre-commit` prerequisite, run the following command: + +```shell +python3 -m pip install commitizen pre-commit +``` + +### Commitizen Quick Guide +To see an example commit to get an idea for the Commitizen style, run: + +```shell +cz example +``` + +To see the allowed types of commits and their descriptions, run: + +```shell +cz info +``` + +If you'd like to create a commit using Commitizen with an interactive prompt to help you get +comfortable with the style, use: + +```shell +cz commit +``` + +## Setup workspace + +1. Clone this repo +2. Run `cargo test` to set up hooks +3. Make changes +4. Run the application using `make run` or `cargo run` +5. Commit changes. This will trigger pre-commit hooks that will run format, test and lint. If there are errors or + warnings from Clippy, please fix them. +6. Push your code to a new branch named after the feature/bug/etc. you're adding. This will trigger pre-push hooks that + will run lint and test. +7. Create a PR + +### CI/CD Testing with Act +If you also are planning on testing out your changes before pushing them with [Act](https://github.com/nektos/act), you will need to set up `act`, +`docker`, and configure your local system to run different architectures: + +1. Install `docker` by following the instructions on the [official Docker installation page](https://docs.docker.com/get-docker/). +2. Install `act` by following the instructions on the [official Act installation page](https://nektosact.com/installation/index.html). +3. Install `binfmt` on your system once so that `act` can run the correct architecture for the CI/CD workflows. + You can do this by running: + ```shell + sudo docker run --rm --privileged tonistiigi/binfmt --install all + ``` + +Then, you can run workflows locally without having to commit and see if the GitHub action passes or fails. + +**For example**: To test the [release.yml](.github/workflows/release.yml) workflow locally, you can run: + +```shell +act -W .github/workflows/release.yml --input_type bump=minor +``` + +## Questions? Reach out to me! +If you encounter any questions while developing G-Man, please don't hesitate to reach out to me at +alex.j.tusa@gmail.com. I'm happy to help contributors in any way I can, regardless of if they're new or experienced! diff --git a/Cargo.lock b/Cargo.lock index 09acb76..c2c4c09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -224,6 +224,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "cargo-husky" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" + [[package]] name = "cc" version = "1.2.36" @@ -708,6 +714,7 @@ dependencies = [ "assert_cmd", "backtrace", "base64", + "cargo-husky", "chacha20poly1305", "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 399ac2b..81b9620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,8 @@ tempfile = "3.10.1" proptest = "1.5.0" assert_cmd = "2.0.16" predicates = "3.1.2" +cargo-husky = { version = "1.5.0", default-features = false, features = ["user-hooks"] } + [[bin]] bench = false diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ecd0d1a --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +#!make +default: run + +.PHONY: test test-cov build run lint lint-fix fmt minimal-versions analyze release delete-tag + +test: + @cargo test --all + +## Run all tests with coverage - `cargo install cargo-tarpaulin` +test-cov: + @cargo tarpaulin + +build: test + @cargo build --release + +run: + @CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run + +lint: + @find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace + +lint-fix: + @cargo fix + +fmt: + @cargo fmt + +minimal-versions: + @cargo +nightly update -Zdirect-minimal-versions + +## Analyze for unsafe usage - `cargo install cargo-geiger` +analyze: + @cargo geiger + +release: + @git tag -a ${V} -m "Release ${V}" && git push origin ${V} + +delete-tag: + @git tag -d ${V} && git push --delete origin ${V} + diff --git a/README.md b/README.md index f4f9c60..e3b7a95 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ -# gman - Universal Credential Manager +# G-Man - Universal Credential Manager + +![Check](https://github.com/Dark-Alex-17/gman/actions/workflows/check.yml/badge.svg) +![Test](https://github.com/Dark-Alex-17/gman/actions/workflows/test.yml/badge.svg) +![LOC](https://tokei.rs/b1/github/Dark-Alex-17/gman?category=code) +[![crates.io link](https://img.shields.io/crates/v/gman.svg)](https://crates.io/crates/gman) +![Release](https://img.shields.io/github/v/release/Dark-Alex-17/gman?color=%23c694ff) +![Crate.io downloads](https://img.shields.io/crates/d/gman?label=Crate%20downloads) +[![GitHub Downloads](https://img.shields.io/github/downloads/Dark-Alex-17/gman/total.svg?label=GitHub%20downloads)](https://github.com/Dark-Alex-17/gman/releases) `gman` is a command-line tool for managing and injecting secrets for your scripts, automations, and applications. It provides a single, secure interface to store, retrieve, and inject secrets so you can stop hand-rolling config diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..d294aa6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +## Supported Versions + +Only latest version of the software will be supported with security patches. + +| Version | Supported | +| -------- | ------------------ | +| latest | :white_check_mark: | + +## Reporting a Vulnerability + +If you find a vulnerability, please reach out to me via email (alex.j.tusa@gmail.com). +If you yourself decide you'd like to tackle a fix, please submit a PR with the fix and I'll review it as soon as +possible. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..52661b6 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + target: auto + threshold: 0.5% + base: auto + patch: + default: + target: 80% + threshold: 0% +ignore: + - "target/**" + - "tests/**" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..85acf93 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "gman", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} diff --git a/src/bin/gman/cli.rs b/src/bin/gman/cli.rs index 0481424..0d48c5c 100644 --- a/src/bin/gman/cli.rs +++ b/src/bin/gman/cli.rs @@ -1,5 +1,5 @@ use crate::command::preview_command; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use gman::config::{Config, ProviderConfig, RunConfig}; use gman::providers::SecretProvider; use heck::ToSnakeCase; diff --git a/src/bin/gman/main.rs b/src/bin/gman/main.rs index 1f02682..5ad4cae 100644 --- a/src/bin/gman/main.rs +++ b/src/bin/gman/main.rs @@ -1,12 +1,12 @@ use clap::{ - crate_authors, crate_description, crate_name, crate_version, CommandFactory, Parser, ValueEnum, + CommandFactory, Parser, ValueEnum, crate_authors, crate_description, crate_name, crate_version, }; use std::ffi::OsString; use anyhow::{Context, Result}; use clap::Subcommand; use crossterm::execute; -use crossterm::terminal::{disable_raw_mode, LeaveAlternateScreen}; +use crossterm::terminal::{LeaveAlternateScreen, disable_raw_mode}; use gman::config::load_config; use heck::ToSnakeCase; use std::io::{self, IsTerminal, Read, Write}; diff --git a/src/config.rs b/src/config.rs index 8edf3d2..45fde61 100644 --- a/src/config.rs +++ b/src/config.rs @@ -25,7 +25,7 @@ use anyhow::Result; use log::debug; use serde::{Deserialize, Serialize}; use serde_with::serde_as; -use serde_with::{skip_serializing_none, DisplayFromStr}; +use serde_with::{DisplayFromStr, skip_serializing_none}; use std::borrow::Cow; use std::path::PathBuf; use validator::{Validate, ValidationError}; @@ -188,19 +188,23 @@ pub struct Config { } fn default_provider_exists(config: &Config) -> Result<(), ValidationError> { - if let Some(default) = &config.default_provider { - if config.providers.iter().any(|p| p.name.as_deref() == Some(default)) { - Ok(()) - } else { - let mut err = ValidationError::new("default_provider_missing"); - err.message = Some(Cow::Borrowed( - "The default_provider does not match any configured provider names", - )); - Err(err) - } - } else { - Ok(()) - } + if let Some(default) = &config.default_provider { + if config + .providers + .iter() + .any(|p| p.name.as_deref() == Some(default)) + { + Ok(()) + } else { + let mut err = ValidationError::new("default_provider_missing"); + err.message = Some(Cow::Borrowed( + "The default_provider does not match any configured provider names", + )); + Err(err) + } + } else { + Ok(()) + } } impl Default for Config { diff --git a/src/lib.rs b/src/lib.rs index eb968f7..631ffeb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,15 +20,15 @@ //! The `config` and `providers` modules power the CLI. They can be embedded //! in other programs, but many functions interact with the user or the //! filesystem. Prefer `no_run` doctests for those. -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{Context, Result, anyhow, bail}; use argon2::{ - password_hash::{rand_core::RngCore, SaltString}, Algorithm, Argon2, Params, Version, + password_hash::{SaltString, rand_core::RngCore}, }; -use base64::{engine::general_purpose::STANDARD as B64, Engine as _}; +use base64::{Engine as _, engine::general_purpose::STANDARD as B64}; use chacha20poly1305::{ - aead::{Aead, KeyInit, OsRng}, Key, XChaCha20Poly1305, XNonce, + aead::{Aead, KeyInit, OsRng}, }; use secrecy::{ExposeSecret, SecretString}; use zeroize::Zeroize; diff --git a/src/providers/git_sync.rs b/src/providers/git_sync.rs index 6a6f697..8cba7e5 100644 --- a/src/providers/git_sync.rs +++ b/src/providers/git_sync.rs @@ -4,9 +4,9 @@ use dialoguer::Confirm; use dialoguer::theme::ColorfulTheme; use indoc::formatdoc; use log::debug; -use std::{env, fs}; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; +use std::{env, fs}; use validator::Validate; #[derive(Debug, Validate, Clone)] @@ -41,11 +41,17 @@ pub fn sync_and_push(opts: &SyncOpts<'_>) -> Result<()> { .with_context(|| "get default vault path")?; let repo_vault = repo_dir.join("vault.yml"); if default_vault.exists() && !repo_vault.exists() { - fs::rename(&default_vault, &repo_vault) - .with_context(|| format!("move {} -> {}", default_vault.display(), repo_vault.display()))?; + fs::rename(&default_vault, &repo_vault).with_context(|| { + format!( + "move {} -> {}", + default_vault.display(), + repo_vault.display() + ) + })?; } else if !repo_vault.exists() { // Ensure an empty vault exists to allow initial commits - fs::write(&repo_vault, "{}\n").with_context(|| format!("create {}", repo_vault.display()))?; + fs::write(&repo_vault, "{}\n") + .with_context(|| format!("create {}", repo_vault.display()))?; } let git = resolve_git(opts.git_executable.as_ref())?; @@ -250,7 +256,7 @@ fn stage_vault_only(git: &Path, repo: &Path) -> Result<()> { fn fetch_and_pull(git: &Path, repo: &Path, branch: &str) -> Result<()> { // Fetch all refs from origin (safe even if branch doesn't exist remotely) - run_git(git, repo, &["fetch", "origin", "--prune"]) + run_git(git, repo, &["fetch", "origin", "--prune"]) .with_context(|| "Failed to fetch changes from remote")?; let origin_ref = format!("origin/{branch}"); @@ -265,19 +271,16 @@ fn fetch_and_pull(git: &Path, repo: &Path, branch: &str) -> Result<()> { .with_context(|| "Failed to checkout remote branch over local state")?; run_git(git, repo, &["reset", "--hard", &origin_ref]) .with_context(|| "Failed to hard reset to remote branch")?; - run_git(git, repo, &["clean", "-fd"]).with_context(|| "Failed to clean untracked files")?; + run_git(git, repo, &["clean", "-fd"]) + .with_context(|| "Failed to clean untracked files")?; } return Ok(()); } // If we have local history and the remote branch exists, fast-forward. if remote_has_branch { - run_git( - git, - repo, - &["merge", "--ff-only", &origin_ref], - ) - .with_context(|| "Failed to merge remote changes")?; + run_git(git, repo, &["merge", "--ff-only", &origin_ref]) + .with_context(|| "Failed to merge remote changes")?; } Ok(()) } @@ -286,7 +289,12 @@ fn has_remote_branch(git: &Path, repo: &Path, branch: &str) -> bool { Command::new(git) .arg("-C") .arg(repo) - .args(["show-ref", "--verify", "--quiet", &format!("refs/remotes/origin/{}", branch)]) + .args([ + "show-ref", + "--verify", + "--quiet", + &format!("refs/remotes/origin/{}", branch), + ]) .stdout(Stdio::null()) .stderr(Stdio::null()) .status() @@ -399,7 +407,10 @@ mod tests { #[test] fn test_repo_name_from_url() { assert_eq!(repo_name_from_url("git@github.com:user/vault.git"), "vault"); - assert_eq!(repo_name_from_url("https://github.com/user/test-vault.git"), "test-vault"); + assert_eq!( + repo_name_from_url("https://github.com/user/test-vault.git"), + "test-vault" + ); assert_eq!(repo_name_from_url("ssh://git@example.com/x/y/z.git"), "z"); assert_eq!(repo_name_from_url("git@example.com:ns/repo"), "repo"); } diff --git a/src/providers/local.rs b/src/providers/local.rs index 762e774..19b60ac 100644 --- a/src/providers/local.rs +++ b/src/providers/local.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail, Context}; +use anyhow::{Context, anyhow, bail}; use secrecy::{ExposeSecret, SecretString}; use std::collections::HashMap; use std::fs; @@ -6,20 +6,20 @@ use std::path::{Path, PathBuf}; use zeroize::Zeroize; use crate::config::ProviderConfig; -use crate::providers::git_sync::{repo_name_from_url, sync_and_push, SyncOpts}; use crate::providers::SecretProvider; +use crate::providers::git_sync::{SyncOpts, repo_name_from_url, sync_and_push}; use crate::{ ARGON_M_COST_KIB, ARGON_P, ARGON_T_COST, HEADER, KDF, KEY_LEN, NONCE_LEN, SALT_LEN, VERSION, }; use anyhow::Result; use argon2::{Algorithm, Argon2, Params, Version}; -use base64::{engine::general_purpose::STANDARD as B64, Engine as _}; +use base64::{Engine as _, engine::general_purpose::STANDARD as B64}; use chacha20poly1305::aead::rand_core::RngCore; use chacha20poly1305::{ - aead::{Aead, KeyInit, OsRng}, Key, XChaCha20Poly1305, XNonce, + aead::{Aead, KeyInit, OsRng}, }; -use dialoguer::{theme, Input}; +use dialoguer::{Input, theme}; use log::{debug, error}; use serde::Deserialize; use theme::ColorfulTheme; diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 0234f5c..d287135 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -16,7 +16,7 @@ pub mod local; use crate::config::ProviderConfig; use crate::providers::local::LocalProvider; -use anyhow::{anyhow, Result}; +use anyhow::{Result, anyhow}; use serde::Deserialize; use std::fmt::{Display, Formatter}; use std::str::FromStr; diff --git a/tests/bin/mod.rs b/tests/bin/mod.rs index c950dfc..66accc3 100644 --- a/tests/bin/mod.rs +++ b/tests/bin/mod.rs @@ -1 +1 @@ -mod cli_tests; \ No newline at end of file +mod cli_tests; diff --git a/tests/tests.rs b/tests/tests.rs index b485920..e441cb9 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,4 +1,4 @@ -mod config_tests; -mod providers; mod bin; -mod prop_crypto; \ No newline at end of file +mod config_tests; +mod prop_crypto; +mod providers;