diff --git a/.github/workflows/build-release-artifacts.yml b/.github/workflows/build-release-artifacts.yml index b85056c..0069093 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 --release --locked --features ui-gpui,gix --bins + cargo build -p gitcomet-app --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.exe dist\portable\gitcomet.exe -Force + Copy-Item target\release\gitcomet-app.exe dist\portable\gitcomet-app.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\wix\main.wxs")) { - cargo wix init --package gitcomet + if (!(Test-Path "crates\gitcomet-app\wix\main.wxs")) { + cargo wix init --package gitcomet-app } $msiName = "gitcomet-v${env:VERSION}-windows-x86_64.msi" - cargo wix --package gitcomet --profile release --nocapture --no-build --output "dist\$msiName" + cargo wix --package gitcomet-app --profile release --nocapture --no-build --output "dist\$msiName" - name: Upload Windows artifacts uses: actions/upload-artifact@v7 @@ -199,6 +199,7 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ + appstream \ desktop-file-utils \ pkg-config \ libxcb1-dev \ @@ -212,22 +213,22 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build release binary - run: cargo build -p gitcomet --release --locked --features ui-gpui,gix + run: cargo build -p gitcomet-app --release --locked --features ui-gpui,gix - name: Normalize desktop entry metadata run: | set -euo pipefail mkdir -p dist - # 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 + 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 - 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 "dist/stage/${root}/gitcomet" + install -m755 target/release/gitcomet-app "dist/stage/${root}/gitcomet-app" 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" @@ -245,9 +246,9 @@ jobs: mkdir -p "${pkg_root}/usr/share/applications" mkdir -p "${pkg_root}/usr/share/icons/hicolor/512x512/apps" - 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" + 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" { echo "Package: gitcomet" @@ -267,15 +268,15 @@ jobs: set -euo pipefail appdir="dist/AppDir" mkdir -p "${appdir}/usr/bin" - 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" + 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" { echo '#!/usr/bin/env bash' echo 'set -euo pipefail' echo 'HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"' - echo 'exec "${HERE}/usr/bin/gitcomet" "$@"' + echo 'exec "${HERE}/usr/bin/gitcomet-app" "$@"' } > "${appdir}/AppRun" chmod +x "${appdir}/AppRun" @@ -298,6 +299,45 @@ 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 }} @@ -350,7 +390,7 @@ jobs: name: Attach assets and checksums to GitHub release runs-on: ubuntu-latest timeout-minutes: 30 - needs: [prepare, build_windows, build_linux, build_macos] + needs: [prepare, build_windows, build_linux, build_flatpak, build_macos] env: TAG: ${{ needs.prepare.outputs.tag }} VERSION: ${{ needs.prepare.outputs.version }} @@ -359,7 +399,7 @@ jobs: steps: - uses: actions/checkout@v6 with: - ref: ${{ needs.prepare.outputs.tag }} + ref: ${{ env.TAG }} fetch-depth: 0 - uses: actions/download-artifact@v8 @@ -372,6 +412,11 @@ 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-* @@ -384,6 +429,7 @@ 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 @@ -392,22 +438,40 @@ jobs: fi ls -lah dist/release - - name: Generate Homebrew cask and formula + - 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 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}" ] || [ ! -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." + 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." exit 1 fi @@ -417,13 +481,6 @@ 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 c4d8044..b28771f 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_FEATURES --test mergetool_git_integration gui_default --verbose - cargo test -p gitcomet $APP_FEATURES --test difftool_git_integration gui_default --verbose + 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 - 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_FEATURES --release --locked - codesign --verify --verbose target/release/gitcomet || true - spctl --assess --type execute --verbose target/release/gitcomet || true + 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 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 --no-default-features --features gix --test standalone_tool_mode_integration help_flag_exits_zero -- --exact + run: cargo test -p gitcomet-app --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 21a01d0..51e3342 100644 --- a/.github/workflows/deploy-apt-repo.yml +++ b/.github/workflows/deploy-apt-repo.yml @@ -21,6 +21,10 @@ on: required: false type: string default: "" + repo_base_url: + required: false + type: string + default: "" distribution: required: false type: string @@ -44,7 +48,7 @@ on: repo_description: required: false type: string - default: "GitComet APT repository." + default: "GitComet APT repository" container_public_access: required: false type: string @@ -71,27 +75,32 @@ on: required: false type: string storage_account: - description: "Azure Storage account name. Defaults to repository variable APT_STORAGE_ACCOUNT when omitted." + description: "Azure Storage account name" required: false default: "" type: string storage_container: - description: "Azure Blob container that holds the APT repository. Defaults to APT_STORAGE_CONTAINER when omitted." + description: "Azure Blob container that holds the APT repository" required: false default: "apt" type: string storage_prefix: - description: "Optional blob prefix within the container. Defaults to APT_STORAGE_PREFIX when omitted." + 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" required: false default: "" type: string distribution: - description: "APT suite/codename. Defaults to APT_REPO_DISTRIBUTION when omitted." + description: "APT suite/codename" required: false default: "stable" type: string component: - description: "APT component. Defaults to APT_REPO_COMPONENT when omitted." + description: "APT component" required: false default: "main" type: string @@ -101,22 +110,22 @@ on: default: "amd64" type: string repo_origin: - description: "Origin metadata in Release file. Defaults to APT_REPO_ORIGIN when omitted." + description: "Origin metadata in Release file" required: false default: "GitComet" type: string repo_label: - description: "Label metadata in Release file. Defaults to APT_REPO_LABEL when omitted." + description: "Label metadata in Release file" required: false default: "GitComet" type: string repo_description: - description: "Description metadata in Release file. Defaults to APT_REPO_DESCRIPTION when omitted." + description: "Description metadata in Release file" 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. Defaults to APT_STORAGE_PUBLIC_ACCESS when omitted." + description: "Public access used only when the blob container must be created" required: false default: "blob" type: choice @@ -156,33 +165,26 @@ 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 }} - VAR_STORAGE_PREFIX: ${{ vars.APT_STORAGE_PREFIX }} + INPUT_REPO_BASE_URL: ${{ inputs.repo_base_url }} + DISPATCH_REPO_BASE_URL: ${{ github.event.inputs.repo_base_url }} 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: | @@ -190,16 +192,17 @@ jobs: tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" - 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}}}" + 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}}" architecture="${INPUT_ARCHITECTURE:-${DISPATCH_ARCHITECTURE:-amd64}}" - 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}}}" + 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}}" dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" tag="$(echo "$tag" | tr -d '[:space:]')" @@ -207,6 +210,7 @@ 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:]')" @@ -270,8 +274,7 @@ jobs: exit 1 fi - repo_base_url="" - if [ -n "$storage_account" ] && [ "$storage_container" != '$web' ]; then + if [ -z "$repo_base_url" ] && [ -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}" @@ -362,7 +365,7 @@ jobs: echo "Key-Type: RSA" echo "Key-Length: 3072" echo "Name-Real: GitComet APT Dry Run" - echo "Name-Email: ci@autoexplore.ai" + echo "Name-Email: ci@example.invalid" echo "Expire-Date: 0" echo "%no-protection" echo "%commit" @@ -416,16 +419,16 @@ jobs: blob_prefix="${blob_prefix}/" fi - 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" + 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 + ) if [ "${#existing_blobs[@]}" -eq 0 ]; then exit 0 @@ -469,7 +472,6 @@ 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 @@ -493,9 +495,6 @@ 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 @@ -506,25 +505,15 @@ jobs: blob_name="${STORAGE_PREFIX}/${rel_path}" fi - 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 + 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) @@ -560,3 +549,28 @@ 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 deleted file mode 100644 index 2fa60c5..0000000 --- a/.github/workflows/deploy-aur.yml +++ /dev/null @@ -1,268 +0,0 @@ -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 new file mode 100644 index 0000000..54b2dad --- /dev/null +++ b/.github/workflows/deploy-flathub.yml @@ -0,0 +1,317 @@ +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 bc37668..a727a7b 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). Defaults to HOMEBREW_TAP_REPO when omitted." + description: "Target tap repository in OWNER/REPO form (e.g. Auto-Explore/homebrew-gitcomet)" required: false default: "" type: string tap_branch: - description: "Target branch in tap repo. Defaults to HOMEBREW_TAP_BRANCH when omitted." + description: "Target branch in tap repo" required: false default: "main" type: string dry_run: - description: "Validate and print cask/formula without pushing to tap" + description: "Validate and print formula without pushing to tap" required: true default: false type: boolean @@ -59,7 +59,7 @@ concurrency: jobs: deploy: - name: Publish Homebrew cask and formula + name: Publish formula to Homebrew tap runs-on: ubuntu-latest timeout-minutes: 20 env: @@ -74,10 +74,8 @@ 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 }} @@ -86,8 +84,8 @@ jobs: tag="${INPUT_TAG:-${DISPATCH_TAG:-}}" version="${INPUT_VERSION:-${DISPATCH_VERSION:-}}" - tap_repo="${INPUT_TAP_REPO:-${DISPATCH_TAP_REPO:-${VAR_TAP_REPO:-}}}" - tap_branch="${INPUT_TAP_BRANCH:-${DISPATCH_TAP_BRANCH:-${VAR_TAP_BRANCH:-main}}}" + tap_repo="${INPUT_TAP_REPO:-${DISPATCH_TAP_REPO:-}}" + tap_branch="${INPUT_TAP_BRANCH:-${DISPATCH_TAP_BRANCH:-main}}" dry_run="${INPUT_DRY_RUN:-${DISPATCH_DRY_RUN:-false}}" tag="$(echo "$tag" | tr -d '[:space:]')" @@ -140,7 +138,7 @@ jobs: echo "tap_branch=$tap_branch" >> "$GITHUB_OUTPUT" echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT" - - name: Download cask and formula from release assets + - name: Download formula from release assets run: | set -euo pipefail mkdir -p dist @@ -148,17 +146,12 @@ 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 cask and formula syntax - run: | - set -euo pipefail - ruby -c dist/gitcomet.rb - ruby -c dist/gitcomet-cli.rb + - name: Validate formula syntax + run: ruby -c dist/gitcomet.rb - name: Emit dry-run summary if: ${{ steps.norm.outputs.dry_run == 'true' }} @@ -171,18 +164,13 @@ jobs: echo "- Target tap: \`${{ steps.norm.outputs.tap_repo }}\`" echo "- Target branch: \`${{ steps.norm.outputs.tap_branch }}\`" echo "" - echo "Cask preview (Casks/gitcomet.rb):" + echo "Formula preview:" 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 cask and formula to tap repository + - name: Publish formula to tap repository if: ${{ steps.norm.outputs.dry_run != 'true' }} env: TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} @@ -199,24 +187,19 @@ jobs: rm -rf tap-repo git clone "https://x-access-token:${TAP_TOKEN}@github.com/${TAP_REPO}.git" tap-repo - 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 + mkdir -p tap-repo/Formula + cp dist/gitcomet.rb tap-repo/Formula/gitcomet.rb pushd tap-repo >/dev/null - 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." + if git diff --quiet -- Formula/gitcomet.rb; then + echo "No 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 f1920da..3456857 100644 --- a/.github/workflows/deployment-ci.yml +++ b/.github/workflows/deployment-ci.yml @@ -5,27 +5,33 @@ on: branches: ["main"] paths: - ".github/workflows/build-release-artifacts.yml" - - ".github/workflows/deploy-aur.yml" + - ".github/workflows/deployment-ci.yml" - ".github/workflows/deploy-homebrew-tap.yml" + - ".github/workflows/deploy-flathub.yml" - ".github/workflows/deploy-apt-repo.yml" - ".github/workflows/release-manual-main.yml" - - "scripts/update-aur.sh" + - "assets/linux/dev.gitcomet.GitComet.desktop" + - "flatpak/**" + - "scripts/generate-flatpak-cargo-sources.py" + - "scripts/render-flathub-manifest.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/deploy-aur.yml" + - ".github/workflows/deployment-ci.yml" - ".github/workflows/deploy-homebrew-tap.yml" + - ".github/workflows/deploy-flathub.yml" - ".github/workflows/deploy-apt-repo.yml" - ".github/workflows/release-manual-main.yml" - - "scripts/update-aur.sh" + - "assets/linux/dev.gitcomet.GitComet.desktop" + - "flatpak/**" + - "scripts/generate-flatpak-cargo-sources.py" + - "scripts/render-flathub-manifest.sh" - "scripts/package-macos.sh" - "scripts/generate-homebrew-formula.sh" - - "scripts/generate-homebrew-cask.sh" - "scripts/build-apt-repo.sh" workflow_dispatch: @@ -45,58 +51,29 @@ 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-aur.yml + .github/workflows/deploy-flathub.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: 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 + - name: Generate 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" \ @@ -104,137 +81,11 @@ 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 - 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 + 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 apt-repo-generation-smoke: name: Validate APT repo generation script @@ -254,14 +105,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" - chmod +x "${pkg_root}/usr/bin/gitcomet" + printf '%s\n' '#!/usr/bin/env bash' 'echo "GitComet CI"' > "${pkg_root}/usr/bin/gitcomet-app" + chmod +x "${pkg_root}/usr/bin/gitcomet-app" { 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" @@ -279,7 +130,7 @@ jobs: echo "Key-Type: RSA" echo "Key-Length: 3072" echo "Name-Real: GitComet CI" - echo "Name-Email: ci@autoexplore.ai" + echo "Name-Email: ci@example.invalid" echo "Expire-Date: 0" echo "%no-protection" echo "%commit" @@ -292,7 +143,6 @@ 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 \ @@ -305,7 +155,6 @@ 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 @@ -328,38 +177,6 @@ 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-app" --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 29e7f22..7631800 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' Cargo.toml | head -n1)" + crate_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' crates/gitcomet-app/Cargo.toml | head -n1)" wanted="${{ steps.norm.outputs.version }}" if [ "$crate_version" != "$wanted" ]; then - echo "::error title=Version mismatch::Cargo.toml version '$crate_version' does not match requested release '$wanted'." + echo "::error title=Version mismatch::crates/gitcomet-app/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 packages + name: Deploy Homebrew tap formula 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,16 +187,17 @@ jobs: dry_run: false secrets: inherit - deploy_aur: - name: Deploy AUR mirror metadata + deploy_flathub: + name: Deploy Flathub manifest needs: [validate, build_and_upload, publish_release] if: ${{ fromJSON(needs.validate.outputs.draft) == false && needs.build_and_upload.result == 'success' }} - uses: ./.github/workflows/deploy-aur.yml + uses: ./.github/workflows/deploy-flathub.yml with: tag: ${{ needs.validate.outputs.tag }} version: ${{ needs.validate.outputs.version }} - aur_repo: ${{ vars.AUR_GITHUB_REPO }} - aur_branch: ${{ vars.AUR_GITHUB_BRANCH }} + flathub_repo: ${{ vars.FLATHUB_REPO }} + flathub_branch: ${{ vars.FLATHUB_BRANCH }} + mode: ${{ vars.FLATHUB_MODE }} dry_run: false secrets: inherit @@ -211,6 +212,7 @@ 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 9b719c1..b02a407 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,7 +18,7 @@ concurrency: env: CARGO_TERM_COLOR: always - # Build gitcomet in headless mode (no GPUI system deps required). + # Build gitcomet-app 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_FEATURES -- -D warnings + run: cargo clippy -p gitcomet-app $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_FEATURES --verbose + run: cargo build -p gitcomet-app $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 binary (headless) - run: cargo build -p gitcomet $APP_FEATURES + - name: Build gitcomet-app binary (headless) + run: cargo build -p gitcomet-app $APP_FEATURES - name: Git mergetool E2E (Phase 4A — t7610 parity) - run: cargo test -p gitcomet $APP_FEATURES --test mergetool_git_integration --verbose + run: cargo test -p gitcomet-app $APP_FEATURES --test mergetool_git_integration --verbose - name: Git difftool E2E (Phase 4B — t7800 parity) - run: cargo test -p gitcomet $APP_FEATURES --test difftool_git_integration --verbose + run: cargo test -p gitcomet-app $APP_FEATURES --test difftool_git_integration --verbose - name: Standalone tool-mode E2E (exit codes + validation) - run: cargo test -p gitcomet $APP_FEATURES --test standalone_tool_mode_integration --verbose + run: cargo test -p gitcomet-app $APP_FEATURES --test standalone_tool_mode_integration --verbose - name: Mergetool/difftool runtime unit tests (bin target) - run: cargo test -p gitcomet $APP_FEATURES --bin gitcomet --verbose + run: cargo test -p gitcomet-app $APP_FEATURES --bin gitcomet-app --verbose # Backend integration — mergetool launcher, status, conflict checkout backend-integration: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32937b3..67cadd2 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`: binary entrypoint, CLI (clap), difftool/mergetool/setup/uninstall modes. +- `crates/gitcomet-app`: 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 --features ui,gix +cargo build -p gitcomet-app --features ui,gix ``` To also compile the gpui-based UI crate: ```bash -cargo build -p gitcomet --features ui-gpui,gix +cargo build -p gitcomet-app --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 --features ui-gpui,gix -- /path/to/repo +cargo run -p gitcomet-app --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 +- Linux: tar.gz + AppImage + .deb + Flatpak bundle - macOS: DMG + tar.gz for `arm64` and `x86_64` -- 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) +- 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 deployment -To push `Casks/gitcomet.rb` and `Formula/gitcomet-cli.rb` into a Homebrew tap repo automatically on release: +To push `Formula/gitcomet.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,27 +104,28 @@ This release flow will: - build and upload release artifacts - publish the GitHub release -- call `.github/workflows/deploy-homebrew-tap.yml` to update `Casks/gitcomet.rb` and `Formula/gitcomet-cli.rb` in the tap repo +- call `.github/workflows/deploy-homebrew-tap.yml` to update `Formula/gitcomet.rb` in the tap repo You can also run `.github/workflows/deploy-homebrew-tap.yml` manually for backfills or dry-runs. -### AUR mirror deployment +### Flathub deployment -To push `PKGBUILD` and `.SRCINFO` into a GitHub-hosted AUR mirror repo automatically on release: +The app ID used for Flatpak/Flathub packaging is `dev.gitcomet.GitComet`. -1. Create the target repository (default expected name: `OWNER/aur-gitcomet`). +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`. 2. In this repo, configure: - - 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`). + - 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). 3. Run `.github/workflows/release-manual-main.yml` with `draft=false`. This release flow will: -- 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 +- 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` -You can also run `.github/workflows/deploy-aur.yml` manually for backfills or dry-runs. +You can also run `.github/workflows/deploy-flathub.yml` manually for backfills or dry-runs. diff --git a/Cargo.lock b/Cargo.lock index faaf34e..f0d1ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2239,8 +2239,8 @@ dependencies = [ ] [[package]] -name = "gitcomet" -version = "0.1.7" +name = "gitcomet-app" +version = "0.1.0" dependencies = [ "clap", "embed-resource", @@ -2256,7 +2256,7 @@ dependencies = [ [[package]] name = "gitcomet-core" -version = "0.1.7" +version = "0.1.0" dependencies = [ "regex", "rustc-hash 2.1.1", @@ -2267,14 +2267,14 @@ dependencies = [ [[package]] name = "gitcomet-git" -version = "0.1.7" +version = "0.1.0" dependencies = [ "gitcomet-core", ] [[package]] name = "gitcomet-git-gix" -version = "0.1.7" +version = "0.1.0" dependencies = [ "gitcomet-core", "gix", @@ -2285,7 +2285,7 @@ dependencies = [ [[package]] name = "gitcomet-state" -version = "0.1.7" +version = "0.1.0" dependencies = [ "gitcomet-core", "gix", @@ -2299,14 +2299,14 @@ dependencies = [ [[package]] name = "gitcomet-ui" -version = "0.1.7" +version = "0.1.0" dependencies = [ "gitcomet-core", ] [[package]] name = "gitcomet-ui-gpui" -version = "0.1.7" +version = "0.1.0" dependencies = [ "criterion", "gitcomet-core", @@ -2399,7 +2399,7 @@ dependencies = [ "bstr", "gix-date", "gix-error", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -2501,7 +2501,7 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "unicode-bom", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -2760,7 +2760,7 @@ dependencies = [ "itoa", "smallvec", "thiserror 2.0.18", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -2859,7 +2859,7 @@ dependencies = [ "maybe-async", "nonempty", "thiserror 2.0.18", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -2891,7 +2891,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror 2.0.18", - "winnow 0.7.15", + "winnow", ] [[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.5+spec-1.1.0", + "toml_edit 0.25.4+spec-1.1.0", ] [[package]] @@ -7044,7 +7044,7 @@ dependencies = [ "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.15", + "winnow", ] [[package]] @@ -7067,9 +7067,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.0.1+spec-1.1.0" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b320e741db58cac564e26c607d3cc1fdc4a88fd36c879568c07856ed83ff3e9" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ "serde_core", ] @@ -7085,28 +7085,28 @@ dependencies = [ "serde_spanned 0.6.9", "toml_datetime 0.6.11", "toml_write", - "winnow 0.7.15", + "winnow", ] [[package]] name = "toml_edit" -version = "0.25.5+spec-1.1.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca1a40644a28bce036923f6a431df0b34236949d111cc07cb6dca830c9ef2e1" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime 1.0.1+spec-1.1.0", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow", ] [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ - "winnow 1.0.0", + "winnow", ] [[package]] @@ -7117,9 +7117,9 @@ checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] name = "toml_writer" -version = "1.0.7+spec-1.1.0" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17aaa1c6e3dc22b1da4b6bba97d066e354c7945cac2f7852d4e4e7ca7a6b56d" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -8426,15 +8426,6 @@ 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" @@ -8744,7 +8735,7 @@ dependencies = [ "uds_windows", "uuid", "windows-sys 0.61.2", - "winnow 0.7.15", + "winnow", "zbus_macros", "zbus_names", "zvariant", @@ -8772,7 +8763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "winnow 0.7.15", + "winnow", "zvariant", ] @@ -9048,7 +9039,7 @@ dependencies = [ "enumflags2", "serde", "url", - "winnow 0.7.15", + "winnow", "zvariant_derive", "zvariant_utils", ] @@ -9076,5 +9067,5 @@ dependencies = [ "quote", "serde", "syn 2.0.117", - "winnow 0.7.15", + "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 5d65900..482d9fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "3" members = [ - "crates/gitcomet", + "crates/gitcomet-app", "crates/gitcomet-core", "crates/gitcomet-git", "crates/gitcomet-git-gix", @@ -11,7 +11,7 @@ members = [ ] default-members = [ - "crates/gitcomet", + "crates/gitcomet-app", "crates/gitcomet-core", "crates/gitcomet-git", "crates/gitcomet-git-gix", @@ -21,7 +21,6 @@ default-members = [ ] [workspace.package] -version = "0.1.7" edition = "2024" license = "AGPL-3.0-only" authors = ["AutoExplore Oy "] @@ -83,7 +82,7 @@ opt-level = 1 split-debuginfo = "packed" [profile.release] -lto = "fat" +lto = "thin" codegen-units = 1 strip = "symbols" diff --git a/README.md b/README.md index 35ec3bd..6a10b99 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,8 @@ [![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) -**Fastest Open Source Git GUI** +**Speed is a feature.** GitComet is built for teams that want fast Git operations with local-first privacy, familiar workflows, and open source freedom. @@ -22,25 +19,21 @@ Download the latest prebuilt binaries/installers from [GitHub Releases](https:// #### Homebrew -install app from tap (recommended): +install from tap: ```bash brew tap auto-explore/gitcomet -brew install --cask gitcomet +brew install gitcomet ``` +#### Flatpak / Flathub -**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: +GitHub releases also publish: -```bash -xattr -d com.apple.quarantine /Applications/GitComet.app -``` - -optional CLI install: - -```bash -brew install gitcomet-cli -``` +- 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` ### Fast, Free, Familiar @@ -87,13 +80,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.dev/#pricing](https://gitcomet.dev/#pricing) +- Join waitlist: [gitcomet.com/#pricing](https://gitcomet.com/#pricing) ### Build from source ```bash -cargo build -p gitcomet --features ui-gpui,gix -cargo run -p gitcomet --features ui-gpui,gix -- /path/to/repo +cargo build -p gitcomet-app --features ui-gpui,gix +cargo run -p gitcomet-app --features ui-gpui,gix -- /path/to/repo ``` ### Contributing @@ -108,10 +101,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 setup +gitcomet-app setup # Remove GitComet integration safely -gitcomet uninstall +gitcomet-app uninstall ``` - Use `--local` to target only the current repository instead of global config. @@ -126,7 +119,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" +GITCOMET_BIN="/absolute/path/to/gitcomet-app" # Headless tool: algorithm-only merge/diff for CI, scripts, and no-display environments git config --global merge.tool gitcomet @@ -169,7 +162,7 @@ Built-in `uninstall` restores those backups only when the key still has the setu **Difftool:** ```bash -gitcomet difftool --local --remote [--path ] [--label-left