diff --git a/.github/workflows/build-release-artifacts.yml b/.github/workflows/build-release-artifacts.yml index 0069093..b85056c 100644 --- a/.github/workflows/build-release-artifacts.yml +++ b/.github/workflows/build-release-artifacts.yml @@ -142,14 +142,14 @@ jobs: - name: Build release binary shell: pwsh run: | - cargo build -p gitcomet-app --release --locked --features ui-gpui,gix --bins + cargo build -p gitcomet --release --locked --features ui-gpui,gix --bins - name: Package portable ZIP shell: pwsh run: | $ErrorActionPreference = "Stop" New-Item -ItemType Directory -Path dist\portable -Force | Out-Null - Copy-Item target\release\gitcomet-app.exe dist\portable\gitcomet-app.exe -Force + Copy-Item target\release\gitcomet.exe dist\portable\gitcomet.exe -Force Copy-Item README.md dist\portable\README.md -Force Copy-Item LICENSE-AGPL-3.0 dist\portable\LICENSE-AGPL-3.0 -Force Copy-Item NOTICE dist\portable\NOTICE -Force @@ -164,11 +164,11 @@ jobs: $ErrorActionPreference = "Stop" choco install wixtoolset --version "${env:WIXTOOLSET_VERSION}" --yes --no-progress cargo install cargo-wix --version "${env:CARGO_WIX_VERSION}" --locked - if (!(Test-Path "crates\gitcomet-app\wix\main.wxs")) { - cargo wix init --package gitcomet-app + if (!(Test-Path "crates\gitcomet\wix\main.wxs")) { + cargo wix init --package gitcomet } $msiName = "gitcomet-v${env:VERSION}-windows-x86_64.msi" - cargo wix --package gitcomet-app --profile release --nocapture --no-build --output "dist\$msiName" + cargo wix --package gitcomet --profile release --nocapture --no-build --output "dist\$msiName" - name: Upload Windows artifacts uses: actions/upload-artifact@v7 @@ -199,7 +199,6 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - appstream \ desktop-file-utils \ pkg-config \ libxcb1-dev \ @@ -213,22 +212,22 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build release binary - run: cargo build -p gitcomet-app --release --locked --features ui-gpui,gix + run: cargo build -p gitcomet --release --locked --features ui-gpui,gix - name: Normalize desktop entry metadata run: | set -euo pipefail mkdir -p dist - cp assets/linux/dev.gitcomet.GitComet.desktop dist/dev.gitcomet.GitComet.desktop - desktop-file-validate dist/dev.gitcomet.GitComet.desktop - appstreamcli validate --no-net flatpak/dev.gitcomet.GitComet.metainfo.xml + # Backward compatibility for older tags that used non-standard categories. + sed 's/VersionControl;/RevisionControl;/g' assets/linux/gitcomet.desktop > dist/gitcomet.desktop + desktop-file-validate dist/gitcomet.desktop - name: Package tar.gz run: | set -euo pipefail root="gitcomet-v${VERSION}-linux-x86_64" mkdir -p "dist/stage/${root}" - install -m755 target/release/gitcomet-app "dist/stage/${root}/gitcomet-app" + install -m755 target/release/gitcomet "dist/stage/${root}/gitcomet" install -m644 README.md "dist/stage/${root}/README.md" install -m644 LICENSE-AGPL-3.0 "dist/stage/${root}/LICENSE-AGPL-3.0" install -m644 NOTICE "dist/stage/${root}/NOTICE" @@ -246,9 +245,9 @@ jobs: mkdir -p "${pkg_root}/usr/share/applications" mkdir -p "${pkg_root}/usr/share/icons/hicolor/512x512/apps" - install -m755 target/release/gitcomet-app "${pkg_root}/usr/bin/gitcomet-app" - install -m644 dist/dev.gitcomet.GitComet.desktop "${pkg_root}/usr/share/applications/dev.gitcomet.GitComet.desktop" - install -m644 assets/gitcomet-512.png "${pkg_root}/usr/share/icons/hicolor/512x512/apps/dev.gitcomet.GitComet.png" + install -m755 target/release/gitcomet "${pkg_root}/usr/bin/gitcomet" + install -m644 dist/gitcomet.desktop "${pkg_root}/usr/share/applications/gitcomet.desktop" + install -m644 assets/gitcomet-512.png "${pkg_root}/usr/share/icons/hicolor/512x512/apps/gitcomet.png" { echo "Package: gitcomet" @@ -268,15 +267,15 @@ jobs: set -euo pipefail appdir="dist/AppDir" mkdir -p "${appdir}/usr/bin" - install -m755 target/release/gitcomet-app "${appdir}/usr/bin/gitcomet-app" - install -m644 dist/dev.gitcomet.GitComet.desktop "${appdir}/dev.gitcomet.GitComet.desktop" - install -m644 assets/gitcomet-512.png "${appdir}/dev.gitcomet.GitComet.png" + install -m755 target/release/gitcomet "${appdir}/usr/bin/gitcomet" + install -m644 dist/gitcomet.desktop "${appdir}/gitcomet.desktop" + install -m644 assets/gitcomet-512.png "${appdir}/gitcomet.png" { echo '#!/usr/bin/env bash' echo 'set -euo pipefail' echo 'HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' - echo 'exec "${HERE}/usr/bin/gitcomet-app" "$@"' + echo 'exec "${HERE}/usr/bin/gitcomet" "$@"' } > "${appdir}/AppRun" chmod +x "${appdir}/AppRun" @@ -299,45 +298,6 @@ jobs: if-no-files-found: error retention-days: 7 - build_flatpak: - name: Build Flatpak artifact - runs-on: ubuntu-latest - timeout-minutes: 120 - needs: prepare - container: - image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08 - options: --privileged - env: - TAG: ${{ needs.prepare.outputs.tag }} - VERSION: ${{ needs.prepare.outputs.version }} - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ needs.prepare.outputs.tag }} - fetch-depth: 0 - - - name: Build Flatpak bundle - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - bundle: dev.gitcomet.GitComet.flatpak - manifest-path: flatpak/dev.gitcomet.GitComet.local.yaml - cache-key: flatpak-builder-${{ needs.prepare.outputs.tag }} - upload-artifact: false - - - name: Stage Flatpak artifact - run: | - set -euo pipefail - mkdir -p dist - mv dev.gitcomet.GitComet.flatpak "dist/gitcomet-v${VERSION}-linux-x86_64.flatpak" - - - name: Upload Flatpak artifact - uses: actions/upload-artifact@v4 - with: - name: flatpak-release-artifacts - path: dist/*.flatpak - if-no-files-found: error - retention-days: 7 - build_macos: name: Build macOS artifacts (${{ matrix.arch }}) runs-on: ${{ matrix.runs_on }} @@ -390,7 +350,7 @@ jobs: name: Attach assets and checksums to GitHub release runs-on: ubuntu-latest timeout-minutes: 30 - needs: [prepare, build_windows, build_linux, build_flatpak, build_macos] + needs: [prepare, build_windows, build_linux, build_macos] env: TAG: ${{ needs.prepare.outputs.tag }} VERSION: ${{ needs.prepare.outputs.version }} @@ -399,7 +359,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - ref: ${{ env.TAG }} + ref: ${{ needs.prepare.outputs.tag }} fetch-depth: 0 - uses: actions/download-artifact@v8 @@ -412,11 +372,6 @@ jobs: name: linux-release-artifacts path: dist/linux - - uses: actions/download-artifact@v8 - with: - name: flatpak-release-artifacts - path: dist/flatpak - - uses: actions/download-artifact@v8 with: pattern: macos-release-artifacts-* @@ -429,7 +384,6 @@ jobs: mkdir -p dist/release find dist/windows -maxdepth 1 -type f -exec cp -f {} dist/release/ \; find dist/linux -maxdepth 1 -type f -exec cp -f {} dist/release/ \; - find dist/flatpak -maxdepth 1 -type f -exec cp -f {} dist/release/ \; find dist/macos -maxdepth 1 -type f -exec cp -f {} dist/release/ \; file_count="$(find dist/release -maxdepth 1 -type f | wc -l | tr -d '[:space:]')" if [ "${file_count}" = "0" ]; then @@ -438,40 +392,22 @@ jobs: fi ls -lah dist/release - - name: Generate source tarball - run: | - set -euo pipefail - git archive \ - --format=tar.gz \ - --prefix="gitcomet-v${VERSION}-source/" \ - "$TAG" \ - -o "dist/release/gitcomet-v${VERSION}-source.tar.gz" - - - name: Render Flathub manifest assets - run: | - set -euo pipefail - source_tar="dist/release/gitcomet-v${VERSION}-source.tar.gz" - source_sha256="$(sha256sum "$source_tar" | awk '{print $1}')" - source_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/gitcomet-v${VERSION}-source.tar.gz" - scripts/render-flathub-manifest.sh \ - --source-url "$source_url" \ - --source-sha256 "$source_sha256" \ - --output "dist/release/dev.gitcomet.GitComet.yaml" - cp flatpak/cargo-sources.json dist/release/cargo-sources.json - cp flatpak/flathub.json dist/release/flathub.json - - - name: Generate Homebrew formula + - name: Generate Homebrew cask and formula run: | set -euo pipefail arm_tar="gitcomet-v${VERSION}-macos-arm64.tar.gz" intel_tar="gitcomet-v${VERSION}-macos-x86_64.tar.gz" linux_tar="gitcomet-v${VERSION}-linux-x86_64.tar.gz" + arm_dmg="gitcomet-v${VERSION}-macos-arm64.dmg" + intel_dmg="gitcomet-v${VERSION}-macos-x86_64.dmg" arm_path="dist/release/${arm_tar}" intel_path="dist/release/${intel_tar}" linux_path="dist/release/${linux_tar}" + arm_dmg_path="dist/release/${arm_dmg}" + intel_dmg_path="dist/release/${intel_dmg}" - if [ ! -f "${arm_path}" ] || [ ! -f "${intel_path}" ] || [ ! -f "${linux_path}" ]; then - echo "::error title=Missing release tarballs::Expected ${arm_tar}, ${intel_tar}, and ${linux_tar} in dist/release." + if [ ! -f "${arm_path}" ] || [ ! -f "${intel_path}" ] || [ ! -f "${linux_path}" ] || [ ! -f "${arm_dmg_path}" ] || [ ! -f "${intel_dmg_path}" ]; then + echo "::error title=Missing release assets::Expected ${arm_tar}, ${intel_tar}, ${linux_tar}, ${arm_dmg}, and ${intel_dmg} in dist/release." exit 1 fi @@ -481,6 +417,13 @@ jobs: --arm-tar "${arm_path}" \ --intel-tar "${intel_path}" \ --linux-tar "${linux_path}" \ + --output "dist/release/gitcomet-cli.rb" + + scripts/generate-homebrew-cask.sh \ + --version "${VERSION}" \ + --github-repo "${GITHUB_REPOSITORY}" \ + --arm-dmg "${arm_dmg_path}" \ + --intel-dmg "${intel_dmg_path}" \ --output "dist/release/gitcomet.rb" - name: Generate checksums diff --git a/.github/workflows/cross-platform-tests.yml b/.github/workflows/cross-platform-tests.yml index b28771f..c4d8044 100644 --- a/.github/workflows/cross-platform-tests.yml +++ b/.github/workflows/cross-platform-tests.yml @@ -163,8 +163,8 @@ jobs: echo "XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP:-}" - name: Run display-selection integration tests run: | - cargo test -p gitcomet-app $APP_FEATURES --test mergetool_git_integration gui_default --verbose - cargo test -p gitcomet-app $APP_FEATURES --test difftool_git_integration gui_default --verbose + cargo test -p gitcomet $APP_FEATURES --test mergetool_git_integration gui_default --verbose + cargo test -p gitcomet $APP_FEATURES --test difftool_git_integration gui_default --verbose - name: Run UI smoke test under selected profile run: cargo test -p gitcomet-ui-gpui smoke_tests::smoke_view_renders_without_panicking -- --exact @@ -200,9 +200,9 @@ jobs: --verbose - name: Gatekeeper and code-signing check (informational) run: | - cargo build -p gitcomet-app $APP_FEATURES --release --locked - codesign --verify --verbose target/release/gitcomet-app || true - spctl --assess --type execute --verbose target/release/gitcomet-app || true + cargo build -p gitcomet $APP_FEATURES --release --locked + codesign --verify --verbose target/release/gitcomet || true + spctl --assess --type execute --verbose target/release/gitcomet || true windows-tests: name: Windows Tests (PowerShell + CMD) @@ -235,7 +235,7 @@ jobs: --verbose - name: Run standalone smoke test in CMD shell: cmd - run: cargo test -p gitcomet-app --no-default-features --features gix --test standalone_tool_mode_integration help_flag_exits_zero -- --exact + run: cargo test -p gitcomet --no-default-features --features gix --test standalone_tool_mode_integration help_flag_exits_zero -- --exact - name: Show path behavior (CMD + Git Bash) shell: cmd run: | diff --git a/.github/workflows/deploy-apt-repo.yml b/.github/workflows/deploy-apt-repo.yml index 51e3342..21a01d0 100644 --- a/.github/workflows/deploy-apt-repo.yml +++ b/.github/workflows/deploy-apt-repo.yml @@ -21,10 +21,6 @@ on: required: false type: string default: "" - repo_base_url: - required: false - type: string - default: "" distribution: required: false type: string @@ -48,7 +44,7 @@ on: repo_description: required: false type: string - default: "GitComet APT repository" + default: "GitComet APT repository." container_public_access: required: false type: string @@ -75,32 +71,27 @@ on: required: false type: string storage_account: - description: "Azure Storage account name" + description: "Azure Storage account name. Defaults to repository variable APT_STORAGE_ACCOUNT when omitted." required: false default: "" type: string storage_container: - description: "Azure Blob container that holds the APT repository" + description: "Azure Blob container that holds the APT repository. Defaults to APT_STORAGE_CONTAINER when omitted." required: false default: "apt" type: string storage_prefix: - description: "Optional blob prefix within the container" - required: false - default: "" - type: string - repo_base_url: - description: "Public HTTPS base URL for the APT repo root" + description: "Optional blob prefix within the container. Defaults to APT_STORAGE_PREFIX when omitted." required: false default: "" type: string distribution: - description: "APT suite/codename" + description: "APT suite/codename. Defaults to APT_REPO_DISTRIBUTION when omitted." required: false default: "stable" type: string component: - description: "APT component" + description: "APT component. Defaults to APT_REPO_COMPONENT when omitted." required: false default: "main" type: string @@ -110,22 +101,22 @@ on: default: "amd64" type: string repo_origin: - description: "Origin metadata in Release file" + description: "Origin metadata in Release file. Defaults to APT_REPO_ORIGIN when omitted." required: false default: "GitComet" type: string repo_label: - description: "Label metadata in Release file" + description: "Label metadata in Release file. Defaults to APT_REPO_LABEL when omitted." required: false default: "GitComet" type: string repo_description: - description: "Description metadata in Release file" + description: "Description metadata in Release file. Defaults to APT_REPO_DESCRIPTION when omitted." required: false - default: "GitComet APT repository" + default: "GitComet APT repository." type: string container_public_access: - description: "Public access used only when the blob container must be created" + description: "Public access used only when the blob container must be created. Defaults to APT_STORAGE_PUBLIC_ACCESS when omitted." required: false default: "blob" type: choice @@ -165,26 +156,33 @@ jobs: DISPATCH_VERSION: ${{ github.event.inputs.version }} INPUT_STORAGE_ACCOUNT: ${{ inputs.storage_account }} DISPATCH_STORAGE_ACCOUNT: ${{ github.event.inputs.storage_account }} + VAR_STORAGE_ACCOUNT: ${{ vars.APT_STORAGE_ACCOUNT }} INPUT_STORAGE_CONTAINER: ${{ inputs.storage_container }} DISPATCH_STORAGE_CONTAINER: ${{ github.event.inputs.storage_container }} + VAR_STORAGE_CONTAINER: ${{ vars.APT_STORAGE_CONTAINER }} INPUT_STORAGE_PREFIX: ${{ inputs.storage_prefix }} DISPATCH_STORAGE_PREFIX: ${{ github.event.inputs.storage_prefix }} - INPUT_REPO_BASE_URL: ${{ inputs.repo_base_url }} - DISPATCH_REPO_BASE_URL: ${{ github.event.inputs.repo_base_url }} + VAR_STORAGE_PREFIX: ${{ vars.APT_STORAGE_PREFIX }} INPUT_DISTRIBUTION: ${{ inputs.distribution }} DISPATCH_DISTRIBUTION: ${{ github.event.inputs.distribution }} + VAR_DISTRIBUTION: ${{ vars.APT_REPO_DISTRIBUTION }} INPUT_COMPONENT: ${{ inputs.component }} DISPATCH_COMPONENT: ${{ github.event.inputs.component }} + VAR_COMPONENT: ${{ vars.APT_REPO_COMPONENT }} INPUT_ARCHITECTURE: ${{ inputs.architecture }} DISPATCH_ARCHITECTURE: ${{ github.event.inputs.architecture }} INPUT_REPO_ORIGIN: ${{ inputs.repo_origin }} DISPATCH_REPO_ORIGIN: ${{ github.event.inputs.repo_origin }} + VAR_REPO_ORIGIN: ${{ vars.APT_REPO_ORIGIN }} INPUT_REPO_LABEL: ${{ inputs.repo_label }} DISPATCH_REPO_LABEL: ${{ github.event.inputs.repo_label }} + VAR_REPO_LABEL: ${{ vars.APT_REPO_LABEL }} INPUT_REPO_DESCRIPTION: ${{ inputs.repo_description }} DISPATCH_REPO_DESCRIPTION: ${{ github.event.inputs.repo_description }} + VAR_REPO_DESCRIPTION: ${{ vars.APT_REPO_DESCRIPTION }} INPUT_CONTAINER_PUBLIC_ACCESS: ${{ inputs.container_public_access }} DISPATCH_CONTAINER_PUBLIC_ACCESS: ${{ github.event.inputs.container_public_access }} + VAR_CONTAINER_PUBLIC_ACCESS: ${{ vars.APT_STORAGE_PUBLIC_ACCESS }} INPUT_DRY_RUN: ${{ inputs.dry_run }} DISPATCH_DRY_RUN: ${{ github.event.inputs.dry_run }} run: | @@ -192,17 +190,16 @@ jobs: tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" - storage_account="${INPUT_STORAGE_ACCOUNT:-${DISPATCH_STORAGE_ACCOUNT:-}}" - storage_container="${INPUT_STORAGE_CONTAINER:-${DISPATCH_STORAGE_CONTAINER:-apt}}" - storage_prefix="${INPUT_STORAGE_PREFIX:-${DISPATCH_STORAGE_PREFIX:-}}" - repo_base_url="${INPUT_REPO_BASE_URL:-${DISPATCH_REPO_BASE_URL:-}}" - distribution="${INPUT_DISTRIBUTION:-${DISPATCH_DISTRIBUTION:-stable}}" - component="${INPUT_COMPONENT:-${DISPATCH_COMPONENT:-main}}" + storage_account="${INPUT_STORAGE_ACCOUNT:-${DISPATCH_STORAGE_ACCOUNT:-${VAR_STORAGE_ACCOUNT:-}}}" + storage_container="${INPUT_STORAGE_CONTAINER:-${DISPATCH_STORAGE_CONTAINER:-${VAR_STORAGE_CONTAINER:-apt}}}" + storage_prefix="${INPUT_STORAGE_PREFIX:-${DISPATCH_STORAGE_PREFIX:-${VAR_STORAGE_PREFIX:-}}}" + distribution="${INPUT_DISTRIBUTION:-${DISPATCH_DISTRIBUTION:-${VAR_DISTRIBUTION:-stable}}}" + component="${INPUT_COMPONENT:-${DISPATCH_COMPONENT:-${VAR_COMPONENT:-main}}}" architecture="${INPUT_ARCHITECTURE:-${DISPATCH_ARCHITECTURE:-amd64}}" - repo_origin="${INPUT_REPO_ORIGIN:-${DISPATCH_REPO_ORIGIN:-GitComet}}" - repo_label="${INPUT_REPO_LABEL:-${DISPATCH_REPO_LABEL:-GitComet}}" - repo_description="${INPUT_REPO_DESCRIPTION:-${DISPATCH_REPO_DESCRIPTION:-GitComet APT repository}}" - container_public_access="${INPUT_CONTAINER_PUBLIC_ACCESS:-${DISPATCH_CONTAINER_PUBLIC_ACCESS:-blob}}" + repo_origin="${INPUT_REPO_ORIGIN:-${DISPATCH_REPO_ORIGIN:-${VAR_REPO_ORIGIN:-GitComet}}}" + repo_label="${INPUT_REPO_LABEL:-${DISPATCH_REPO_LABEL:-${VAR_REPO_LABEL:-GitComet}}}" + repo_description="${INPUT_REPO_DESCRIPTION:-${DISPATCH_REPO_DESCRIPTION:-${VAR_REPO_DESCRIPTION:-GitComet APT repository.}}}" + container_public_access="${INPUT_CONTAINER_PUBLIC_ACCESS:-${DISPATCH_CONTAINER_PUBLIC_ACCESS:-${VAR_CONTAINER_PUBLIC_ACCESS:-blob}}}" dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" tag="$(echo "$tag" | tr -d '[:space:]')" @@ -210,7 +207,6 @@ jobs: storage_account="$(echo "$storage_account" | tr -d '[:space:]')" storage_container="$(echo "$storage_container" | tr -d '[:space:]')" storage_prefix="$(echo "$storage_prefix" | sed 's#^/*##;s#/*$##')" - repo_base_url="$(echo "$repo_base_url" | sed 's#[[:space:]]##g;s#/*$##')" distribution="$(echo "$distribution" | tr -d '[:space:]')" component="$(echo "$component" | tr -d '[:space:]')" architecture="$(echo "$architecture" | tr -d '[:space:]')" @@ -274,7 +270,8 @@ jobs: exit 1 fi - if [ -z "$repo_base_url" ] && [ -n "$storage_account" ] && [ "$storage_container" != '$web' ]; then + repo_base_url="" + if [ -n "$storage_account" ] && [ "$storage_container" != '$web' ]; then repo_base_url="https://${storage_account}.blob.core.windows.net/${storage_container}" if [ -n "$storage_prefix" ]; then repo_base_url="${repo_base_url}/${storage_prefix}" @@ -365,7 +362,7 @@ jobs: echo "Key-Type: RSA" echo "Key-Length: 3072" echo "Name-Real: GitComet APT Dry Run" - echo "Name-Email: ci@example.invalid" + echo "Name-Email: ci@autoexplore.ai" echo "Expire-Date: 0" echo "%no-protection" echo "%commit" @@ -419,16 +416,16 @@ jobs: blob_prefix="${blob_prefix}/" fi - mapfile -t existing_blobs < <( - az storage blob list \ - --account-name "$STORAGE_ACCOUNT" \ - --container-name "$STORAGE_CONTAINER" \ - --account-key "$AZURE_STORAGE_ACCOUNT_KEY" \ - --prefix "$blob_prefix" \ - --num-results "*" \ - --query "[].name" \ - -o tsv - ) + existing_blobs_file="${RUNNER_TEMP}/apt-existing-blobs.txt" + az storage blob list \ + --account-name "$STORAGE_ACCOUNT" \ + --container-name "$STORAGE_CONTAINER" \ + --account-key "$AZURE_STORAGE_ACCOUNT_KEY" \ + --prefix "$blob_prefix" \ + --num-results "*" \ + --query "[].name" \ + -o tsv > "$existing_blobs_file" + mapfile -t existing_blobs < "$existing_blobs_file" if [ "${#existing_blobs[@]}" -eq 0 ]; then exit 0 @@ -472,6 +469,7 @@ jobs: --label "${{ steps.norm.outputs.repo_label }}" --description "${{ steps.norm.outputs.repo_description }}" --signing-key "${{ steps.gpg.outputs.signing_key }}" + --repo-url "https://apt.gitcomet.dev" ) if [ -n "${{ steps.norm.outputs.repo_base_url }}" ]; then @@ -495,6 +493,9 @@ jobs: run: | set -euo pipefail + metadata_cache_control='no-cache, no-store, max-age=0, must-revalidate' + package_cache_control='public, max-age=31536000, immutable' + mkdir -p "$AZURE_CONFIG_DIR" : > dist/apt-uploaded-blobs.txt @@ -505,15 +506,25 @@ jobs: blob_name="${STORAGE_PREFIX}/${rel_path}" fi - az storage blob upload \ - --account-name "$STORAGE_ACCOUNT" \ - --container-name "$STORAGE_CONTAINER" \ - --account-key "$AZURE_STORAGE_ACCOUNT_KEY" \ - --name "$blob_name" \ - --file "$file_path" \ - --overwrite true \ - --no-progress \ + upload_args=( + az storage blob upload + --account-name "$STORAGE_ACCOUNT" + --container-name "$STORAGE_CONTAINER" + --account-key "$AZURE_STORAGE_ACCOUNT_KEY" + --name "$blob_name" + --file "$file_path" + --overwrite true + --no-progress --output none + ) + + if [[ "$rel_path" == dists/* || "$rel_path" == "gitcomet.sources" || "$rel_path" == "gitcomet.list" || "$rel_path" == "gitcomet-archive-keyring.gpg" || "$rel_path" == "gitcomet-archive-keyring.asc" || "$rel_path" == "README.txt" ]]; then + upload_args+=(--content-cache-control "$metadata_cache_control") + elif [[ "$rel_path" == pool/* ]]; then + upload_args+=(--content-cache-control "$package_cache_control") + fi + + "${upload_args[@]}" printf '%s\n' "$blob_name" >> dist/apt-uploaded-blobs.txt done < <(find dist/apt-repo -type f -print0 | sort -z) @@ -549,28 +560,3 @@ jobs: --delete-snapshots include \ --output none done < "${RUNNER_TEMP}/apt-stale.txt" - - - name: Emit deployment summary - run: | - set -euo pipefail - uploaded="yes" - if [ "${{ steps.norm.outputs.dry_run }}" = "true" ]; then - uploaded="no (dry run)" - fi - - { - echo "### APT repository deployment" - echo "" - echo "- Release: \`${{ steps.norm.outputs.tag }}\`" - echo "- Debian package: \`${{ steps.deb.outputs.name }}\`" - echo "- Distribution: \`${{ steps.norm.outputs.distribution }}\`" - echo "- Component: \`${{ steps.norm.outputs.component }}\`" - echo "- Architecture: \`${{ steps.norm.outputs.architecture }}\`" - echo "- Azure target: \`${{ steps.norm.outputs.storage_account }}/${{ steps.norm.outputs.storage_container }}\`" - echo "- Prefix: \`${{ steps.norm.outputs.storage_prefix }}\`" - echo "- Public repo URL: \`${{ steps.norm.outputs.repo_base_url }}\`" - echo "- Uploaded: \`${uploaded}\`" - echo "" - echo "Generated files:" - find dist/apt-repo -type f | sed 's#^dist/apt-repo/#- `#;s#$#`#' - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-aur.yml b/.github/workflows/deploy-aur.yml new file mode 100644 index 0000000..2fa60c5 --- /dev/null +++ b/.github/workflows/deploy-aur.yml @@ -0,0 +1,268 @@ +name: Deploy AUR Mirror + +on: + workflow_call: + inputs: + tag: + required: true + type: string + version: + required: true + type: string + aur_repo: + required: false + type: string + default: "" + aur_branch: + required: false + type: string + default: "main" + dry_run: + required: false + type: boolean + default: false + secrets: + AUR_REPO_TOKEN: + required: false + workflow_dispatch: + inputs: + version: + description: "Release version (e.g. 0.2.0 or v0.2.0)" + required: true + type: string + tag: + description: "Optional tag override (e.g. v0.2.0). Defaults to v." + required: false + type: string + aur_repo: + description: "Target GitHub repo in OWNER/REPO form (e.g. Auto-Explore/aur-gitcomet). Defaults to AUR_GITHUB_REPO when omitted." + required: false + default: "" + type: string + aur_branch: + description: "Target branch in aur repo. Defaults to AUR_GITHUB_BRANCH when omitted." + required: false + default: "main" + type: string + dry_run: + description: "Validate and print PKGBUILD/.SRCINFO without pushing" + required: true + default: false + type: boolean + +permissions: + contents: read + +concurrency: + group: deploy-aur-${{ inputs.tag || github.event.inputs.tag || inputs.version || github.event.inputs.version || github.run_id }} + cancel-in-progress: false + +jobs: + deploy: + name: Publish PKGBUILD and .SRCINFO to AUR mirror repo + runs-on: ubuntu-latest + timeout-minutes: 30 + container: + image: archlinux:base-devel + steps: + - name: Install Arch packaging tooling + run: | + set -euo pipefail + pacman -Sy --noconfirm --needed ca-certificates ca-certificates-utils curl git perl shadow + + - uses: actions/checkout@v6 + + - name: Normalize inputs + id: norm + env: + INPUT_TAG: ${{ inputs.tag }} + DISPATCH_TAG: ${{ github.event.inputs.tag }} + INPUT_VERSION: ${{ inputs.version }} + DISPATCH_VERSION: ${{ github.event.inputs.version }} + INPUT_AUR_REPO: ${{ inputs.aur_repo }} + DISPATCH_AUR_REPO: ${{ github.event.inputs.aur_repo }} + VAR_AUR_REPO: ${{ vars.AUR_GITHUB_REPO }} + INPUT_AUR_BRANCH: ${{ inputs.aur_branch }} + DISPATCH_AUR_BRANCH: ${{ github.event.inputs.aur_branch }} + VAR_AUR_BRANCH: ${{ vars.AUR_GITHUB_BRANCH }} + INPUT_DRY_RUN: ${{ inputs.dry_run }} + DISPATCH_DRY_RUN: ${{ github.event.inputs.dry_run }} + REPO_OWNER: ${{ github.repository_owner }} + run: | + set -euo pipefail + + tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" + version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" + aur_repo="${INPUT_AUR_REPO:-${DISPATCH_AUR_REPO:-${VAR_AUR_REPO:-}}}" + aur_branch="${INPUT_AUR_BRANCH:-${DISPATCH_AUR_BRANCH:-${VAR_AUR_BRANCH:-main}}}" + dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" + + tag="$(echo "$tag" | tr -d '[:space:]')" + version="$(echo "$version" | tr -d '[:space:]')" + aur_repo="$(echo "$aur_repo" | tr -d '[:space:]')" + aur_branch="$(echo "$aur_branch" | tr -d '[:space:]')" + dry_run="$(echo "$dry_run" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" + + if [ -z "$version" ]; then + echo "::error title=Missing version::Version is required." + exit 1 + fi + + version="${version#v}" + if [ -z "$tag" ]; then + tag="v${version}" + fi + + if [[ "$tag" != v* ]]; then + tag="v${tag}" + fi + + if [ "$tag" != "v${version}" ]; then + echo "::error title=Tag/version mismatch::Tag '$tag' does not match version '$version'." + exit 1 + fi + + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then + echo "::error title=Invalid version::Expected semver like 1.2.3 or 1.2.3-rc.1." + exit 1 + fi + + if [ -z "$aur_repo" ]; then + aur_repo="${REPO_OWNER}/aur-gitcomet" + fi + + if ! [[ "$aur_repo" =~ ^[^/]+/[^/]+$ ]]; then + echo "::error title=Invalid AUR repo::aur_repo must be OWNER/REPO." + exit 1 + fi + + if [ -z "$aur_branch" ]; then + echo "::error title=Missing AUR branch::aur_branch must not be empty." + exit 1 + fi + + if [[ "$dry_run" != "true" && "$dry_run" != "false" ]]; then + echo "::error title=Invalid dry_run::dry_run must be true or false." + exit 1 + fi + + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "aur_repo=$aur_repo" >> "$GITHUB_OUTPUT" + echo "aur_branch=$aur_branch" >> "$GITHUB_OUTPUT" + echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" + + - name: Create non-root packaging user + run: | + set -euo pipefail + id -u builder >/dev/null 2>&1 || useradd -m builder + chown -R builder:builder "$GITHUB_WORKSPACE" + + - name: Download release archives referenced by PKGBUILD + env: + TAG: ${{ steps.norm.outputs.tag }} + VERSION: ${{ steps.norm.outputs.version }} + run: | + set -euo pipefail + mkdir -p dist/aur + binary_name="gitcomet-v${VERSION}-linux-x86_64.tar.gz" + source_name="gitcomet-source-v${VERSION}.tar.gz" + + curl -fL --retry 3 --retry-all-errors \ + "https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${binary_name}" \ + -o "dist/aur/${binary_name}" + + curl -fL --retry 3 --retry-all-errors \ + "https://github.com/${GITHUB_REPOSITORY}/archive/refs/tags/${TAG}.tar.gz" \ + -o "dist/aur/${source_name}" + + - name: Clone AUR mirror repository + env: + AUR_REPO: ${{ steps.norm.outputs.aur_repo }} + AUR_BRANCH: ${{ steps.norm.outputs.aur_branch }} + DRY_RUN: ${{ steps.norm.outputs.dry_run }} + AUR_TOKEN: ${{ secrets.AUR_REPO_TOKEN }} + run: | + set -euo pipefail + clone_url="https://github.com/${AUR_REPO}.git" + + if [ "$DRY_RUN" != "true" ]; then + if [ -z "${AUR_TOKEN:-}" ]; then + echo "::error title=Missing secret::Set AUR_REPO_TOKEN to push to ${AUR_REPO}." + exit 1 + fi + clone_url="https://x-access-token:${AUR_TOKEN}@github.com/${AUR_REPO}.git" + fi + + rm -rf aur-repo + git clone --depth 1 --branch "$AUR_BRANCH" --single-branch "$clone_url" aur-repo + chown -R builder:builder aur-repo dist + + - name: Update PKGBUILD and regenerate .SRCINFO + env: + VERSION: ${{ steps.norm.outputs.version }} + run: | + set -euo pipefail + su builder -c "cd '$GITHUB_WORKSPACE' && scripts/update-aur.sh \ + --aur-dir '$GITHUB_WORKSPACE/aur-repo' \ + --version '$VERSION' \ + --binary-tar '$GITHUB_WORKSPACE/dist/aur/gitcomet-v${VERSION}-linux-x86_64.tar.gz' \ + --source-tar '$GITHUB_WORKSPACE/dist/aur/gitcomet-source-v${VERSION}.tar.gz' \ + --verify-source" + + - name: Emit dry-run summary + if: ${{ steps.norm.outputs.dry_run == 'true' }} + run: | + set -euo pipefail + { + echo "### AUR deployment dry run" + echo "" + echo "- Source release: \`${{ steps.norm.outputs.tag }}\`" + echo "- Target repo: \`${{ steps.norm.outputs.aur_repo }}\`" + echo "- Target branch: \`${{ steps.norm.outputs.aur_branch }}\`" + echo "" + echo "PKGBUILD preview:" + echo '```bash' + cat aur-repo/PKGBUILD + echo '```' + echo "" + echo ".SRCINFO preview:" + echo '```ini' + cat aur-repo/.SRCINFO + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Publish metadata to AUR mirror repo + if: ${{ steps.norm.outputs.dry_run != 'true' }} + env: + AUR_BRANCH: ${{ steps.norm.outputs.aur_branch }} + TAG: ${{ steps.norm.outputs.tag }} + run: | + set -euo pipefail + git config --global --add safe.directory "$GITHUB_WORKSPACE/aur-repo" + + pushd aur-repo >/dev/null + git add PKGBUILD .SRCINFO + if git diff --cached --quiet -- PKGBUILD .SRCINFO; then + echo "No AUR metadata changes detected; mirror repo is already up to date." + popd >/dev/null + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "gitcomet ${TAG}" + git push origin "HEAD:${AUR_BRANCH}" + popd >/dev/null + + - name: Emit deployment summary + run: | + set -euo pipefail + { + echo "### AUR mirror deployment" + echo "" + echo "- Release: \`${{ steps.norm.outputs.tag }}\`" + echo "- Target repo: \`${{ steps.norm.outputs.aur_repo }}\`" + echo "- Target branch: \`${{ steps.norm.outputs.aur_branch }}\`" + echo "- Dry run: \`${{ steps.norm.outputs.dry_run }}\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-flathub.yml b/.github/workflows/deploy-flathub.yml deleted file mode 100644 index 54b2dad..0000000 --- a/.github/workflows/deploy-flathub.yml +++ /dev/null @@ -1,317 +0,0 @@ -name: Deploy Flathub Manifest - -on: - workflow_call: - inputs: - tag: - required: true - type: string - version: - required: true - type: string - flathub_repo: - required: false - type: string - default: "" - flathub_branch: - required: false - type: string - default: "" - mode: - required: false - type: string - default: "pull_request" - dry_run: - required: false - type: boolean - default: false - secrets: - FLATHUB_TOKEN: - required: false - workflow_dispatch: - inputs: - version: - description: "Release version (e.g. 0.2.0 or v0.2.0)" - required: true - type: string - tag: - description: "Optional tag override (e.g. v0.2.0). Defaults to v." - required: false - type: string - flathub_repo: - description: "Target Flathub packaging repo in OWNER/REPO form" - required: false - default: "" - type: string - flathub_branch: - description: "Target branch in the Flathub packaging repo (defaults to that repo's default branch)" - required: false - default: "" - type: string - mode: - description: "Update mode: pull_request or push" - required: false - default: "pull_request" - type: string - dry_run: - description: "Validate and report manifest deployment without pushing" - required: true - default: false - type: boolean - -permissions: - contents: read - -concurrency: - group: deploy-flathub-${{ inputs.tag || github.event.inputs.tag || inputs.version || github.event.inputs.version || github.run_id }} - cancel-in-progress: false - -jobs: - deploy: - name: Publish manifest to Flathub packaging repo - runs-on: ubuntu-latest - timeout-minutes: 20 - env: - GH_TOKEN: ${{ github.token }} - steps: - - name: Normalize inputs - id: norm - env: - INPUT_TAG: ${{ inputs.tag }} - DISPATCH_TAG: ${{ github.event.inputs.tag }} - INPUT_VERSION: ${{ inputs.version }} - DISPATCH_VERSION: ${{ github.event.inputs.version }} - INPUT_FLATHUB_REPO: ${{ inputs.flathub_repo }} - DISPATCH_FLATHUB_REPO: ${{ github.event.inputs.flathub_repo }} - INPUT_FLATHUB_BRANCH: ${{ inputs.flathub_branch }} - DISPATCH_FLATHUB_BRANCH: ${{ github.event.inputs.flathub_branch }} - INPUT_MODE: ${{ inputs.mode }} - DISPATCH_MODE: ${{ github.event.inputs.mode }} - INPUT_DRY_RUN: ${{ inputs.dry_run }} - DISPATCH_DRY_RUN: ${{ github.event.inputs.dry_run }} - run: | - set -euo pipefail - - tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" - version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" - flathub_repo="${INPUT_FLATHUB_REPO:-${DISPATCH_FLATHUB_REPO:-}}" - flathub_branch="${INPUT_FLATHUB_BRANCH:-${DISPATCH_FLATHUB_BRANCH:-}}" - mode="${INPUT_MODE:-${DISPATCH_MODE:-pull_request}}" - dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" - - tag="$(echo "$tag" | tr -d '[:space:]')" - version="$(echo "$version" | tr -d '[:space:]')" - flathub_repo="$(echo "$flathub_repo" | tr -d '[:space:]')" - flathub_branch="$(echo "$flathub_branch" | tr -d '[:space:]')" - mode="$(echo "$mode" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" - dry_run="$(echo "$dry_run" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')" - - if [ -z "$version" ]; then - echo "::error title=Missing version::Version is required." - exit 1 - fi - - version="${version#v}" - if [ -z "$tag" ]; then - tag="v${version}" - fi - - if [[ "$tag" != v* ]]; then - tag="v${tag}" - fi - - if [ "$tag" != "v${version}" ]; then - echo "::error title=Tag/version mismatch::Tag '$tag' does not match version '$version'." - exit 1 - fi - - if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?$ ]]; then - echo "::error title=Invalid version::Expected semver like 1.2.3 or 1.2.3-rc.1." - exit 1 - fi - - if [ -z "$flathub_repo" ]; then - flathub_repo="flathub/dev.gitcomet.GitComet" - fi - - if ! [[ "$flathub_repo" =~ ^[^/]+/[^/]+$ ]]; then - echo "::error title=Invalid Flathub repo::flathub_repo must be OWNER/REPO." - exit 1 - fi - - if [[ "$mode" != "pull_request" && "$mode" != "push" ]]; then - echo "::error title=Invalid mode::mode must be pull_request or push." - exit 1 - fi - - if [[ "$dry_run" != "true" && "$dry_run" != "false" ]]; then - echo "::error title=Invalid dry_run::dry_run must be true or false." - exit 1 - fi - - echo "tag=$tag" >> "$GITHUB_OUTPUT" - echo "version=$version" >> "$GITHUB_OUTPUT" - echo "flathub_repo=$flathub_repo" >> "$GITHUB_OUTPUT" - echo "flathub_branch=$flathub_branch" >> "$GITHUB_OUTPUT" - echo "mode=$mode" >> "$GITHUB_OUTPUT" - echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" - - - name: Download Flathub assets from GitHub release - run: | - set -euo pipefail - mkdir -p dist - gh release view "${{ steps.norm.outputs.tag }}" --repo "$GITHUB_REPOSITORY" >/dev/null - gh release download "${{ steps.norm.outputs.tag }}" \ - --repo "$GITHUB_REPOSITORY" \ - --pattern "dev.gitcomet.GitComet.yaml" \ - --pattern "cargo-sources.json" \ - --pattern "flathub.json" \ - --dir dist \ - --clobber - test -f dist/dev.gitcomet.GitComet.yaml - test -f dist/cargo-sources.json - test -f dist/flathub.json - - - name: Emit dry-run summary - if: ${{ steps.norm.outputs.dry_run == 'true' }} - run: | - set -euo pipefail - branch_display="${{ steps.norm.outputs.flathub_branch }}" - if [ -z "$branch_display" ]; then - branch_display="default branch" - fi - - { - echo "### Flathub deployment dry run" - echo "" - echo "- Source release: \`${{ steps.norm.outputs.tag }}\`" - echo "- Target repo: \`${{ steps.norm.outputs.flathub_repo }}\`" - echo "- Target branch: \`${branch_display}\`" - echo "- Mode: \`${{ steps.norm.outputs.mode }}\`" - echo "" - echo "Manifest SHA256: \`$(sha256sum dist/dev.gitcomet.GitComet.yaml | awk '{print $1}')\`" - echo "Cargo sources SHA256: \`$(sha256sum dist/cargo-sources.json | awk '{print $1}')\`" - echo "flathub.json SHA256: \`$(sha256sum dist/flathub.json | awk '{print $1}')\`" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Publish manifest to Flathub packaging repo - if: ${{ steps.norm.outputs.dry_run != 'true' }} - env: - FLATHUB_TOKEN: ${{ secrets.FLATHUB_TOKEN }} - FLATHUB_REPO: ${{ steps.norm.outputs.flathub_repo }} - FLATHUB_BRANCH: ${{ steps.norm.outputs.flathub_branch }} - FLATHUB_MODE: ${{ steps.norm.outputs.mode }} - TAG: ${{ steps.norm.outputs.tag }} - run: | - set -euo pipefail - - if [ -z "${FLATHUB_TOKEN:-}" ]; then - echo "::error title=Missing secret::Set FLATHUB_TOKEN to push to ${FLATHUB_REPO}." - exit 1 - fi - - clone_args=() - if [ -n "$FLATHUB_BRANCH" ]; then - clone_args+=(--branch "$FLATHUB_BRANCH") - fi - - rm -rf flathub-repo - git clone "${clone_args[@]}" "https://x-access-token:${FLATHUB_TOKEN}@github.com/${FLATHUB_REPO}.git" flathub-repo - - cp dist/dev.gitcomet.GitComet.yaml flathub-repo/dev.gitcomet.GitComet.yaml - cp dist/cargo-sources.json flathub-repo/cargo-sources.json - cp dist/flathub.json flathub-repo/flathub.json - - pushd flathub-repo >/dev/null - base_branch="$(git branch --show-current)" - if [ -z "$base_branch" ]; then - echo "::error title=Unknown branch::Could not determine the checked out branch for ${FLATHUB_REPO}." - popd >/dev/null - exit 1 - fi - - echo "FLATHUB_BASE_BRANCH=${base_branch}" >> "$GITHUB_ENV" - echo "FLATHUB_MODE_RESOLVED=${FLATHUB_MODE}" >> "$GITHUB_ENV" - - if [ -z "$(git status --porcelain -- dev.gitcomet.GitComet.yaml cargo-sources.json flathub.json)" ]; then - echo "No Flathub manifest changes detected; packaging repo already up to date." - popd >/dev/null - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - if [ "$FLATHUB_MODE" = "push" ]; then - git add dev.gitcomet.GitComet.yaml cargo-sources.json flathub.json - git commit -m "dev.gitcomet.GitComet ${TAG}" - git push origin "HEAD:${base_branch}" - echo "FLATHUB_TARGET_BRANCH=${base_branch}" >> "$GITHUB_ENV" - else - pr_branch="gitcomet/release-${TAG}" - git checkout -B "$pr_branch" - git add dev.gitcomet.GitComet.yaml cargo-sources.json flathub.json - git commit -m "dev.gitcomet.GitComet ${TAG}" - - if git ls-remote --exit-code --heads origin "$pr_branch" >/dev/null 2>&1; then - git push --force-with-lease -u origin "$pr_branch" - else - git push -u origin "$pr_branch" - fi - - { - printf 'Update GitComet to %s.\n\n' "$TAG" - printf 'Generated by `.github/workflows/deploy-flathub.yml` from GitHub release assets in %s.\n\n' "$GITHUB_REPOSITORY" - printf 'Assets:\n- `dev.gitcomet.GitComet.yaml`\n- `cargo-sources.json`\n- `flathub.json`\n' - } > /tmp/flathub-pr-body.md - - existing_pr="$(GH_TOKEN="$FLATHUB_TOKEN" gh pr list \ - --repo "$FLATHUB_REPO" \ - --head "$pr_branch" \ - --base "$base_branch" \ - --json number \ - --jq '.[0].number // empty')" - - if [ -n "$existing_pr" ]; then - GH_TOKEN="$FLATHUB_TOKEN" gh pr edit "$existing_pr" \ - --repo "$FLATHUB_REPO" \ - --title "dev.gitcomet.GitComet ${TAG}" \ - --body-file /tmp/flathub-pr-body.md >/dev/null - pr_url="$(GH_TOKEN="$FLATHUB_TOKEN" gh pr view "$existing_pr" --repo "$FLATHUB_REPO" --json url --jq '.url')" - else - pr_url="$(GH_TOKEN="$FLATHUB_TOKEN" gh pr create \ - --repo "$FLATHUB_REPO" \ - --base "$base_branch" \ - --head "$pr_branch" \ - --title "dev.gitcomet.GitComet ${TAG}" \ - --body-file /tmp/flathub-pr-body.md)" - fi - - echo "FLATHUB_TARGET_BRANCH=${pr_branch}" >> "$GITHUB_ENV" - echo "FLATHUB_PR_URL=${pr_url}" >> "$GITHUB_ENV" - fi - - popd >/dev/null - - - name: Emit deployment summary - run: | - set -euo pipefail - branch_display="${FLATHUB_TARGET_BRANCH:-${{ steps.norm.outputs.flathub_branch }}}" - if [ -z "$branch_display" ]; then - branch_display="default branch" - fi - mode_display="${FLATHUB_MODE_RESOLVED:-${{ steps.norm.outputs.mode }}}" - - { - echo "### Flathub deployment" - echo "" - echo "- Release: \`${{ steps.norm.outputs.tag }}\`" - echo "- Target repo: \`${{ steps.norm.outputs.flathub_repo }}\`" - echo "- Target branch: \`${branch_display}\`" - echo "- Base branch: \`${FLATHUB_BASE_BRANCH:-default branch}\`" - echo "- Mode: \`${mode_display}\`" - echo "- Dry run: \`${{ steps.norm.outputs.dry_run }}\`" - if [ -n "${FLATHUB_PR_URL:-}" ]; then - echo "- Pull request: ${FLATHUB_PR_URL}" - fi - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/deploy-homebrew-tap.yml b/.github/workflows/deploy-homebrew-tap.yml index a727a7b..bc37668 100644 --- a/.github/workflows/deploy-homebrew-tap.yml +++ b/.github/workflows/deploy-homebrew-tap.yml @@ -35,17 +35,17 @@ on: required: false type: string tap_repo: - description: "Target tap repository in OWNER/REPO form (e.g. Auto-Explore/homebrew-gitcomet)" + description: "Target tap repository in OWNER/REPO form (e.g. Auto-Explore/homebrew-gitcomet). Defaults to HOMEBREW_TAP_REPO when omitted." required: false default: "" type: string tap_branch: - description: "Target branch in tap repo" + description: "Target branch in tap repo. Defaults to HOMEBREW_TAP_BRANCH when omitted." required: false default: "main" type: string dry_run: - description: "Validate and print formula without pushing to tap" + description: "Validate and print cask/formula without pushing to tap" required: true default: false type: boolean @@ -59,7 +59,7 @@ concurrency: jobs: deploy: - name: Publish formula to Homebrew tap + name: Publish Homebrew cask and formula runs-on: ubuntu-latest timeout-minutes: 20 env: @@ -74,8 +74,10 @@ jobs: DISPATCH_VERSION: ${{ github.event.inputs.version }} INPUT_TAP_REPO: ${{ inputs.tap_repo }} DISPATCH_TAP_REPO: ${{ github.event.inputs.tap_repo }} + VAR_TAP_REPO: ${{ vars.HOMEBREW_TAP_REPO }} INPUT_TAP_BRANCH: ${{ inputs.tap_branch }} DISPATCH_TAP_BRANCH: ${{ github.event.inputs.tap_branch }} + VAR_TAP_BRANCH: ${{ vars.HOMEBREW_TAP_BRANCH }} INPUT_DRY_RUN: ${{ inputs.dry_run }} DISPATCH_DRY_RUN: ${{ github.event.inputs.dry_run }} REPO_OWNER: ${{ github.repository_owner }} @@ -84,8 +86,8 @@ jobs: tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" - tap_repo="${INPUT_TAP_REPO:-${DISPATCH_TAP_REPO:-}}" - tap_branch="${INPUT_TAP_BRANCH:-${DISPATCH_TAP_BRANCH:-main}}" + tap_repo="${INPUT_TAP_REPO:-${DISPATCH_TAP_REPO:-${VAR_TAP_REPO:-}}}" + tap_branch="${INPUT_TAP_BRANCH:-${DISPATCH_TAP_BRANCH:-${VAR_TAP_BRANCH:-main}}}" dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" tag="$(echo "$tag" | tr -d '[:space:]')" @@ -138,7 +140,7 @@ jobs: echo "tap_branch=$tap_branch" >> "$GITHUB_OUTPUT" echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" - - name: Download formula from release assets + - name: Download cask and formula from release assets run: | set -euo pipefail mkdir -p dist @@ -146,12 +148,17 @@ jobs: gh release download "${{ steps.norm.outputs.tag }}" \ --repo "$GITHUB_REPOSITORY" \ --pattern "gitcomet.rb" \ + --pattern "gitcomet-cli.rb" \ --dir dist \ --clobber test -f dist/gitcomet.rb + test -f dist/gitcomet-cli.rb - - name: Validate formula syntax - run: ruby -c dist/gitcomet.rb + - name: Validate cask and formula syntax + run: | + set -euo pipefail + ruby -c dist/gitcomet.rb + ruby -c dist/gitcomet-cli.rb - name: Emit dry-run summary if: ${{ steps.norm.outputs.dry_run == 'true' }} @@ -164,13 +171,18 @@ jobs: echo "- Target tap: \`${{ steps.norm.outputs.tap_repo }}\`" echo "- Target branch: \`${{ steps.norm.outputs.tap_branch }}\`" echo "" - echo "Formula preview:" + echo "Cask preview (Casks/gitcomet.rb):" echo '```ruby' cat dist/gitcomet.rb echo '```' + echo "" + echo "Formula preview (Formula/gitcomet-cli.rb):" + echo '```ruby' + cat dist/gitcomet-cli.rb + echo '```' } >> "$GITHUB_STEP_SUMMARY" - - name: Publish formula to tap repository + - name: Publish cask and formula to tap repository if: ${{ steps.norm.outputs.dry_run != 'true' }} env: TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} @@ -187,19 +199,24 @@ jobs: rm -rf tap-repo git clone "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_REPO}.git" tap-repo - mkdir -p tap-repo/Formula - cp dist/gitcomet.rb tap-repo/Formula/gitcomet.rb + mkdir -p tap-repo/Casks tap-repo/Formula + cp dist/gitcomet.rb tap-repo/Casks/gitcomet.rb + cp dist/gitcomet-cli.rb tap-repo/Formula/gitcomet-cli.rb pushd tap-repo >/dev/null - if git diff --quiet -- Formula/gitcomet.rb; then - echo "No formula changes detected; tap already up to date." + git add Casks/gitcomet.rb Formula/gitcomet-cli.rb + if git ls-files --error-unmatch Formula/gitcomet.rb >/dev/null 2>&1; then + git rm -f Formula/gitcomet.rb + fi + + if git diff --cached --quiet -- Casks/gitcomet.rb Formula/gitcomet-cli.rb Formula/gitcomet.rb; then + echo "No Homebrew cask/formula changes detected; tap already up to date." popd >/dev/null exit 0 fi git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add Formula/gitcomet.rb git commit -m "gitcomet ${TAG}" git push origin "HEAD:${TAP_BRANCH}" popd >/dev/null diff --git a/.github/workflows/deployment-ci.yml b/.github/workflows/deployment-ci.yml index 3456857..f1920da 100644 --- a/.github/workflows/deployment-ci.yml +++ b/.github/workflows/deployment-ci.yml @@ -5,33 +5,27 @@ on: branches: ["main"] paths: - ".github/workflows/build-release-artifacts.yml" - - ".github/workflows/deployment-ci.yml" + - ".github/workflows/deploy-aur.yml" - ".github/workflows/deploy-homebrew-tap.yml" - - ".github/workflows/deploy-flathub.yml" - ".github/workflows/deploy-apt-repo.yml" - ".github/workflows/release-manual-main.yml" - - "assets/linux/dev.gitcomet.GitComet.desktop" - - "flatpak/**" - - "scripts/generate-flatpak-cargo-sources.py" - - "scripts/render-flathub-manifest.sh" + - "scripts/update-aur.sh" - "scripts/package-macos.sh" - "scripts/generate-homebrew-formula.sh" + - "scripts/generate-homebrew-cask.sh" - "scripts/build-apt-repo.sh" push: branches: ["main"] paths: - ".github/workflows/build-release-artifacts.yml" - - ".github/workflows/deployment-ci.yml" + - ".github/workflows/deploy-aur.yml" - ".github/workflows/deploy-homebrew-tap.yml" - - ".github/workflows/deploy-flathub.yml" - ".github/workflows/deploy-apt-repo.yml" - ".github/workflows/release-manual-main.yml" - - "assets/linux/dev.gitcomet.GitComet.desktop" - - "flatpak/**" - - "scripts/generate-flatpak-cargo-sources.py" - - "scripts/render-flathub-manifest.sh" + - "scripts/update-aur.sh" - "scripts/package-macos.sh" - "scripts/generate-homebrew-formula.sh" + - "scripts/generate-homebrew-cask.sh" - "scripts/build-apt-repo.sh" workflow_dispatch: @@ -51,29 +45,58 @@ jobs: - name: Validate shell scripts syntax run: | + bash -n scripts/update-aur.sh bash -n scripts/package-macos.sh bash -n scripts/generate-homebrew-formula.sh + bash -n scripts/generate-homebrew-cask.sh bash -n scripts/build-apt-repo.sh - bash -n scripts/render-flathub-manifest.sh - name: Validate workflow YAML run: | ruby -e 'require "yaml"; %w[ .github/workflows/build-release-artifacts.yml - .github/workflows/deploy-flathub.yml + .github/workflows/deploy-aur.yml .github/workflows/deploy-homebrew-tap.yml .github/workflows/deploy-apt-repo.yml .github/workflows/deployment-ci.yml .github/workflows/release-manual-main.yml ].each { |path| YAML.load_file(path) }' - - name: Generate formula from synthetic artifacts + - name: Validate deployment workflow config keys + run: | + set -euo pipefail + grep -Fq 'vars.AUR_GITHUB_REPO' .github/workflows/release-manual-main.yml + grep -Fq 'vars.AUR_GITHUB_BRANCH' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_STORAGE_ACCOUNT' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_STORAGE_CONTAINER' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_REPO_DISTRIBUTION' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_REPO_COMPONENT' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_REPO_ORIGIN' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_REPO_LABEL' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_REPO_DESCRIPTION' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_STORAGE_PUBLIC_ACCESS' .github/workflows/release-manual-main.yml + grep -Fq 'vars.APT_STORAGE_ACCOUNT' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_STORAGE_CONTAINER' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_REPO_DISTRIBUTION' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_REPO_COMPONENT' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_REPO_ORIGIN' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_REPO_LABEL' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_REPO_DESCRIPTION' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.APT_STORAGE_PUBLIC_ACCESS' .github/workflows/deploy-apt-repo.yml + grep -Fq 'vars.AUR_GITHUB_REPO' .github/workflows/deploy-aur.yml + grep -Fq 'vars.AUR_GITHUB_BRANCH' .github/workflows/deploy-aur.yml + grep -Fq 'AUR_REPO_TOKEN' .github/workflows/deploy-aur.yml + grep -Fq 'HOMEBREW_TAP_TOKEN' .github/workflows/deploy-homebrew-tap.yml + + - name: Generate Homebrew cask and formula from synthetic artifacts run: | set -euo pipefail mkdir -p dist/deployment-ci printf 'arm64-test' > dist/deployment-ci/gitcomet-v0.0.0-ci-macos-arm64.tar.gz printf 'intel-test' > dist/deployment-ci/gitcomet-v0.0.0-ci-macos-x86_64.tar.gz printf 'linux-test' > dist/deployment-ci/gitcomet-v0.0.0-ci-linux-x86_64.tar.gz + printf 'arm64-dmg-test' > dist/deployment-ci/gitcomet-v0.0.0-ci-macos-arm64.dmg + printf 'intel-dmg-test' > dist/deployment-ci/gitcomet-v0.0.0-ci-macos-x86_64.dmg scripts/generate-homebrew-formula.sh \ --version "0.0.0-ci" \ @@ -81,11 +104,137 @@ jobs: --arm-tar "dist/deployment-ci/gitcomet-v0.0.0-ci-macos-arm64.tar.gz" \ --intel-tar "dist/deployment-ci/gitcomet-v0.0.0-ci-macos-x86_64.tar.gz" \ --linux-tar "dist/deployment-ci/gitcomet-v0.0.0-ci-linux-x86_64.tar.gz" \ + --output "dist/deployment-ci/gitcomet-cli.rb" + + scripts/generate-homebrew-cask.sh \ + --version "0.0.0-ci" \ + --github-repo "Auto-Explore/GitComet" \ + --arm-dmg "dist/deployment-ci/gitcomet-v0.0.0-ci-macos-arm64.dmg" \ + --intel-dmg "dist/deployment-ci/gitcomet-v0.0.0-ci-macos-x86_64.dmg" \ --output "dist/deployment-ci/gitcomet.rb" ruby -c dist/deployment-ci/gitcomet.rb - grep -q "on_linux do" dist/deployment-ci/gitcomet.rb - grep -q "gitcomet-v0.0.0-ci-linux-x86_64.tar.gz" dist/deployment-ci/gitcomet.rb + ruby -c dist/deployment-ci/gitcomet-cli.rb + grep -q 'cask "gitcomet" do' dist/deployment-ci/gitcomet.rb + grep -q 'app "GitComet.app"' dist/deployment-ci/gitcomet.rb + grep -q 'brew install gitcomet-cli' dist/deployment-ci/gitcomet.rb + grep -q 'class GitcometCli < Formula' dist/deployment-ci/gitcomet-cli.rb + grep -q "on_linux do" dist/deployment-ci/gitcomet-cli.rb + grep -q 'bin.install "gitcomet"' dist/deployment-ci/gitcomet-cli.rb + + - name: Validate Homebrew tap publish detection + run: | + set -euo pipefail + rm -rf dist/deployment-ci/tap-repo + git init --initial-branch=main dist/deployment-ci/tap-repo + + pushd dist/deployment-ci/tap-repo >/dev/null + git config user.name "GitComet CI" + git config user.email "ci@autoexplore.ai" + echo "# Homebrew Tap" > README.md + mkdir -p Formula + cat > Formula/gitcomet.rb <<'RUBY' + class Gitcomet < Formula + desc "Legacy GitComet formula" + homepage "https://example.com" + url "https://example.com/gitcomet.tar.gz" + sha256 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + version "0.0.0" + end + RUBY + + git add README.md Formula/gitcomet.rb + git commit -m "Initial tap repo" + + mkdir -p Casks Formula + cp ../gitcomet.rb Casks/gitcomet.rb + cp ../gitcomet-cli.rb Formula/gitcomet-cli.rb + + git add Casks/gitcomet.rb Formula/gitcomet-cli.rb + if git ls-files --error-unmatch Formula/gitcomet.rb >/dev/null 2>&1; then + git rm -f Formula/gitcomet.rb + fi + + if git diff --cached --quiet -- Casks/gitcomet.rb Formula/gitcomet-cli.rb Formula/gitcomet.rb; then + echo "Expected Homebrew cask/formula publish to be detected as a change." + exit 1 + fi + popd >/dev/null + + aur-metadata-smoke: + name: Validate AUR metadata update script + runs-on: ubuntu-latest + timeout-minutes: 20 + container: + image: archlinux:base-devel + steps: + - name: Install Arch packaging tooling + run: | + set -euo pipefail + pacman -Sy --noconfirm --needed perl shadow + + - uses: actions/checkout@v6 + + - name: Create non-root packaging user + run: | + set -euo pipefail + id -u builder >/dev/null 2>&1 || useradd -m builder + chown -R builder:builder "$GITHUB_WORKSPACE" + + - name: Prepare synthetic AUR repo and assets + run: | + set -euo pipefail + mkdir -p dist/aur-ci/repo + cat > dist/aur-ci/repo/PKGBUILD <<'EOF' + pkgname=gitcomet + pkgver=0.0.0 + pkgrel=1 + pkgdesc="Fast, resource-efficient Git GUI written in Rust" + arch=('x86_64') + url="https://gitcomet.dev/" + license=('AGPL-3.0-only') + depends=( + 'fontconfig' + 'freetype2' + 'git' + 'libx11' + 'libxcb' + 'libxkbcommon' + 'wayland' + ) + source=( + "gitcomet-v$pkgver-linux-x86_64.tar.gz::https://example.invalid/gitcomet-v$pkgver-linux-x86_64.tar.gz" + "gitcomet-source-v$pkgver.tar.gz::https://example.invalid/gitcomet-source-v$pkgver.tar.gz" + ) + sha256sums=('oldbinary' + 'oldsource') + + package() { + install -D -m755 "gitcomet-v$pkgver-linux-x86_64/gitcomet" "$pkgdir/usr/bin/gitcomet" + } + EOF + + printf 'synthetic-binary' > dist/aur-ci/gitcomet-v0.0.0-linux-x86_64.tar.gz + printf 'synthetic-source' > dist/aur-ci/gitcomet-source-v0.0.0.tar.gz + chown -R builder:builder dist + + - name: Run update-aur.sh + run: | + set -euo pipefail + su builder -c "cd '$GITHUB_WORKSPACE' && scripts/update-aur.sh \ + --aur-dir '$GITHUB_WORKSPACE/dist/aur-ci/repo' \ + --version 0.0.0 \ + --binary-tar '$GITHUB_WORKSPACE/dist/aur-ci/gitcomet-v0.0.0-linux-x86_64.tar.gz' \ + --source-tar '$GITHUB_WORKSPACE/dist/aur-ci/gitcomet-source-v0.0.0.tar.gz' \ + --verify-source" + + - name: Validate generated PKGBUILD and .SRCINFO + run: | + set -euo pipefail + test -f dist/aur-ci/repo/.SRCINFO + grep -q '^pkgver=0.0.0$' dist/aur-ci/repo/PKGBUILD + grep -q 'source = gitcomet-v0.0.0-linux-x86_64.tar.gz::https://example.invalid/gitcomet-v0.0.0-linux-x86_64.tar.gz$' dist/aur-ci/repo/.SRCINFO + grep -q 'source = gitcomet-source-v0.0.0.tar.gz::https://example.invalid/gitcomet-source-v0.0.0.tar.gz$' dist/aur-ci/repo/.SRCINFO apt-repo-generation-smoke: name: Validate APT repo generation script @@ -105,14 +254,14 @@ jobs: pkg_root="dist/apt-ci/pkg" mkdir -p "${pkg_root}/DEBIAN" "${pkg_root}/usr/bin" - printf '%s\n' '#!/usr/bin/env bash' 'echo "GitComet CI"' > "${pkg_root}/usr/bin/gitcomet-app" - chmod +x "${pkg_root}/usr/bin/gitcomet-app" + printf '%s\n' '#!/usr/bin/env bash' 'echo "GitComet CI"' > "${pkg_root}/usr/bin/gitcomet" + chmod +x "${pkg_root}/usr/bin/gitcomet" { echo "Package: gitcomet" echo "Version: 0.0.0~ci1" echo "Architecture: amd64" - echo "Maintainer: GitComet CI " + echo "Maintainer: GitComet CI " echo "Description: Synthetic GitComet package for deployment CI." } > "${pkg_root}/DEBIAN/control" @@ -130,7 +279,7 @@ jobs: echo "Key-Type: RSA" echo "Key-Length: 3072" echo "Name-Real: GitComet CI" - echo "Name-Email: ci@example.invalid" + echo "Name-Email: ci@autoexplore.ai" echo "Expire-Date: 0" echo "%no-protection" echo "%commit" @@ -143,6 +292,7 @@ jobs: - name: Build signed APT repo env: GNUPGHOME: ${{ github.workspace }}/dist/apt-ci/gnupg + APT_GPG_PASSPHRASE: ${{ secrets.APT_GPG_PASSPHRASE }} run: | set -euo pipefail scripts/build-apt-repo.sh \ @@ -155,6 +305,7 @@ jobs: --label GitComet \ --description "GitComet deployment CI repository" \ --repo-url https://example.invalid/gitcomet/apt \ + --gpg-passphrase "${APT_GPG_PASSPHRASE}" \ --signing-key "${APT_TEST_GPG_KEY_ID}" - name: Validate generated repo @@ -177,6 +328,38 @@ jobs: chmod 700 "$GNUPGHOME" gpg --batch --no-default-keyring --keyring dist/apt-ci/repo/gitcomet-archive-keyring.gpg --verify dist/apt-ci/repo/dists/stable/InRelease >/dev/null + - name: Verify generated repo with apt-secure + run: | + set -euo pipefail + + repo_root="$(pwd)/dist/apt-ci/repo" + source_dir="${RUNNER_TEMP}/gitcomet-apt-sources.d" + lists_dir="${RUNNER_TEMP}/gitcomet-apt-lists" + + mkdir -p "$source_dir" + sudo mkdir -p "${lists_dir}/partial" + + cat > "${source_dir}/gitcomet-ci.sources" </dev/null + "${mount_point}/GitComet.app/Contents/MacOS/gitcomet" --help >/dev/null hdiutil detach "$mount_point" - - flatpak-packaging-smoke: - name: Flatpak packaging smoke - runs-on: ubuntu-latest - timeout-minutes: 120 - container: - image: ghcr.io/flathub-infra/flatpak-github-actions:freedesktop-24.08 - options: --privileged - steps: - - uses: actions/checkout@v6 - - - name: Verify generated cargo sources stay in sync - run: | - set -euo pipefail - python3 scripts/generate-flatpak-cargo-sources.py --output /tmp/cargo-sources.json - cmp /tmp/cargo-sources.json flatpak/cargo-sources.json - - - name: Render release manifest - run: | - set -euo pipefail - scripts/render-flathub-manifest.sh \ - --source-url "https://example.invalid/gitcomet-v0.0.0-ci-source.tar.gz" \ - --source-sha256 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" \ - --output /tmp/dev.gitcomet.GitComet.yaml - test -s /tmp/dev.gitcomet.GitComet.yaml - - - name: Build Flatpak bundle - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 - with: - bundle: dev.gitcomet.GitComet.flatpak - manifest-path: flatpak/dev.gitcomet.GitComet.local.yaml - cache-key: deployment-ci-flatpak - upload-artifact: false - - - name: Verify Flatpak bundle output - run: | - set -euo pipefail - test -f dev.gitcomet.GitComet.flatpak diff --git a/.github/workflows/release-manual-main.yml b/.github/workflows/release-manual-main.yml index 7631800..29e7f22 100644 --- a/.github/workflows/release-manual-main.yml +++ b/.github/workflows/release-manual-main.yml @@ -82,10 +82,10 @@ jobs: - name: Verify crate version matches release version run: | set -euo pipefail - crate_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' crates/gitcomet-app/Cargo.toml | head -n1)" + crate_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -n1)" wanted="${{ steps.norm.outputs.version }}" if [ "$crate_version" != "$wanted" ]; then - echo "::error title=Version mismatch::crates/gitcomet-app/Cargo.toml version '$crate_version' does not match requested release '$wanted'." + echo "::error title=Version mismatch::Cargo.toml version '$crate_version' does not match requested release '$wanted'." exit 1 fi @@ -175,7 +175,7 @@ jobs: gh release view "$tag" --repo "$GITHUB_REPOSITORY" deploy_homebrew_tap: - name: Deploy Homebrew tap formula + name: Deploy Homebrew tap packages needs: [validate, build_and_upload, publish_release] if: ${{ fromJSON(needs.validate.outputs.draft) == false && needs.build_and_upload.result == 'success' }} uses: ./.github/workflows/deploy-homebrew-tap.yml @@ -187,17 +187,16 @@ jobs: dry_run: false secrets: inherit - deploy_flathub: - name: Deploy Flathub manifest + deploy_aur: + name: Deploy AUR mirror metadata needs: [validate, build_and_upload, publish_release] if: ${{ fromJSON(needs.validate.outputs.draft) == false && needs.build_and_upload.result == 'success' }} - uses: ./.github/workflows/deploy-flathub.yml + uses: ./.github/workflows/deploy-aur.yml with: tag: ${{ needs.validate.outputs.tag }} version: ${{ needs.validate.outputs.version }} - flathub_repo: ${{ vars.FLATHUB_REPO }} - flathub_branch: ${{ vars.FLATHUB_BRANCH }} - mode: ${{ vars.FLATHUB_MODE }} + aur_repo: ${{ vars.AUR_GITHUB_REPO }} + aur_branch: ${{ vars.AUR_GITHUB_BRANCH }} dry_run: false secrets: inherit @@ -212,7 +211,6 @@ jobs: storage_account: ${{ vars.APT_STORAGE_ACCOUNT }} storage_container: ${{ vars.APT_STORAGE_CONTAINER }} storage_prefix: ${{ vars.APT_STORAGE_PREFIX }} - repo_base_url: ${{ vars.APT_REPO_BASE_URL }} distribution: ${{ vars.APT_REPO_DISTRIBUTION }} component: ${{ vars.APT_REPO_COMPONENT }} architecture: "amd64" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b02a407..9b719c1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ concurrency: env: CARGO_TERM_COLOR: always - # Build gitcomet-app in headless mode (no GPUI system deps required). + # Build gitcomet in headless mode (no GPUI system deps required). # The UI-only code paths are behind #[cfg(feature = "ui-gpui")] guards. APP_FEATURES: "--no-default-features --features gix" @@ -77,7 +77,7 @@ jobs: echo "Skipping cargo clippy -p gitcomet-ui-gpui in this job." echo "Reason: GPUI requires native windowing/system dependencies that are not available in headless CI." - name: Clippy (app — headless) - run: cargo clippy -p gitcomet-app $APP_FEATURES -- -D warnings + run: cargo clippy -p gitcomet $APP_FEATURES -- -D warnings build: name: Build @@ -92,7 +92,7 @@ jobs: - name: Build (core + state + backend) run: cargo build -p gitcomet-core -p gitcomet-state -p gitcomet-git-gix --verbose - name: Build (app — headless) - run: cargo build -p gitcomet-app $APP_FEATURES --verbose + run: cargo build -p gitcomet $APP_FEATURES --verbose # Core merge algorithm correctness — Phase 1A/1B/1C portability tests merge-algorithm: @@ -148,16 +148,16 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Cache Rust artifacts uses: Swatinem/rust-cache@v2 - - name: Build gitcomet-app binary (headless) - run: cargo build -p gitcomet-app $APP_FEATURES + - name: Build gitcomet binary (headless) + run: cargo build -p gitcomet $APP_FEATURES - name: Git mergetool E2E (Phase 4A — t7610 parity) - run: cargo test -p gitcomet-app $APP_FEATURES --test mergetool_git_integration --verbose + run: cargo test -p gitcomet $APP_FEATURES --test mergetool_git_integration --verbose - name: Git difftool E2E (Phase 4B — t7800 parity) - run: cargo test -p gitcomet-app $APP_FEATURES --test difftool_git_integration --verbose + run: cargo test -p gitcomet $APP_FEATURES --test difftool_git_integration --verbose - name: Standalone tool-mode E2E (exit codes + validation) - run: cargo test -p gitcomet-app $APP_FEATURES --test standalone_tool_mode_integration --verbose + run: cargo test -p gitcomet $APP_FEATURES --test standalone_tool_mode_integration --verbose - name: Mergetool/difftool runtime unit tests (bin target) - run: cargo test -p gitcomet-app $APP_FEATURES --bin gitcomet-app --verbose + run: cargo test -p gitcomet $APP_FEATURES --bin gitcomet --verbose # Backend integration — mergetool launcher, status, conflict checkout backend-integration: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67cadd2..32937b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ - `crates/gitcomet-state`: MVU state store, reducers, effects, conflict session management. - `crates/gitcomet-ui`: UI model/state (toolkit-independent). - `crates/gitcomet-ui-gpui`: gpui views/components (focused diff/merge windows, conflict resolver, word diff). -- `crates/gitcomet-app`: binary entrypoint, CLI (clap), difftool/mergetool/setup/uninstall modes. +- `crates/gitcomet`: binary entrypoint, CLI (clap), difftool/mergetool/setup/uninstall modes. ### Getting started @@ -28,19 +28,19 @@ cargo build To build the actual app you'll enable features (requires network for dependencies): ```bash -cargo build -p gitcomet-app --features ui,gix +cargo build -p gitcomet --features ui,gix ``` To also compile the gpui-based UI crate: ```bash -cargo build -p gitcomet-app --features ui-gpui,gix +cargo build -p gitcomet --features ui-gpui,gix ``` Run (opens the repo passed as the first arg, or falls back to the current directory): ```bash -cargo run -p gitcomet-app --features ui-gpui,gix -- /path/to/repo +cargo run -p gitcomet --features ui-gpui,gix -- /path/to/repo ``` ### Testing @@ -84,14 +84,14 @@ Use `--skip-dmg` when running in restricted/sandboxed environments where `hdiuti The release workflow `.github/workflows/build-release-artifacts.yml` builds and publishes: - Windows: portable ZIP + MSI -- Linux: tar.gz + AppImage + .deb + Flatpak bundle +- Linux: tar.gz + AppImage + .deb - macOS: DMG + tar.gz for `arm64` and `x86_64` -- Flathub assets: `gitcomet-v-source.tar.gz`, `dev.gitcomet.GitComet.yaml`, `cargo-sources.json`, and `flathub.json` -- Homebrew formula asset: `gitcomet.rb` (generated from macOS + Linux x86_64 tarballs and their SHA256 values) +- Homebrew cask asset: `gitcomet.rb` (generated from macOS DMG artifacts and their SHA256 values) +- Homebrew CLI formula asset: `gitcomet-cli.rb` (generated from macOS + Linux x86_64 tarballs and their SHA256 values) ### Homebrew deployment -To push `Formula/gitcomet.rb` into a Homebrew tap repo automatically on release: +To push `Casks/gitcomet.rb` and `Formula/gitcomet-cli.rb` into a Homebrew tap repo automatically on release: 1. Create a tap repository (default expected name: `OWNER/homebrew-gitcomet`). 2. In this repo, configure: @@ -104,28 +104,27 @@ This release flow will: - build and upload release artifacts - publish the GitHub release -- call `.github/workflows/deploy-homebrew-tap.yml` to update `Formula/gitcomet.rb` in the tap repo +- call `.github/workflows/deploy-homebrew-tap.yml` to update `Casks/gitcomet.rb` and `Formula/gitcomet-cli.rb` in the tap repo You can also run `.github/workflows/deploy-homebrew-tap.yml` manually for backfills or dry-runs. -### Flathub deployment +### AUR mirror deployment -The app ID used for Flatpak/Flathub packaging is `dev.gitcomet.GitComet`. +To push `PKGBUILD` and `.SRCINFO` into a GitHub-hosted AUR mirror repo automatically on release: -To push `dev.gitcomet.GitComet.yaml`, `cargo-sources.json`, and `flathub.json` into the Flathub packaging repo automatically on release: - -1. Complete the one-time Flathub onboarding so the app has a packaging repo, typically `flathub/dev.gitcomet.GitComet`. +1. Create the target repository (default expected name: `OWNER/aur-gitcomet`). 2. In this repo, configure: - - secret `FLATHUB_TOKEN`: GitHub token with `contents:write` access to the Flathub packaging repository. - - optional variable `FLATHUB_REPO`: packaging repository in `OWNER/REPO` form. Defaults to `flathub/dev.gitcomet.GitComet`. - - optional variable `FLATHUB_BRANCH`: target branch. If unset, the packaging repo default branch is used. - - optional variable `FLATHUB_MODE`: `pull_request` (default, opens/updates a PR against the packaging repo) or `push` (commits straight to the target branch). + - secret `AUR_REPO_TOKEN`: GitHub token with `contents:write` access to the AUR mirror repository. + - optional variable `AUR_GITHUB_REPO`: target repository in `OWNER/REPO` form. + - optional variable `AUR_GITHUB_BRANCH`: target branch (default `main`). 3. Run `.github/workflows/release-manual-main.yml` with `draft=false`. This release flow will: -- build and upload the Flatpak bundle plus Flathub source/manifest assets -- publish the GitHub release so the source tarball URL is public -- call `.github/workflows/deploy-flathub.yml` to update the Flathub packaging repo, either by opening/updating a PR or by pushing directly depending on `FLATHUB_MODE` +- download the published Linux release tarball and source tarball +- update `PKGBUILD` `pkgver` and `sha256sums` +- regenerate `.SRCINFO` +- validate sources with `makepkg --verifysource` +- push the updated metadata into the configured AUR mirror repository -You can also run `.github/workflows/deploy-flathub.yml` manually for backfills or dry-runs. +You can also run `.github/workflows/deploy-aur.yml` manually for backfills or dry-runs. diff --git a/Cargo.lock b/Cargo.lock index f0d1ab5..faaf34e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2239,8 +2239,8 @@ dependencies = [ ] [[package]] -name = "gitcomet-app" -version = "0.1.0" +name = "gitcomet" +version = "0.1.7" dependencies = [ "clap", "embed-resource", @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "gitcomet-core" -version = "0.1.0" +version = "0.1.7" dependencies = [ "regex", "rustc-hash 2.1.1", @@ -2267,14 +2267,14 @@ dependencies = [ [[package]] name = "gitcomet-git" -version = "0.1.0" +version = "0.1.7" dependencies = [ "gitcomet-core", ] [[package]] name = "gitcomet-git-gix" -version = "0.1.0" +version = "0.1.7" dependencies = [ "gitcomet-core", "gix", @@ -2285,7 +2285,7 @@ dependencies = [ [[package]] name = "gitcomet-state" -version = "0.1.0" +version = "0.1.7" dependencies = [ "gitcomet-core", "gix", @@ -2299,14 +2299,14 @@ dependencies = [ [[package]] name = "gitcomet-ui" -version = "0.1.0" +version = "0.1.7" dependencies = [ "gitcomet-core", ] [[package]] name = "gitcomet-ui-gpui" -version = "0.1.0" +version = "0.1.7" dependencies = [ "criterion", "gitcomet-core", @@ -2399,7 +2399,7 @@ dependencies = [ "bstr", "gix-date", "gix-error", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2501,7 +2501,7 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "unicode-bom", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2760,7 +2760,7 @@ dependencies = [ "itoa", "smallvec", "thiserror 2.0.18", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2859,7 +2859,7 @@ dependencies = [ "maybe-async", "nonempty", "thiserror 2.0.18", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -2891,7 +2891,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror 2.0.18", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -5314,7 +5314,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.4+spec-1.1.0", + "toml_edit 0.25.5+spec-1.1.0", ] [[package]] @@ -7044,7 +7044,7 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.15", ] [[package]] @@ -7067,9 +7067,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.0+spec-1.1.0" +version = "1.0.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" dependencies = [ "serde_core", ] @@ -7085,28 +7085,28 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", ] [[package]] name = "toml_edit" -version = "0.25.4+spec-1.1.0" +version = "0.25.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" dependencies = [ "indexmap", - "toml_datetime 1.0.0+spec-1.1.0", + "toml_datetime 1.0.1+spec-1.1.0", "toml_parser", - "winnow", + "winnow 1.0.0", ] [[package]] name = "toml_parser" -version = "1.0.9+spec-1.1.0" +version = "1.0.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" dependencies = [ - "winnow", + "winnow 1.0.0", ] [[package]] @@ -7117,9 +7117,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.0.7+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" [[package]] name = "tower" @@ -8426,6 +8426,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.55.0" @@ -8735,7 +8744,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow", + "winnow 0.7.15", "zbus_macros", "zbus_names", "zvariant", @@ -8763,7 +8772,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "winnow", + "winnow 0.7.15", "zvariant", ] @@ -9039,7 +9048,7 @@ dependencies = [ "enumflags2", "serde", "url", - "winnow", + "winnow 0.7.15", "zvariant_derive", "zvariant_utils", ] @@ -9067,5 +9076,5 @@ dependencies = [ "quote", "serde", "syn 2.0.117", - "winnow", + "winnow 0.7.15", ] diff --git a/Cargo.toml b/Cargo.toml index 482d9fb..5d65900 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "3" members = [ - "crates/gitcomet-app", + "crates/gitcomet", "crates/gitcomet-core", "crates/gitcomet-git", "crates/gitcomet-git-gix", @@ -11,7 +11,7 @@ members = [ ] default-members = [ - "crates/gitcomet-app", + "crates/gitcomet", "crates/gitcomet-core", "crates/gitcomet-git", "crates/gitcomet-git-gix", @@ -21,6 +21,7 @@ default-members = [ ] [workspace.package] +version = "0.1.7" edition = "2024" license = "AGPL-3.0-only" authors = ["AutoExplore Oy "] @@ -82,7 +83,7 @@ opt-level = 1 split-debuginfo = "packed" [profile.release] -lto = "thin" +lto = "fat" codegen-units = 1 strip = "symbols" diff --git a/README.md b/README.md index 6a10b99..35ec3bd 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ [![Discord](https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/2ufDGP8RnA) [![Website](https://img.shields.io/badge/Website-gitcomet.dev-0A66C2?logo=googlechrome&logoColor=white)](https://gitcomet.dev) [![AutoExplore](https://img.shields.io/badge/AutoExplore-autoexplore.ai-0B7A75?logo=safari&logoColor=white)](https://autoexplore.ai) +[![license](https://img.shields.io/github/license/Auto-Explore/gitcomet.svg)](LICENSE) +[![latest](https://img.shields.io/github/v/release/Auto-Explore/gitcomet.svg)](https://github.com/Auto-Explore/gitcomet/releases/latest) +[![downloads](https://img.shields.io/github/downloads/Auto-Explore/gitcomet/total)](https://github.com/Auto-Explore/gitcomet/releases) -**Speed is a feature.** +**Fastest Open Source Git GUI** GitComet is built for teams that want fast Git operations with local-first privacy, familiar workflows, and open source freedom. @@ -19,21 +22,25 @@ Download the latest prebuilt binaries/installers from [GitHub Releases](https:// #### Homebrew -install from tap: +install app from tap (recommended): ```bash brew tap auto-explore/gitcomet -brew install gitcomet +brew install --cask gitcomet ``` -#### Flatpak / Flathub -GitHub releases also publish: +**MacOS Troubleshooting:** If you downloaded the app manually and macOS reports that GitComet *"is damaged and can't be opened"*, this is due to Apple's Gatekeeper quarantine for apps downloaded outside the App Store. To resolve this, run the following command in your terminal to remove the quarantine attribute: -- a Linux Flatpak bundle: `gitcomet-v-linux-x86_64.flatpak` -- a source tarball for Flathub: `gitcomet-v-source.tar.gz` -- the rendered Flathub manifest: `dev.gitcomet.GitComet.yaml` -- the pinned Cargo source list: `cargo-sources.json` +```bash +xattr -d com.apple.quarantine /Applications/GitComet.app +``` + +optional CLI install: + +```bash +brew install gitcomet-cli +``` ### Fast, Free, Familiar @@ -80,13 +87,13 @@ Measured on Linux 6.19.6-zen (x64), Ryzen 5950x, 128GB DDR4. Detailed test steps - Code test coverage workflows - GitHub and Azure DevOps integrations - Priority improvements during early access -- Join waitlist: [gitcomet.com/#pricing](https://gitcomet.com/#pricing) +- Join waitlist: [gitcomet.dev/#pricing](https://gitcomet.dev/#pricing) ### Build from source ```bash -cargo build -p gitcomet-app --features ui-gpui,gix -cargo run -p gitcomet-app --features ui-gpui,gix -- /path/to/repo +cargo build -p gitcomet --features ui-gpui,gix +cargo run -p gitcomet --features ui-gpui,gix -- /path/to/repo ``` ### Contributing @@ -101,10 +108,10 @@ GitComet can be used as a standalone diff and merge tool invoked by `git difftoo ```bash # Configure Git globally to use GitComet for both difftool + mergetool -gitcomet-app setup +gitcomet setup # Remove GitComet integration safely -gitcomet-app uninstall +gitcomet uninstall ``` - Use `--local` to target only the current repository instead of global config. @@ -119,7 +126,7 @@ This setup registers both headless and GUI variants with `guiDefault=auto`, so G Built-in `setup` writes these Git config entries: ```bash -GITCOMET_BIN="/absolute/path/to/gitcomet-app" +GITCOMET_BIN="/absolute/path/to/gitcomet" # Headless tool: algorithm-only merge/diff for CI, scripts, and no-display environments git config --global merge.tool gitcomet @@ -162,7 +169,7 @@ Built-in `uninstall` restores those backups only when the key still has the setu **Difftool:** ```bash -gitcomet-app difftool --local --remote [--path ] [--label-left