<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

# Creating a Release

This guide describes how to create a release of Apache Paimon Rust, including the Rust crates, Python binding, and Go binding. It follows the [ASF Release Policy](https://www.apache.org/legal/release-policy.html) and [Release Distribution Policy](https://infra.apache.org/release-distribution.html).

## Overview

![Release process overview](img/release-guide.png)

The release process consists of:

1. [Decide to release](#decide-to-release)
2. [Prepare for the release](#prepare-for-the-release)
3. [Build a release candidate](#build-a-release-candidate)
4. [Vote on the release candidate](#vote-on-the-release-candidate)
5. [If necessary, fix any issues and go back to step 3](#fix-any-issues)
6. [Finalize the release](#finalize-the-release)
7. [Promote the release](#promote-the-release)

### Automated Publishing

When a version tag is pushed, GitHub Actions automatically publishes the language-specific artifacts:

| Component        | Tag Pattern              | Published To      | Pre-release (`-rc`) Behavior          |
|------------------|--------------------------|-------------------|---------------------------------------|
| Rust crates      | `v0.1.0`                 | crates.io         | Dry-run only                          |
| Python binding   | `v0.1.0`                 | PyPI              | Publishes to TestPyPI                 |
| Go binding       | `v0.1.0`                 | Go module proxy   | Publishes as `bindings/go/vX.Y.Z-rcN` |

The Release Manager's primary responsibility is managing the **source release** (tarball + signature) and coordinating the community vote. Language artifact publishing is handled by CI once the final tag is pushed.

## Decide to Release

Deciding to release and selecting a Release Manager is the first step. This is a consensus-based decision of the community.

Anybody can propose a release on the dev [mailing list](https://lists.apache.org/list.html?dev@paimon.apache.org), giving a short rationale and nominating a committer as Release Manager (including themselves).

**Checklist**

- [ ] Community agrees to release
- [ ] A Release Manager is selected

## Prepare for the Release

### One-time Release Manager Setup

Before your first release, complete the following setup.

#### GPG Key

1. Install GnuPG if not already available:

    ```bash
    # macOS
    brew install gnupg

    # Ubuntu / Debian
    sudo apt install gnupg2
    ```

2. Generate a key pair:

    ```bash
    gpg --full-gen-key
    ```

    When prompted, select:

    - Key type: **RSA and RSA** (option 1)
    - Key size: **4096**
    - Validity: **0** (does not expire)
    - Real name and email: use your **Apache name and `@apache.org` email**

3. List your keys to find your key ID (the 8-digit hex string in the `pub` line):

    ```bash
    gpg --list-keys --keyid-format short
    ```

    Example output:

    ```text
    pub   rsa4096/845E6689 2024-01-01 [SC]
          ABCDEF1234567890ABCDEF1234567890845E6689
    uid         [ultimate] Your Name <yourname@apache.org>
    sub   rsa4096/12345678 2024-01-01 [E]
    ```

    In this example, the key ID is `845E6689`. Replace `<YOUR_KEY_ID>` with your actual key ID in the following steps.

4. Upload your public key to the Ubuntu key server:

    ```bash
    gpg --keyserver hkps://keyserver.ubuntu.com --send-keys <YOUR_KEY_ID>
    ```

5. Append your key to the project [KEYS](https://downloads.apache.org/paimon/KEYS) file (requires PMC write access):

    ```bash
    svn co https://dist.apache.org/repos/dist/release/paimon/ paimon-dist-release --depth=files
    cd paimon-dist-release
    (gpg --list-sigs <YOUR_KEY_ID> && gpg --armor --export <YOUR_KEY_ID>) >> KEYS
    svn ci -m "Add <YOUR_NAME>'s public key"
    ```

    !!! note
        Never remove existing keys from the KEYS file — users may need them to verify older releases.

6. Configure Git to sign tags with your key:

    ```bash
    git config --global user.signingkey <YOUR_KEY_ID>
    ```

    Omit `--global` to only configure signing for the current repository.

7. (Optional) Upload the GPG public key to your GitHub account:

    Go to [https://github.com/settings/keys](https://github.com/settings/keys) and add your GPG key. Make sure the email associated with the key is also added at [https://github.com/settings/emails](https://github.com/settings/emails), otherwise signed commits and tags may show as "Unverified".

#### GitHub Actions Secrets

Ensure the following repository secrets are configured:

- `CARGO_REGISTRY_TOKEN` — for crates.io publishing
- `PYPI_API_TOKEN` — for PyPI publishing
- `TEST_PYPI_API_TOKEN` — for TestPyPI publishing

### Clone into a fresh workspace

Use a clean clone to avoid local changes affecting the release.

```bash
git clone https://github.com/apache/paimon-rust.git
cd paimon-rust
```

### Set up environment variables

```bash
RELEASE_VERSION="0.1.0"
SHORT_RELEASE_VERSION="0.1"
NEXT_VERSION="0.2.0"
RELEASE_TAG="v${RELEASE_VERSION}"
RC_NUM="1"
RC_TAG="v${RELEASE_VERSION}-rc${RC_NUM}"
```

### Generate dependencies list

[ASF release policy](https://www.apache.org/legal/release-policy.html) requires that every release comply with [ASF licensing policy](https://www.apache.org/legal/resolved.html). Generate and commit a dependency list on `main` **before** creating the release branch, so both `main` and the release branch have the same list.

1. Install [cargo-deny](https://embarkstudios.github.io/cargo-deny/):

    ```bash
    cargo install cargo-deny --version 0.19.6 --locked
    ```

2. Generate the dependency list (requires **Python 3.11+**):

    ```bash
    git checkout main
    git pull
    cargo fetch --locked
    python3 scripts/dependencies.py generate
    python3 scripts/dependencies.py verify
    ```

    This creates dependency reports for the workspace, each member crate, and the Go binding.

3. Commit the result:

    ```bash
    git add Cargo.lock DEPENDENCIES.rust.tsv '**/DEPENDENCIES.rust.tsv'
    git commit -m "chore: update dependency list for release ${RELEASE_VERSION}"
    git push origin main
    ```

To only check licenses without generating files: `python3 scripts/dependencies.py check`.

### Create a release branch

From `main`, create a release branch:

```bash
git checkout -b release-${SHORT_RELEASE_VERSION}
git push origin release-${SHORT_RELEASE_VERSION}
```

### Bump version on main for next development cycle

After cutting the release branch, bump `main` to the next version so that ongoing development does not use the released version number:

```bash
git checkout main
./scripts/bump-version.sh ${RELEASE_VERSION} ${NEXT_VERSION}
git add Cargo.toml Cargo.lock DEPENDENCIES.rust.tsv '**/DEPENDENCIES.rust.tsv'
git commit -m "chore: bump version to ${NEXT_VERSION}"
git push origin main
```

The script updates `version` in root `Cargo.toml` (`[workspace.package]` and the `paimon` entry in `[workspace.dependencies]`), refreshes `Cargo.lock`, and regenerates the dependency reports. It requires Python 3.11+ and cargo-deny 0.19.6; set `PYTHON` when Python 3.11+ is not available as `python3`. All member crates inherit the workspace version.

### Optional: Create PRs for release blog and download page

If the project website has a release blog or download page, create pull requests to add the new version. **Do not merge these PRs until the release is finalized.**

## Build a Release Candidate

### Create the RC tag

Check out the release branch, create a signed RC tag, and push it. Pushing the tag triggers CI workflows for all three components.

```bash
git checkout release-${SHORT_RELEASE_VERSION}
git pull
git tag -s ${RC_TAG} -m "${RC_TAG}"
git push origin ${RC_TAG}
```

After pushing, verify in [GitHub Actions](https://github.com/apache/paimon-rust/actions) that all release workflows succeed:

- **Release Rust** — dry-run check for RC tags
- **Release Python Binding** — publishes to TestPyPI
- **Release Go Binding** — builds embedded libraries for all platforms, creates `bindings/go/${RC_TAG}` tag

### Create source release artifacts

From the repository root (on the release branch, at the commit you tagged):

```bash
./scripts/release.sh ${RELEASE_VERSION}
```

This creates the following under `dist/`:

- `paimon-rust-${RELEASE_VERSION}.tar.gz` — source archive
- `paimon-rust-${RELEASE_VERSION}.tar.gz.asc` — GPG signature
- `paimon-rust-${RELEASE_VERSION}.tar.gz.sha512` — SHA-512 checksum

The script automatically generates the archive from `HEAD` via `git archive`, signs it with your GPG key, and verifies the signature.

### Stage artifacts to SVN

Upload the source release to the ASF dev area:

```bash
svn checkout https://dist.apache.org/repos/dist/dev/paimon/ paimon-dist-dev --depth=immediates
cd paimon-dist-dev
mkdir paimon-rust-${RELEASE_VERSION}-rc${RC_NUM}
cp ../paimon-rust-${RELEASE_VERSION}.tar.gz* paimon-rust-${RELEASE_VERSION}-rc${RC_NUM}/
svn add paimon-rust-${RELEASE_VERSION}-rc${RC_NUM}
svn commit -m "Add paimon-rust ${RELEASE_VERSION} RC${RC_NUM}"
```

**Checklist**

- [ ] RC tag pushed and CI workflows succeeded
- [ ] Source tarball, signature, and checksum staged to [dist.apache.org dev](https://dist.apache.org/repos/dist/dev/paimon/)

## Vote on the Release Candidate

Start a vote on the dev mailing list.

**Subject:** `[VOTE] Release Apache Paimon Rust ${RELEASE_VERSION} (RC${RC_NUM})`

**Body:**

```text
Hi everyone,

Please review and vote on release candidate #${RC_NUM} for Apache Paimon Rust ${RELEASE_VERSION}.

[ ] +1 Approve the release
[ ] +0 No opinion
[ ] -1 Do not approve (please provide specific comments)

The release candidate is available at:
https://dist.apache.org/repos/dist/dev/paimon/paimon-rust-${RELEASE_VERSION}-rc${RC_NUM}/

Git tag:
https://github.com/apache/paimon-rust/releases/tag/${RC_TAG}

KEYS for signature verification:
https://downloads.apache.org/paimon/KEYS

The vote will be open for at least 72 hours.

Thanks,
Release Manager
```

After the vote passes, send a result email:

**Subject:** `[RESULT][VOTE] Release Apache Paimon Rust ${RELEASE_VERSION} (RC${RC_NUM})`

## Fix Any Issues

If the vote reveals issues:

1. Fix them on the release branch via normal PRs.
2. Remove the old RC from dist dev (optional):

```bash
cd paimon-dist-dev
svn remove paimon-rust-${RELEASE_VERSION}-rc${RC_NUM}
svn commit -m "Remove paimon-rust ${RELEASE_VERSION} RC${RC_NUM} (superseded)"
```

3. Increment `RC_NUM`, then go back to [Build a release candidate](#build-a-release-candidate).

## Finalize the Release

### Push the release tag

Once the vote passes, create and push the final release tag. This triggers CI to publish to crates.io, PyPI, and Go module proxy automatically.

```bash
git checkout ${RC_TAG}
git tag -s ${RELEASE_TAG} -m "Release Apache Paimon Rust ${RELEASE_VERSION}"
git push origin ${RELEASE_TAG}
```

### Move source artifacts to the release repository

```bash
svn mv -m "Release paimon-rust ${RELEASE_VERSION}" \
  https://dist.apache.org/repos/dist/dev/paimon/paimon-rust-${RELEASE_VERSION}-rc${RC_NUM} \
  https://dist.apache.org/repos/dist/release/paimon/paimon-rust-${RELEASE_VERSION}
```

### Verify published artifacts

- **Rust:** [crates.io/crates/paimon](https://crates.io/crates/paimon) shows version `${RELEASE_VERSION}`
- **Python:** [PyPI — pypaimon-rust](https://pypi.org/project/pypaimon-rust/) shows version `${RELEASE_VERSION}`
- **Go:** `go list -m github.com/apache/paimon-rust/bindings/go@v${RELEASE_VERSION}` resolves

### Create GitHub Release

1. Go to [Releases — New release](https://github.com/apache/paimon-rust/releases/new).
2. Choose tag `${RELEASE_TAG}`.
3. Click **Generate release notes** and review.
4. Click **Publish release**.

**Checklist**

- [ ] Release tag pushed; CI published to crates.io, PyPI, and Go module proxy
- [ ] Source artifacts moved to [dist release](https://dist.apache.org/repos/dist/release/paimon/)
- [ ] GitHub Release created

## Promote the Release

### Update the Releases page

Update the [Releases](../releases.md) page: move the released version from "Upcoming" to "Past Releases" with a summary of key features and a link to the GitHub release notes.

### Announce the release

Wait at least 24 hours after finalizing. Send the announcement to `dev@paimon.apache.org` and `announce@apache.org` using your `@apache.org` email in **plain text**.

**Subject:** `[ANNOUNCE] Release Apache Paimon Rust ${RELEASE_VERSION}`

**Body:**

```text
The Apache Paimon community is pleased to announce the release of
Apache Paimon Rust ${RELEASE_VERSION}.

Rust:   cargo add paimon
Python: pip install pypaimon-rust
Go:     go get github.com/apache/paimon-rust/bindings/go@v${RELEASE_VERSION}

Release notes:
https://github.com/apache/paimon-rust/releases/tag/v${RELEASE_VERSION}

Thanks to all contributors!
```
