Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Sampo Kivistö
00d1420e30
Merge branch 'main' of github.com:Auto-Explore/GitComet into feat/flatpak 2026-03-17 14:47:53 +02:00
Sampo Kivistö
916c19b2cc
Merge branch 'main' of github.com:Auto-Explore/GitComet into feat/flatpak 2026-03-17 14:28:38 +02:00
Sampo Kivistö
a0fe809d3a
Merge branch 'main' of github.com:Auto-Explore/GitComet into feat/flatpak 2026-03-14 20:37:51 +02:00
Sampo Kivistö
dd0d5efda0
flatpak wip 2026-03-13 16:54:30 +02:00
30 changed files with 12431 additions and 53 deletions

View file

@ -199,6 +199,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y \
appstream \
desktop-file-utils \
pkg-config \
libxcb1-dev \
@ -218,9 +219,9 @@ jobs:
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: |
@ -246,8 +247,8 @@ jobs:
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/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 -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"
@ -268,8 +269,8 @@ jobs:
appdir="dist/AppDir"
mkdir -p "${appdir}/usr/bin"
install -m755 target/release/gitcomet-app "${appdir}/usr/bin/gitcomet-app"
install -m644 dist/gitcomet.desktop "${appdir}/gitcomet.desktop"
install -m644 assets/gitcomet-512.png "${appdir}/gitcomet.png"
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'
@ -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,13 +390,18 @@ 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 }}
RELEASE_ID: ${{ needs.prepare.outputs.release_id }}
ENABLE_KEYLESS_SIGNING: ${{ vars.ENABLE_KEYLESS_SIGNING }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ env.TAG }}
fetch-depth: 0
- uses: actions/download-artifact@v8
with:
name: windows-release-artifacts
@ -367,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-*
@ -379,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
@ -387,6 +438,28 @@ 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
run: |
set -euo pipefail

317
.github/workflows/deploy-flathub.yml vendored Normal file
View file

@ -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<version>."
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"

View file

@ -5,9 +5,15 @@ on:
branches: ["main"]
paths:
- ".github/workflows/build-release-artifacts.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"
- "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/build-apt-repo.sh"
@ -15,9 +21,15 @@ on:
branches: ["main"]
paths:
- ".github/workflows/build-release-artifacts.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"
- "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/build-apt-repo.sh"
@ -42,11 +54,13 @@ jobs:
bash -n scripts/package-macos.sh
bash -n scripts/generate-homebrew-formula.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-homebrew-tap.yml
.github/workflows/deploy-apt-repo.yml
.github/workflows/deployment-ci.yml
@ -218,3 +232,41 @@ jobs:
"${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

View file

@ -187,6 +187,20 @@ jobs:
dry_run: false
secrets: inherit
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-flathub.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 }}
dry_run: false
secrets: inherit
deploy_apt_repo:
name: Deploy Azure APT repository
needs: [validate, build_and_upload, publish_release]

View file

@ -84,8 +84,9 @@ 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`
- Flathub assets: `gitcomet-v<VERSION>-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
@ -106,3 +107,25 @@ This release flow will:
- 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.
### Flathub deployment
The app ID used for Flatpak/Flathub packaging is `dev.gitcomet.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 `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:
- 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-flathub.yml` manually for backfills or dry-runs.

View file

@ -26,6 +26,15 @@ brew tap auto-explore/gitcomet
brew install gitcomet
```
#### Flatpak / Flathub
GitHub releases also publish:
- a Linux Flatpak bundle: `gitcomet-v<VERSION>-linux-x86_64.flatpak`
- a source tarball for Flathub: `gitcomet-v<VERSION>-source.tar.gz`
- the rendered Flathub manifest: `dev.gitcomet.GitComet.yaml`
- the pinned Cargo source list: `cargo-sources.json`
### Fast, Free, Familiar
- **Fast**: Built end-to-end in Rust for speed and efficiency using [smol](https://github.com/smol-rs/smol), [gix](https://github.com/GitoxideLabs/gitoxide), and [gpui](https://www.gpui.rs/).

View file

@ -3,7 +3,8 @@ Type=Application
Name=GitComet
Comment=Git UI built with GPUI
Exec=gitcomet-app
Icon=gitcomet
StartupWMClass=gitcomet
Icon=dev.gitcomet.GitComet
StartupWMClass=dev.gitcomet.GitComet
Terminal=false
Categories=Development;RevisionControl;
Keywords=git;diff;merge;history;revision-control;

View file

@ -1,9 +1,10 @@
use crate::cli::{DifftoolConfig, DifftoolInputKind, classify_difftool_input, exit_code};
use gitcomet_core::platform::host_tempdir;
use rustc_hash::FxHashSet as HashSet;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::{Builder, TempDir};
use tempfile::TempDir;
/// Format a `"Failed to {op} {path}: {err}"` message concisely.
macro_rules! io_err {
@ -255,9 +256,7 @@ fn prepare_diff_inputs(config: &DifftoolConfig) -> Result<PreparedDiffInputs, St
});
}
let tempdir = Builder::new()
.prefix("gitcomet-difftool-")
.tempdir()
let tempdir = host_tempdir("gitcomet-difftool-")
.map_err(|e| io_err!("create temporary directory staging area", e))?;
let staged_local = tempdir.path().join("left");

View file

@ -20,6 +20,9 @@ pub(crate) fn hex_encode(bytes: &[u8]) -> String {
}
use std::io::{self, Write};
#[cfg(all(target_os = "macos", feature = "ui-gpui"))]
use gitcomet_core::platform::APP_ID;
#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
@ -374,7 +377,7 @@ fn ensure_macos_dev_app_bundle(
<key>CFBundleExecutable</key>
<string>gitcomet-app</string>
<key>CFBundleIdentifier</key>
<string>ai.autoexplore.gitcomet.dev</string>
<string>{app_id}</string>
<key>CFBundleIconFile</key>
<string>GitComet.icns</string>
<key>CFBundleInfoDictionaryVersion</key>
@ -394,6 +397,7 @@ fn ensure_macos_dev_app_bundle(
</dict>
</plist>
"#,
app_id = APP_ID,
version = env!("CARGO_PKG_VERSION")
);
std::fs::write(contents.join("Info.plist"), plist)

View file

@ -16,9 +16,7 @@ benchmarks = []
[dependencies]
regex = { workspace = true }
tempfile = { workspace = true }
rustc-hash = { workspace = true }
strsim = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }

View file

@ -9,5 +9,6 @@ pub mod file_diff;
pub mod merge;
pub mod merge_extraction;
pub mod mergetool_trace;
pub mod platform;
pub mod services;
pub mod text_utils;

View file

@ -0,0 +1,50 @@
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
pub const APP_ID: &str = "dev.gitcomet.GitComet";
pub const APP_DESKTOP_FILE_NAME: &str = "dev.gitcomet.GitComet.desktop";
pub const APP_ICON_NAME: &str = APP_ID;
pub fn is_flatpak_sandbox() -> bool {
cfg!(any(target_os = "linux", target_os = "freebsd")) && Path::new("/.flatpak-info").exists()
}
pub fn host_command<S: AsRef<OsStr>>(program: S) -> Command {
let program = program.as_ref();
if is_flatpak_sandbox() {
let mut command = Command::new("flatpak-spawn");
command.arg("--host").arg(program);
command
} else {
Command::new(program)
}
}
pub fn host_tempdir(prefix: &str) -> io::Result<tempfile::TempDir> {
let mut builder = tempfile::Builder::new();
builder.prefix(prefix);
if let Some(root) = host_visible_temp_root()? {
builder.tempdir_in(root)
} else {
builder.tempdir()
}
}
fn host_visible_temp_root() -> io::Result<Option<PathBuf>> {
if !is_flatpak_sandbox() {
return Ok(None);
}
let cache_home = env::var_os("XDG_CACHE_HOME")
.map(PathBuf::from)
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".cache")));
let root = cache_home
.unwrap_or_else(env::temp_dir)
.join("gitcomet/tmp");
fs::create_dir_all(&root)?;
Ok(Some(root))
}

View file

@ -1,6 +1,7 @@
use super::{GixRepo, conflict_stages::gix_index_stage_blob_bytes_optional};
use crate::util::{bytes_to_text_preserving_utf8, run_git_simple};
use gitcomet_core::error::{Error, ErrorKind};
use gitcomet_core::platform::{host_command, host_tempdir};
use gitcomet_core::services::{
CommandOutput, MergetoolResult, Result, validate_conflict_resolution_text,
};
@ -68,7 +69,7 @@ impl GixRepo {
// No custom command — try invoking the tool name directly with
// the standard argument convention used by many merge tools.
let tool_executable = tool_path.as_deref().unwrap_or(&tool_name);
Command::new(tool_executable)
host_command(tool_executable)
.arg(local_path)
.arg(base_path)
.arg(remote_path)
@ -183,14 +184,14 @@ fn env_has_display() -> bool {
#[cfg(all(windows, test))]
fn shell_command(custom_cmd: &str) -> Command {
let mut command = Command::new("cmd");
let mut command = host_command("cmd");
command.arg("/C").arg(custom_cmd);
command
}
#[cfg(not(windows))]
fn shell_command(custom_cmd: &str) -> Command {
let mut command = Command::new("sh");
let mut command = host_command("sh");
command.arg("-c").arg(custom_cmd);
command
}
@ -510,10 +511,8 @@ fn build_stage_paths(
let pid = std::process::id();
if write_to_temp {
let tmp_dir = tempfile::Builder::new()
.prefix("gitcomet-mergetool-")
.tempdir()
.map_err(|e| Error::new(ErrorKind::Io(e.kind())))?;
let tmp_dir =
host_tempdir("gitcomet-mergetool-").map_err(|e| Error::new(ErrorKind::Io(e.kind())))?;
let (tmp_dir_path, temp_dir_guard) = if keep_temporaries {
(tmp_dir.keep(), None)
} else {

View file

@ -5,6 +5,7 @@ use gitcomet_core::auth::{
};
use gitcomet_core::domain::{Commit, CommitId, LogPage};
use gitcomet_core::error::{Error, ErrorKind, GitFailure, GitFailureId};
use gitcomet_core::platform::host_tempdir;
use gitcomet_core::services::{CommandOutput, Result};
use std::fs;
use std::path::{Path, PathBuf};
@ -189,7 +190,7 @@ echo %GITCOMET_AUTH_SECRET%
}
fn create_askpass_script() -> Result<AskPassScript> {
let dir = tempfile::tempdir().map_err(io_err)?;
let dir = host_tempdir("gitcomet-askpass-").map_err(|e| Error::new(ErrorKind::Io(e.kind())))?;
#[cfg(windows)]
let script_name = "gitcomet-askpass.cmd";
#[cfg(not(windows))]

View file

@ -5,6 +5,7 @@ use gitcomet_core::auth::{
GitAuthKind, StagedGitAuth, take_staged_git_auth,
};
use gitcomet_core::error::{Error, ErrorKind};
use gitcomet_core::platform::host_tempdir;
use gitcomet_core::services::CommandOutput;
use std::fs;
use std::io::{BufRead as _, BufReader, Read as _};
@ -199,7 +200,7 @@ echo %GITCOMET_AUTH_SECRET%
}
fn create_askpass_script() -> Result<AskPassScript, Error> {
let dir = tempfile::tempdir().map_err(|e| Error::new(ErrorKind::Io(e.kind())))?;
let dir = host_tempdir("gitcomet-askpass-").map_err(|e| Error::new(ErrorKind::Io(e.kind())))?;
#[cfg(windows)]
let script_name = "gitcomet-askpass.cmd";
#[cfg(not(windows))]

View file

@ -4,6 +4,7 @@ use crate::view::{
FocusedMergetoolLabels, FocusedMergetoolViewConfig, GitCometView, GitCometViewConfig,
GitCometViewMode, StartupCrashReport,
};
use gitcomet_core::platform::APP_ID;
use gitcomet_core::services::GitBackend;
use gitcomet_state::session;
use gitcomet_state::store::AppStore;
@ -80,7 +81,7 @@ fn normal_launch_config(
) -> WindowLaunchConfig {
WindowLaunchConfig {
title: "GitComet".to_string(),
app_id: "gitcomet".to_string(),
app_id: APP_ID.to_string(),
view_config: GitCometViewConfig::normal(initial_path, startup_crash_report),
}
}
@ -91,7 +92,7 @@ fn focused_mergetool_launch_config(
) -> WindowLaunchConfig {
WindowLaunchConfig {
title: focused_mergetool_window_title(&config.conflicted_file_path),
app_id: "gitcomet-mergetool".to_string(),
app_id: APP_ID.to_string(),
view_config: GitCometViewConfig {
initial_path: Some(config.repo_path.clone()),
view_mode: GitCometViewMode::FocusedMergetool,
@ -309,7 +310,7 @@ mod tests {
};
let launch = focused_mergetool_launch_config(&config, None);
assert_eq!(launch.app_id, "gitcomet-mergetool");
assert_eq!(launch.app_id, APP_ID);
assert_eq!(launch.title, "GitComet - Mergetool (conflict.txt)");
assert_eq!(launch.view_config.initial_path, Some(config.repo_path));
assert_eq!(

View file

@ -6,6 +6,7 @@
use crate::assets::GitCometAssets;
use crate::launch_guard::run_with_panic_guard;
use crate::theme::{AppTheme, with_alpha};
use gitcomet_core::platform::APP_ID;
use gpui::prelude::*;
use gpui::{
App, Application, Bounds, FocusHandle, Focusable, FontWeight, KeyBinding, Render, ScrollHandle,
@ -291,7 +292,7 @@ pub fn run_focused_diff(config: FocusedDiffConfig) -> i32 {
appears_transparent: false,
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
app_id: Some("gitcomet-diff".to_string()),
app_id: Some(APP_ID.to_string()),
window_decorations: Some(WindowDecorations::Server),
is_movable: true,
is_resizable: true,

View file

@ -1,4 +1,6 @@
use super::*;
#[cfg(any(test, target_os = "linux", target_os = "freebsd"))]
use gitcomet_core::platform::{APP_DESKTOP_FILE_NAME, APP_ICON_NAME, is_flatpak_sandbox};
#[cfg(any(test, target_os = "linux", target_os = "freebsd"))]
fn desktop_entry_exec_path_arg(exe: &std::path::Path) -> Result<String, String> {
@ -29,9 +31,10 @@ fn desktop_entry_exec_path_arg(exe: &std::path::Path) -> Result<String, String>
fn should_auto_install_linux_desktop_integration(
no_desktop_install_flag_present: bool,
_xdg_current_desktop: Option<&str>,
is_flatpak: bool,
) -> bool {
// `.desktop` entries follow the FreeDesktop spec, so installation is not GNOME-specific.
!no_desktop_install_flag_present
!no_desktop_install_flag_present && !is_flatpak
}
impl GitCometView {
@ -47,6 +50,7 @@ impl GitCometView {
if !should_auto_install_linux_desktop_integration(
std::env::var_os("GITCOMET_NO_DESKTOP_INSTALL").is_some(),
desktop.as_deref(),
is_flatpak_sandbox(),
) {
return;
}
@ -59,10 +63,12 @@ impl GitCometView {
return;
};
let desktop_path = data_home.join("applications/gitcomet.desktop");
let desktop_path = data_home.join(format!("applications/{APP_DESKTOP_FILE_NAME}"));
let all_icons_exist = ICON_SIZES.iter().all(|size| {
data_home
.join(format!("icons/hicolor/{size}x{size}/apps/gitcomet.png"))
.join(format!(
"icons/hicolor/{size}x{size}/apps/{APP_ICON_NAME}.png"
))
.exists()
});
if desktop_path.exists() && all_icons_exist {
@ -87,7 +93,7 @@ impl GitCometView {
const DESKTOP_TEMPLATE: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../assets/linux/gitcomet.desktop"
"/../../assets/linux/dev.gitcomet.GitComet.desktop"
));
const ICON_32_PNG: &[u8] = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
@ -131,8 +137,9 @@ impl GitCometView {
let applications_dir = data_home.join("applications");
let icons_root = data_home.join("icons/hicolor");
let desktop_path = applications_dir.join("gitcomet.desktop");
let icon_path = icons_root.join("512x512/apps/gitcomet.png");
let desktop_path = applications_dir.join(APP_DESKTOP_FILE_NAME);
let icon_path =
icons_root.join(format!("512x512/apps/{APP_ICON_NAME}.png"));
fs::create_dir_all(&applications_dir)
.map_err(|e| format!("Desktop install failed: {e}"))?;
@ -159,7 +166,7 @@ impl GitCometView {
for (size, icon_bytes) in ICON_ASSETS {
let icon_dir = icons_root.join(format!("{size}x{size}/apps"));
let icon_file = icon_dir.join("gitcomet.png");
let icon_file = icon_dir.join(format!("{APP_ICON_NAME}.png"));
fs::create_dir_all(&icon_dir)
.and_then(|_| fs::write(&icon_file, icon_bytes))
.map_err(|e| format!("Desktop install failed: {e}"))?;
@ -226,22 +233,35 @@ mod tests {
fn auto_install_is_not_limited_to_gnome() {
for desktop in ["GNOME", "KDE", "XFCE", "sway", ""] {
assert!(
should_auto_install_linux_desktop_integration(false, Some(desktop)),
should_auto_install_linux_desktop_integration(false, Some(desktop), false),
"expected desktop '{desktop}' to allow auto install"
);
}
assert!(should_auto_install_linux_desktop_integration(false, None));
assert!(should_auto_install_linux_desktop_integration(
false, None, false
));
}
#[test]
fn auto_install_respects_opt_out_flag() {
assert!(!should_auto_install_linux_desktop_integration(
true,
Some("GNOME")
Some("GNOME"),
false
));
assert!(!should_auto_install_linux_desktop_integration(
true,
Some("KDE")
Some("KDE"),
false
));
}
#[test]
fn auto_install_is_disabled_in_flatpak() {
assert!(!should_auto_install_linux_desktop_integration(
false,
Some("GNOME"),
true
));
}
}

388
flatpak.md Normal file
View file

@ -0,0 +1,388 @@
# GitComet Flatpak Verification and Flathub Submission
This document covers two things:
1. How to manually verify that the GitComet Flatpak installs and works.
2. How to submit GitComet to Flathub for the first time, then switch to the repo's automated update flow.
The Flatpak app ID used in this repo is `dev.gitcomet.GitComet`.
## 1. What to verify before submission
Before opening a Flathub submission, verify all of these on a Linux machine:
- the Flatpak builds successfully from this repo
- the single-file `.flatpak` bundle installs cleanly
- the app launches from the desktop and from `flatpak run`
- the app can open a real host repository
- normal Git operations work inside the sandbox
- the desktop file, icon, and metainfo look correct
- the declared permissions match what the app really needs
If you develop on macOS or Windows, do this section in a Linux VM.
## 2. Prerequisites
Install Flatpak and `flatpak-builder` from your distro, then make sure the Flathub remote exists:
```bash
flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
```
Install the Flathub-maintained builder runtime used by Flathub's docs and linter:
```bash
flatpak install --user -y flathub org.flatpak.Builder
```
## 3. Clean out any previous local test install
If you already installed an older GitComet Flatpak locally, remove it first:
```bash
flatpak uninstall --user -y dev.gitcomet.GitComet || true
rm -rf builddir repo dist/dev.gitcomet.GitComet.flatpak
```
## 4. Build the local Flatpak from this repo
From the repository root:
```bash
flatpak-builder \
--force-clean \
--user \
--install-deps-from=flathub \
--repo=repo \
--install \
builddir \
flatpak/dev.gitcomet.GitComet.local.yaml
```
What this does:
- builds the Flatpak from `flatpak/dev.gitcomet.GitComet.local.yaml`
- exports the result to a local OSTree repo at `./repo`
- installs the Flatpak into your user Flatpak installation
## 5. Build the single-file `.flatpak` bundle
This is the install format you should manually verify before submission:
```bash
mkdir -p dist
flatpak build-bundle \
repo \
dist/dev.gitcomet.GitComet.flatpak \
dev.gitcomet.GitComet \
--runtime-repo=https://dl.flathub.org/repo/flathub.flatpakrepo
```
## 6. Reinstall from the bundle
First remove the locally installed build, then install the actual bundle file:
```bash
flatpak uninstall --user -y dev.gitcomet.GitComet || true
flatpak install --user -y dist/dev.gitcomet.GitComet.flatpak
```
Confirm the installed app ID:
```bash
flatpak info dev.gitcomet.GitComet
```
Inspect the shipped permissions:
```bash
flatpak info --show-permissions dev.gitcomet.GitComet
```
Expected defaults from this repo:
- `--filesystem=host`
- `--share=network`
- `--socket=ssh-auth`
- `--socket=gpg-agent`
- `--socket=wayland`
- `--socket=fallback-x11`
- `--talk-name=org.freedesktop.Flatpak`
- `--talk-name=org.freedesktop.FileManager1`
- `--talk-name=org.freedesktop.Notifications`
## 7. Launch tests
Launch from the terminal:
```bash
flatpak run dev.gitcomet.GitComet
```
Also verify that it appears correctly in the desktop launcher:
- app name is `GitComet`
- icon is present
- the app opens normally from the graphical launcher
## 8. Functional tests inside the sandbox
Use a disposable test repo so you can verify normal flows end to end.
Create one:
```bash
mkdir -p /tmp/gitcomet-flatpak-test
cd /tmp/gitcomet-flatpak-test
git init
printf '# Flatpak test\n' > README.md
git add README.md
git commit -m "Initial commit"
```
Then verify this checklist manually in the Flatpak build:
1. Open `/tmp/gitcomet-flatpak-test` in GitComet.
2. Confirm the commit history appears.
3. Edit `README.md`, then stage and unstage the change.
4. Create a commit from inside GitComet.
5. If you use SSH remotes, add a disposable SSH remote and verify fetch/pull/push.
6. If you sign commits or tags, verify GPG agent access.
7. If you rely on difftool or mergetool paths, test at least one real conflict or diff flow.
Important things to watch for:
- no missing repository access errors
- no missing `git` binary errors
- auth prompts work
- the app is using the host Git successfully
- temp-file-based auth, merge, or diff flows work
## 9. Lint and metadata checks
Run the same basic checks Flathub expects before you submit:
```bash
flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest flatpak/dev.gitcomet.GitComet.local.yaml
flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo
flatpak run --command=flatpak-builder-lint org.flatpak.Builder appstream flatpak/dev.gitcomet.GitComet.metainfo.xml
desktop-file-validate assets/linux/dev.gitcomet.GitComet.desktop
appstreamcli validate --no-net flatpak/dev.gitcomet.GitComet.metainfo.xml
```
Do not submit until these are clean or you explicitly know which exception you need.
## 10. Prepare the first public release assets
For the first Flathub submission, you need public release assets that Flathub can fetch.
The minimum assets needed for the Flathub PR are:
- `gitcomet-v<VERSION>-source.tar.gz`
- `dev.gitcomet.GitComet.yaml`
- `cargo-sources.json`
- `flathub.json`
### Recommended first-release path
Use a normal public GitHub release first, then use this repo's existing build workflow to attach the Flatpak/Flathub assets.
Example for version `0.2.0`:
```bash
git tag -a v0.2.0 -m "GitComet v0.2.0"
git push origin v0.2.0
gh release create v0.2.0 \
--repo Auto-Explore/GitComet \
--title "GitComet v0.2.0" \
--generate-notes
RELEASE_ID="$(gh release view v0.2.0 --repo Auto-Explore/GitComet --json id --jq '.id')"
gh workflow run build-release-artifacts.yml \
-f tag=v0.2.0 \
-f version=0.2.0 \
-f release_id="$RELEASE_ID"
```
After that workflow finishes, the GitHub release should contain:
- the Linux Flatpak bundle
- the source tarball
- the rendered Flathub manifest
- `cargo-sources.json`
- `flathub.json`
Note:
- for the very first Flathub submission, do not rely on `release-manual-main.yml` unless the Flathub app repo already exists and `FLATHUB_TOKEN` has been configured
- the app-specific Flathub repo does not exist until the initial submission is accepted
## 11. Download the submission files
Download these release assets locally:
- `dev.gitcomet.GitComet.yaml`
- `cargo-sources.json`
- `flathub.json`
You will put those into the Flathub submission PR.
Before you open the PR, lint the exact rendered manifest you are about to submit:
```bash
flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest dev.gitcomet.GitComet.yaml
```
## 12. Open the first Flathub submission PR
New Flathub app submissions go through `flathub/flathub` and must target the `new-pr` base branch.
### Option A: with GitHub CLI
```bash
gh repo fork --clone flathub/flathub
cd flathub
git checkout --track origin/new-pr
git checkout -b gitcomet-submission
```
### Option B: manual Git setup
1. Fork `flathub/flathub` on GitHub.
2. Make sure "Copy the master branch only" is unchecked when you fork.
3. Clone your fork's `new-pr` branch:
```bash
git clone --branch=new-pr git@github.com:YOUR_GITHUB_USERNAME/flathub.git
cd flathub
git checkout -b gitcomet-submission
```
### Add the GitComet submission files
Copy these into the root of the cloned `flathub` repo:
- `dev.gitcomet.GitComet.yaml`
- `cargo-sources.json`
- `flathub.json`
Then commit and push:
```bash
git add dev.gitcomet.GitComet.yaml cargo-sources.json flathub.json
git commit -m "Add dev.gitcomet.GitComet"
git push origin gitcomet-submission
```
### Create the pull request
Open a PR with:
- base repo: `flathub/flathub`
- base branch: `new-pr`
- title: `Add dev.gitcomet.GitComet`
Do not target `master` or the default branch.
If you use GitHub CLI, this is the equivalent command:
```bash
gh pr create \
--repo flathub/flathub \
--base new-pr \
--head YOUR_GITHUB_USERNAME:gitcomet-submission \
--title "Add dev.gitcomet.GitComet"
```
## 13. Handle review
While the submission is under review:
- keep using the same PR
- push fixes to the same branch
- do not close and reopen the PR just to address comments
- do not merge the default Flathub branch into your submission branch
If reviewers ask for permission changes, metadata fixes, or linter exceptions, update the same PR.
## 14. Verify ownership of `gitcomet.dev`
Once the submission is accepted and you have collaborator access to the app repository, verify the app in the Flathub Developer Portal.
The verification path for this app ID is:
```text
https://gitcomet.dev/.well-known/org.flathub.VerifiedApps.txt
```
The process is:
1. Log in to Flathub.
2. Open the Developer Portal.
3. Open the GitComet app entry.
4. Open `Verification`.
5. Copy the generated token.
6. Publish that token at `https://gitcomet.dev/.well-known/org.flathub.VerifiedApps.txt`.
7. Retry verification in the portal.
If multiple apps are ever verified under `gitcomet.dev`, put each token on its own line.
## 15. Switch to automated updates after the first acceptance
After the first submission is accepted, Flathub will create an app-specific packaging repo, typically:
```text
flathub/dev.gitcomet.GitComet
```
At that point, configure these in the GitComet GitHub repo:
- secret `FLATHUB_TOKEN`
- optional variable `FLATHUB_REPO=flathub/dev.gitcomet.GitComet`
- optional variable `FLATHUB_BRANCH`
- optional variable `FLATHUB_MODE=pull_request`
Recommended setting:
- use `FLATHUB_MODE=pull_request` first
- only switch to `push` if you intentionally want direct commits to the Flathub app repo
This repo already has a release-time Flathub deployment workflow. Once the app repo exists and the token is configured, `release-manual-main.yml` can:
1. publish the GitHub release
2. upload the Flatpak and Flathub assets
3. open or update the Flathub packaging PR automatically
## 16. Why `flathub.json` is included
This repo ships a `flathub.json` with:
```json
{
"disable-external-data-checker": true
}
```
That is intentional.
GitComet's GitHub release flow already creates the Flathub update payload. Disabling Flathub's default hourly external-data-checker avoids duplicate update PRs.
## 17. Quick checklist
Before first submission:
- local Flatpak build passes
- bundle install passes
- app launches and works
- linter checks pass
- public GitHub release exists
- release contains `dev.gitcomet.GitComet.yaml`
- release contains `cargo-sources.json`
- release contains `flathub.json`
- submission PR targets `flathub/flathub:new-pr`
After acceptance:
- app verified with `gitcomet.dev`
- collaborator access to the app repo confirmed
- `FLATHUB_TOKEN` configured in GitHub
- `release-manual-main.yml` used for ongoing releases

11097
flatpak/cargo-sources.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
app-id: dev.gitcomet.GitComet
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: gitcomet-app
finish-args:
- --device=dri
- --share=ipc
- --share=network
- --socket=wayland
- --socket=fallback-x11
- --socket=ssh-auth
- --socket=gpg-agent
- --filesystem=host
- --talk-name=org.freedesktop.FileManager1
- --talk-name=org.freedesktop.Flatpak
- --talk-name=org.freedesktop.Notifications
modules:
- name: gitcomet
buildsystem: simple
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
env:
CARGO_HOME: /run/build/gitcomet/cargo
CARGO_NET_OFFLINE: "true"
build-commands:
- cargo --offline fetch --manifest-path Cargo.toml --locked --verbose
- cargo build --offline --release --locked -p gitcomet-app --features ui-gpui,gix
- install -Dm755 target/release/gitcomet-app ${FLATPAK_DEST}/bin/gitcomet-app
- install -Dm755 flatpak/git-wrapper.sh ${FLATPAK_DEST}/bin/git
- install -Dm644 assets/linux/dev.gitcomet.GitComet.desktop ${FLATPAK_DEST}/share/applications/dev.gitcomet.GitComet.desktop
- install -Dm644 flatpak/dev.gitcomet.GitComet.metainfo.xml ${FLATPAK_DEST}/share/metainfo/dev.gitcomet.GitComet.metainfo.xml
- install -Dm644 assets/linux/hicolor/32x32/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/32x32/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/48x48/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/48x48/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/128x128/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/128x128/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/256x256/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/256x256/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/512x512/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/dev.gitcomet.GitComet.png
sources:
- type: dir
path: ..
- cargo-sources.json

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>dev.gitcomet.GitComet</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>AGPL-3.0-only</project_license>
<name>GitComet</name>
<summary>Fast, resource-efficient Git GUI written in Rust</summary>
<developer id="dev.gitcomet">
<name>AutoExplore Oy</name>
</developer>
<launchable type="desktop-id">dev.gitcomet.GitComet.desktop</launchable>
<provides>
<binary>gitcomet-app</binary>
</provides>
<description>
<p>GitComet is a fast Git GUI built for large repositories, familiar history browsing, local-first privacy, and drop-in difftool and mergetool workflows.</p>
<p>It supports staging, commits, branches, remotes, worktrees, history, side-by-side diffs, and conflict resolution.</p>
</description>
<categories>
<category>Development</category>
<category>RevisionControl</category>
</categories>
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/Auto-Explore/GitComet/main/assets/gitcomet_screenshot.png</image>
</screenshot>
</screenshots>
<content_rating type="oars-1.1"/>
<url type="homepage">https://gitcomet.dev</url>
<url type="bugtracker">https://github.com/Auto-Explore/GitComet/issues</url>
<url type="vcs-browser">https://github.com/Auto-Explore/GitComet</url>
</component>

View file

@ -0,0 +1,50 @@
app-id: dev.gitcomet.GitComet
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: gitcomet-app
finish-args:
- --device=dri
- --share=ipc
- --share=network
- --socket=wayland
- --socket=fallback-x11
- --socket=ssh-auth
- --socket=gpg-agent
- --filesystem=host
- --talk-name=org.freedesktop.FileManager1
- --talk-name=org.freedesktop.Flatpak
- --talk-name=org.freedesktop.Notifications
modules:
- name: gitcomet
buildsystem: simple
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
env:
CARGO_HOME: /run/build/gitcomet/cargo
CARGO_NET_OFFLINE: "true"
build-commands:
- cargo --offline fetch --manifest-path Cargo.toml --locked --verbose
- cargo build --offline --release --locked -p gitcomet-app --features ui-gpui,gix
- install -Dm755 target/release/gitcomet-app ${FLATPAK_DEST}/bin/gitcomet-app
- install -Dm755 flatpak/git-wrapper.sh ${FLATPAK_DEST}/bin/git
- install -Dm644 assets/linux/dev.gitcomet.GitComet.desktop ${FLATPAK_DEST}/share/applications/dev.gitcomet.GitComet.desktop
- install -Dm644 flatpak/dev.gitcomet.GitComet.metainfo.xml ${FLATPAK_DEST}/share/metainfo/dev.gitcomet.GitComet.metainfo.xml
- install -Dm644 assets/linux/hicolor/32x32/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/32x32/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/48x48/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/48x48/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/128x128/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/128x128/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/256x256/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/256x256/apps/dev.gitcomet.GitComet.png
- install -Dm644 assets/linux/hicolor/512x512/apps/gitcomet.png ${FLATPAK_DEST}/share/icons/hicolor/512x512/apps/dev.gitcomet.GitComet.png
sources:
- type: archive
url: @SOURCE_URL@
sha256: @SOURCE_SHA256@
strip-components: 1
x-checker-data:
type: json
url: https://api.github.com/repos/Auto-Explore/GitComet/releases/latest
version-query: .tag_name | sub("^v"; "")
url-query: '"https://github.com/Auto-Explore/GitComet/releases/download/" + .tag_name + "/gitcomet-v" + (.tag_name | sub("^v"; "")) + "-source.tar.gz"'
- cargo-sources.json

3
flatpak/flathub.json Normal file
View file

@ -0,0 +1,3 @@
{
"disable-external-data-checker": true
}

35
flatpak/git-wrapper.sh Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env sh
set -eu
host_home=""
if [ -n "${FLATPAK_ID:-}" ] && [ -n "${HOME:-}" ]; then
suffix="/.var/app/${FLATPAK_ID}"
case "$HOME" in
*"$suffix")
host_home="${HOME%$suffix}"
;;
esac
fi
set -- git "$@"
if [ -n "${host_home:-}" ]; then
set -- "HOME=${host_home}" "$@"
fi
[ -n "${HOST_XDG_CONFIG_HOME:-}" ] && set -- "XDG_CONFIG_HOME=${HOST_XDG_CONFIG_HOME}" "$@"
[ -n "${HOST_XDG_DATA_HOME:-}" ] && set -- "XDG_DATA_HOME=${HOST_XDG_DATA_HOME}" "$@"
[ -n "${HOST_XDG_CACHE_HOME:-}" ] && set -- "XDG_CACHE_HOME=${HOST_XDG_CACHE_HOME}" "$@"
[ -n "${HOST_XDG_STATE_HOME:-}" ] && set -- "XDG_STATE_HOME=${HOST_XDG_STATE_HOME}" "$@"
[ -n "${DISPLAY:-}" ] && set -- "DISPLAY=${DISPLAY}" "$@"
[ -n "${WAYLAND_DISPLAY:-}" ] && set -- "WAYLAND_DISPLAY=${WAYLAND_DISPLAY}" "$@"
[ -n "${GIT_ASKPASS:-}" ] && set -- "GIT_ASKPASS=${GIT_ASKPASS}" "$@"
[ -n "${SSH_ASKPASS:-}" ] && set -- "SSH_ASKPASS=${SSH_ASKPASS}" "$@"
[ -n "${SSH_ASKPASS_REQUIRE:-}" ] && set -- "SSH_ASKPASS_REQUIRE=${SSH_ASKPASS_REQUIRE}" "$@"
[ -n "${GIT_TERMINAL_PROMPT:-}" ] && set -- "GIT_TERMINAL_PROMPT=${GIT_TERMINAL_PROMPT}" "$@"
[ -n "${GITCOMET_ASKPASS_PROMPT_LOG:-}" ] && set -- "GITCOMET_ASKPASS_PROMPT_LOG=${GITCOMET_ASKPASS_PROMPT_LOG}" "$@"
[ -n "${GITCOMET_AUTH_KIND:-}" ] && set -- "GITCOMET_AUTH_KIND=${GITCOMET_AUTH_KIND}" "$@"
[ -n "${GITCOMET_AUTH_USERNAME:-}" ] && set -- "GITCOMET_AUTH_USERNAME=${GITCOMET_AUTH_USERNAME}" "$@"
[ -n "${GITCOMET_AUTH_SECRET:-}" ] && set -- "GITCOMET_AUTH_SECRET=${GITCOMET_AUTH_SECRET}" "$@"
exec flatpak-spawn --host env "$@"

View file

@ -0,0 +1,118 @@
#!/usr/bin/env python3
import argparse
import json
import tomllib
from pathlib import Path
CRATES_IO = "https://static.crates.io/crates"
CARGO_HOME = "cargo"
CARGO_CRATES = f"{CARGO_HOME}/vendor"
VENDORED_SOURCES = "vendored-sources"
CRATES_IO_SOURCE = "registry+https://github.com/rust-lang/crates.io-index"
def package_checksum(package: dict, metadata: dict) -> str:
checksum = package.get("checksum")
if checksum:
return checksum
key = (
f'checksum {package["name"]} {package["version"]} '
f'({package["source"]})'
)
checksum = metadata.get(key)
if checksum:
return checksum
raise SystemExit(
f'missing checksum for {package["name"]} {package["version"]}'
)
def cargo_config_contents() -> str:
return (
"[source.crates-io]\n"
f'replace-with = "{VENDORED_SOURCES}"\n\n'
f"[source.{VENDORED_SOURCES}]\n"
f'directory = "{CARGO_CRATES}"\n'
)
def generate_sources(lock_path: Path) -> list[dict]:
with lock_path.open("rb") as handle:
cargo_lock = tomllib.load(handle)
metadata = cargo_lock.get("metadata", {})
seen = set()
sources = []
for package in cargo_lock["package"]:
source = package.get("source")
if source is None:
continue
if source != CRATES_IO_SOURCE:
raise SystemExit(
f'unsupported non-crates.io dependency: {package["name"]} {source}'
)
key = (package["name"], package["version"])
if key in seen:
continue
seen.add(key)
checksum = package_checksum(package, metadata)
dest = f'{CARGO_CRATES}/{package["name"]}-{package["version"]}'
sources.append(
{
"type": "archive",
"archive-type": "tar-gzip",
"url": (
f'{CRATES_IO}/{package["name"]}/'
f'{package["name"]}-{package["version"]}.crate'
),
"sha256": checksum,
"dest": dest,
}
)
sources.append(
{
"type": "inline",
"contents": json.dumps({"package": checksum, "files": {}}),
"dest": dest,
"dest-filename": ".cargo-checksum.json",
}
)
sources.append(
{
"type": "inline",
"contents": cargo_config_contents(),
"dest": CARGO_HOME,
"dest-filename": "config",
}
)
return sources
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument(
"cargo_lock",
nargs="?",
default="Cargo.lock",
help="Path to Cargo.lock",
)
parser.add_argument(
"-o",
"--output",
default="flatpak/cargo-sources.json",
help="Path to write generated cargo sources JSON",
)
args = parser.parse_args()
sources = generate_sources(Path(args.cargo_lock))
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(sources, indent=4) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()

View file

@ -7,8 +7,8 @@ Usage: scripts/install-linux.sh [--release|--debug] [--prefix PATH] [--no-build]
Installs:
- binary to <prefix>/bin/gitcomet-app
- desktop entry to ~/.local/share/applications/gitcomet.desktop
- icons to ~/.local/share/icons/hicolor/<size>x<size>/apps/gitcomet.png
- desktop entry to ~/.local/share/applications/dev.gitcomet.GitComet.desktop
- icons to ~/.local/share/icons/hicolor/<size>x<size>/apps/dev.gitcomet.GitComet.png
sizes: 32, 48, 128, 256, 512
Defaults:
@ -52,6 +52,8 @@ bindir="${prefix}/bin"
appdir="${XDG_DATA_HOME:-${HOME}/.local/share}/applications"
iconsroot="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/hicolor"
icon_sizes=(32 48 128 256 512)
desktop_file="dev.gitcomet.GitComet.desktop"
icon_name="dev.gitcomet.GitComet"
install -Dm755 "$bin_src" "${bindir}/gitcomet-app"
@ -59,12 +61,17 @@ install -Dm755 "$bin_src" "${bindir}/gitcomet-app"
tmp_desktop="$(mktemp)"
trap 'rm -f "$tmp_desktop"' EXIT
sed "s|^Exec=.*$|Exec=${bindir}/gitcomet-app|g" \
"${repo_root}/assets/linux/gitcomet.desktop" >"$tmp_desktop"
install -Dm644 "$tmp_desktop" "${appdir}/gitcomet.desktop"
"${repo_root}/assets/linux/${desktop_file}" >"$tmp_desktop"
install -Dm644 "$tmp_desktop" "${appdir}/${desktop_file}"
for size in "${icon_sizes[@]}"; do
install -Dm644 "${repo_root}/assets/linux/hicolor/${size}x${size}/apps/gitcomet.png" \
"${iconsroot}/${size}x${size}/apps/gitcomet.png"
"${iconsroot}/${size}x${size}/apps/${icon_name}.png"
done
rm -f "${appdir}/gitcomet.desktop"
for size in "${icon_sizes[@]}"; do
rm -f "${iconsroot}/${size}x${size}/apps/gitcomet.png"
done
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$appdir" >/dev/null 2>&1 || true
@ -72,8 +79,8 @@ command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache "${ico
echo "Installed GitComet:"
echo " ${bindir}/gitcomet-app"
echo " ${appdir}/gitcomet.desktop"
echo " ${appdir}/${desktop_file}"
for size in "${icon_sizes[@]}"; do
echo " ${iconsroot}/${size}x${size}/apps/gitcomet.png"
echo " ${iconsroot}/${size}x${size}/apps/${icon_name}.png"
done
echo "If GNOME still shows a generic icon, log out/in (or restart GNOME Shell)."

View file

@ -160,7 +160,7 @@ cat > "${contents_dir}/Info.plist" <<PLIST
<key>CFBundleExecutable</key>
<string>gitcomet-app</string>
<key>CFBundleIdentifier</key>
<string>ai.autoexplore.gitcomet</string>
<string>dev.gitcomet.GitComet</string>
<key>CFBundleIconFile</key>
<string>GitComet.icns</string>
<key>CFBundleInfoDictionaryVersion</key>

View file

@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Usage: scripts/render-flathub-manifest.sh --source-url URL --source-sha256 SHA256 --output PATH [--template PATH]
EOF
}
template="flatpak/dev.gitcomet.GitComet.yaml.in"
source_url=""
source_sha256=""
output=""
while [[ $# -gt 0 ]]; do
case "$1" in
--template) template="$2"; shift 2 ;;
--source-url) source_url="$2"; shift 2 ;;
--source-sha256) source_sha256="$2"; shift 2 ;;
--output) output="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "Unknown arg: $1" >&2; usage; exit 2 ;;
esac
done
if [[ -z "$source_url" || -z "$source_sha256" || -z "$output" ]]; then
usage
exit 2
fi
mkdir -p "$(dirname "$output")"
sed \
-e "s|@SOURCE_URL@|${source_url}|g" \
-e "s|@SOURCE_SHA256@|${source_sha256}|g" \
"$template" >"$output"

View file

@ -17,11 +17,17 @@ bindir="${prefix}/bin"
appdir="${XDG_DATA_HOME:-${HOME}/.local/share}/applications"
iconsroot="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/hicolor"
icon_sizes=(32 48 128 256 512)
desktop_files=("dev.gitcomet.GitComet.desktop" "gitcomet.desktop")
icon_names=("dev.gitcomet.GitComet" "gitcomet")
rm -f "${bindir}/gitcomet-app"
rm -f "${appdir}/gitcomet.desktop"
for desktop_file in "${desktop_files[@]}"; do
rm -f "${appdir}/${desktop_file}"
done
for size in "${icon_sizes[@]}"; do
rm -f "${iconsroot}/${size}x${size}/apps/gitcomet.png"
for icon_name in "${icon_names[@]}"; do
rm -f "${iconsroot}/${size}x${size}/apps/${icon_name}.png"
done
done
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$appdir" >/dev/null 2>&1 || true