Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .claude/rules/changelog.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
---
paths:
- "CHANGELOG.md"
- "changelog.d/**/*"
- "**/CHANGELOG.md"
- "**/changelog.d/**/*"
- "towncrier.toml"
---

# Changelog

Unreleased changes are towncrier fragments in `changelog.d/`. A release turns them into a `CHANGELOG.md` section with `scripts/release/notes.py assemble` (`RELEASING.md`, Notes).
Unreleased changes are towncrier fragments in `changelog.d/`. A release turns them into a `CHANGELOG.md` section with `scripts/release/notes.py assemble` (`RELEASING.md`, Notes). The Linux app is a second towncrier project under the same rules, in `linux/changelog.d/` and `linux/CHANGELOG.md`; its release runs `towncrier build --dir linux` (`linux/docs/releasing.md`).

- **One file per user-visible change**, named `changelog.d/<PR>.<type>.md`: `<PR>` is the pull request that adds it, `<type>` is `added`, `changed`, `deprecated`, `removed`, `fixed` or `security`. A second entry of the same type in one PR adds a counter, as `towncrier create` does: `3452.fixed.1.md`, then `.2`. The counter does not order entries.
- **One line, at most 120 characters, ending with a period.** Added and Changed name the new thing (`Open in Window for row inspector text fields.`); Fixed names the bug, not the fix (`Empty context menu when right-clicking a read-only inspector field.`). No file paths or type names.
- **No refs, no credit, no `- ` prefix.** towncrier links the PR named by the file and adds the bullet; `notes.py` adds ` by @login` when the PR's author is not a maintainer.
- **One entry per thing a user would notice.** Defects with the same visible symptom share an entry.
- **No fragment** for a fix to something still unreleased (edit that fragment instead) or for a docs-only change.
- **Only a `chore(release): vX.Y.Z` PR edits `CHANGELOG.md`.**
- **Only a `chore(release): vX.Y.Z` PR edits `CHANGELOG.md`**, and only a `chore(release): linux-vX.Y.Z` PR edits `linux/CHANGELOG.md`.
- **The lead block and `[CRITICAL]`**: `RELEASING.md`, Notes and Channels.
- `scripts/ci/check-changelog.py` checks the fragments' text, the headings and the lead blocks in Repo Hygiene, then `towncrier build --draft --version 0.0.0` renders the fragments. towncrier refuses an unknown type and a non-numeric PR number (`ignore`, `issue_pattern`). In PR title, `--pr` checks that a PR into main names each new fragment after itself, that only a release PR edits `CHANGELOG.md`, and that a release PR changes nothing else.
- `scripts/ci/check-changelog.py` checks both projects' fragment text, headings and lead blocks in Repo Hygiene, then `towncrier build --draft --version 0.0.0` renders the root fragments; `--dir linux` renders the Linux ones. towncrier refuses an unknown type and a non-numeric PR number (`ignore`, `issue_pattern`). In PR title, `--pr` checks that a PR into main names each new fragment after itself, that only a release PR edits a `CHANGELOG.md`, and that a release PR changes nothing else; a Linux one also sets the version.
16 changes: 14 additions & 2 deletions .github/workflows/release-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ jobs:
with:
name: linux-sources
path: release
# A pre-release has no CHANGELOG.md section; its notes are a draft of the fragments at the tag.
- name: Install towncrier
if: needs.verify.outputs.prerelease == 'true'
run: |
python3 -m venv "$RUNNER_TEMP/release-venv"
"$RUNNER_TEMP/release-venv/bin/pip" install --quiet --disable-pip-version-check --require-hashes \
-r scripts/release/requirements.txt
- name: Publish the release
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -117,10 +124,15 @@ jobs:
run: |
version="${TAG#linux-v}"
(cd release && sha256sum -- * > SHA256SUMS)
python3 linux/build-aux/packaging/check_packaging.py --source-root linux \
--version "$version" --tag "$TAG" --notes notes.md
if [ "$PRERELEASE" = true ]; then
python3 linux/build-aux/packaging/check_packaging.py --source-root linux \
--version "$version" --tag "$TAG"
"$RUNNER_TEMP/release-venv/bin/towncrier" build --draft --dir linux --version "$version" > draft.md
sed 1d draft.md > notes.md
set -- --prerelease
else
python3 linux/build-aux/packaging/check_packaging.py --source-root linux \
--version "$version" --tag "$TAG" --notes notes.md
fi
# The repository's latest release stays the macOS one.
gh release create "$TAG" release/* --repo "$GITHUB_REPOSITORY" --verify-tag --latest=false \
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Claude Code loads a rule when it reads or edits a file the rule covers. Under Co
| Rule | Read before editing |
| --- | --- |
| `ai-mcp-security.md` | `TablePro/Core/AI`, `TablePro/Core/MCP`, `Packages/TableProMacCore/Sources/TableProMCP` |
| `changelog.md` | `CHANGELOG.md`, `changelog.d`, `towncrier.toml` |
| `changelog.md` | `**/CHANGELOG.md`, `**/changelog.d`, `towncrier.toml` |
| `ci.md` | `.github/workflows`, `.github/actions`, `.github/actionlint.yaml`, `.github/*baseline*`, `scripts/ci`, `scripts/release`, `.swiftlint.yml`, `**/.swiftlint.yml` |
| `connection-window.md` | `TablePro/Core/Services/Infrastructure`, `TablePro/Models/Connection`, `TablePro/Core/Database/DatabaseManager*.swift`, `Packages/TableProCore/Sources/TableProDatabase`, `TablePro/Views/Main` |
| `data-grid.md` | `TablePro/Views/Results`, `TablePro/Core/DataGrid`, `TableProTests/Views/Results` |
Expand Down
2 changes: 1 addition & 1 deletion linux/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ From the repository root, through the verification wrapper, which keeps the full
## Every change

1. **Tests.** A unit test for testable behavior, a `#[gtk4::test]` widget test for UI behavior, and a test against the real engine, ignored with `requires docker`, for driver behavior. No mocks. Details: `docs/testing.md`.
2. **CHANGELOG.** A user-visible change gets an entry under `[Unreleased]` in `linux/CHANGELOG.md`, never the root one or `changelog.d/`. `CONTRIBUTING.md`, Changelog, has the rule.
2. **Changelog.** A user-visible change adds `linux/changelog.d/<PR>.<type>.md`, never the root `changelog.d/`; only a release PR edits `linux/CHANGELOG.md`. Format: the root `.claude/rules/changelog.md`.
3. **Translations.** User-facing strings go through `crate::i18n` (`gettext`, `gettext_f` with named placeholders, `ngettext_f`, `pgettext`). A new or removed file under `crates/app/src` or `data/resources` regenerates `po/POTFILES.in`. See `po/README.md`.
4. **Lint** the workspace: rustfmt, then clippy with and without default features.
5. **Commits and pull requests** follow the root rules: Conventional Commits, at most 72 characters, scope `linux` or `linux-<area>` (for example `linux-grid`, `linux-storage`), and the root `.github/pull_request_template.md`.
Expand Down
22 changes: 2 additions & 20 deletions linux/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,6 @@ All notable changes to TablePro for Linux will be documented in this file.
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
Unreleased changes are files in `changelog.d/`.

The first release of TablePro for Linux, a GTK 4 and libadwaita app for GNOME 50 and later.
It connects to PostgreSQL, MySQL and MariaDB, SQL Server, ClickHouse and SQLite, directly or through an SSH tunnel.
Ubuntu 26.04 and 26.10 install it from a Launchpad PPA, and Fedora 44 and later from COPR.

### Added

- Saved connections with groups, colours, TLS options and read-only mode, and passwords kept in the system keyring.
- SSH tunnels through the system OpenSSH client, with your agent, `~/.ssh/config`, jump hosts and host key checks.
- SQL Server sign-in with Kerberos, using the ticket from `kinit`.
- A window per connection, with its table, structure and SQL editor tabs reopened at the next launch.
- Table grid with filters, sorting and paging, editing in place, new and deleted rows, and undo until you save.
- Structure tab to change columns, indexes and foreign keys, with the SQL shown before it runs.
- SQL editor with completion, formatting, find and replace, a result per statement, and Stop.
- Query history with full-text search, and saved queries.
- Export to CSV, JSON, SQL, Excel, Markdown, HTML and XML, CSV import, and Copy As.
- Open SQLite files from the file manager's Open With, or with `tablepro file.sqlite3`.
- Keyboard navigation of the grid, and names and states for screen readers.
- Desktop notification when a query, save or export finishes in a tab you are not looking at.
- Troubleshooting in About TablePro, with the versions to paste into a bug report.
<!-- towncrier release notes start -->
8 changes: 1 addition & 7 deletions linux/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,7 @@ docs(linux): clarify the TLS step for a new driver

## Changelog

A user-visible change gets one entry under `[Unreleased]` in [CHANGELOG.md](CHANGELOG.md), which follows [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). The repository-level `CHANGELOG.md` and its `changelog.d/` fragments are the macOS release notes; a Linux change never goes there.

- Sections, each at most once per version and in this order: `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`.
- An entry is one line naming the change, under about 120 characters. Added and Changed name the new thing; Fixed names the bug, not the fix. No file paths or type names; an issue reference goes at the end, `(#1234)`.
- One entry per thing a user would notice. Defects with the same visible symptom share an entry.
- No entry for a fix to something still unreleased (fold it into its entry) or for a docs-only change.
- A version may open with a lead block before its first `###`.
A user-visible change adds one towncrier fragment, `changelog.d/<PR>.<type>.md` in this directory, which the release turns into a [CHANGELOG.md](CHANGELOG.md) section ([docs/releasing.md](docs/releasing.md)). The repository-level `CHANGELOG.md` and `changelog.d/` are the macOS release notes; a Linux change never goes there. [`.claude/rules/changelog.md`](../.claude/rules/changelog.md) has the rules for both.

## Pull requests

Expand Down
12 changes: 8 additions & 4 deletions linux/build-aux/packaging/check_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# translations exactly when po/LINGUAS ships one, and that the Debian
# package builds with the Rust the workspace names as its rust-version. The
# release workflow adds the tag and its date, and then CHANGELOG.md must
# carry that version's notes and the spec must be the version's first
# package release.
# carry that version's notes, unless it is a pre-release, and the spec must
# be the version's first package release.
#
# Once CHANGELOG.md has a heading for the version, the release commit has
# set its dates, and the heading, the spec's %changelog entry and the man
Expand Down Expand Up @@ -77,7 +77,10 @@ def release_notes(changelog, version):

def release_heading_date(changelog, version):
"""The date on the version's heading: None without a heading, "" when it has no date."""
heading = re.compile(rf"^## \[{re.escape(version)}\](?: - (\d{{4}}-\d{{2}}-\d{{2}}))?\s*$")
# towncrier links the version to its GitHub release.
heading = re.compile(
rf"^## \[{re.escape(version)}\](?:\([^)\s]*\))?(?: - (\d{{4}}-\d{{2}}-\d{{2}}))?\s*$"
)
for line in changelog.read_text(encoding="utf-8").splitlines():
match = heading.match(line)
if match:
Expand Down Expand Up @@ -163,7 +166,8 @@ def problems(root, version, tag=None, tag_date=None):
if tag is not None:
if tag != f"linux-v{version}":
found.append(f"tag {tag} does not name version {version}; expected linux-v{version}")
if not release_notes(root / CHANGELOG, version):
# A pre-release keeps the fragments; its notes are a towncrier draft of them.
if "-" not in version and not release_notes(root / CHANGELOG, version):
found.append(f"{CHANGELOG} has no notes under ## [{version}]")

packaged = package_version(version)
Expand Down
18 changes: 17 additions & 1 deletion linux/build-aux/tests/test_check_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
- Older notes.
"""

LINKED = "## [1.2.0](https://github.com/TableProApp/TablePro/releases/tag/linux-v1.2.0) - 2026-10-14"


class ProblemsTests(unittest.TestCase):
def tree(
Expand Down Expand Up @@ -210,7 +212,6 @@ def test_a_pre_release_tag_passes_when_the_spec_packages_the_tilde_version(self)
releases='<release version="1.2.0-rc.1" date="2026-10-14"/>',
version="1.2.0~rc.1",
changelog="1.2.0~rc.1",
notes=NOTES.replace("## [1.2.0]", "## [1.2.0-rc.1]"),
man_version="1.2.0-rc.1",
)

Expand Down Expand Up @@ -262,6 +263,21 @@ def test_a_release_needs_its_changelog_notes(self):
self.assertEqual(len(found), 1)
self.assertIn("no notes under ## [1.2.0]", found[0])

def test_a_towncrier_heading_carries_the_release_date(self):
root = self.tree(notes=NOTES.replace("## [1.2.0] - 2026-10-14", LINKED))

self.assertEqual(check_packaging.problems(root, "1.2.0", "linux-v1.2.0", "2026-10-14"), [])
self.assertEqual(
check_packaging.release_notes(root / check_packaging.CHANGELOG, "1.2.0"),
"### Added\n\n- Packages for Ubuntu and Fedora.",
)

root = self.tree(notes=NOTES.replace("## [1.2.0] - 2026-10-14", LINKED.replace("10-14", "10-13")))

self.assertEqual(
check_packaging.problems(root, "1.2.0"), ["CHANGELOG.md dates 1.2.0 2026-10-13, the metainfo 2026-10-14"]
)

def test_the_changelog_is_only_required_for_a_release(self):
self.assertEqual(check_packaging.problems(self.tree(notes="# Changelog\n"), "1.2.0"), [])

Expand Down
Empty file added linux/changelog.d/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions linux/changelog.d/3413.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Open SQLite files from the file manager's Open With, or with `tablepro file.sqlite3`.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SSH tunnels through the system OpenSSH client, with your agent, `~/.ssh/config`, jump hosts and host key checks.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Desktop notification when a query, save or export finishes in a tab you are not looking at.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Troubleshooting in About TablePro, with the versions to paste into a bug report.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SQL Server sign-in with Kerberos, using the ticket from `kinit`.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A window per connection, with its table, structure and SQL editor tabs reopened at the next launch.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Table grid with filters, sorting and paging, editing in place, new and deleted rows, and undo until you save.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Structure tab to change columns, indexes and foreign keys, with the SQL shown before it runs.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
SQL editor with completion, formatting, find and replace, a result per statement, and Stop.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.7.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Query history with full-text search, and saved queries.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Export to CSV, JSON, SQL, Excel, Markdown, HTML and XML, CSV import, and Copy As.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Keyboard navigation of the grid, and names and states for screen readers.
1 change: 1 addition & 0 deletions linux/changelog.d/3464.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Saved connections with groups, colours, TLS options and read-only mode, and passwords kept in the system keyring.
10 changes: 5 additions & 5 deletions linux/docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ A repository admin does these once. The workflow's header names the same secrets

## A release

1. **The release commit**, on `linux`, sets one version and one date (UTC) everywhere:
1. **The release commit**, a pull request titled `chore(release): linux-vX.Y.Z`, sets one version and one date (UTC) everywhere:
- `version` in `meson.build`.
- A new first `<release>` in `data/app.tablepro.TablePro.metainfo.xml.in.in`, with the date and a short description.
- `Version`, `Release: 1%{?dist}` and a new first `%changelog` entry in `build-aux/rpm/tablepro.spec`, with the same date.
- `## [Unreleased]` in `CHANGELOG.md` becomes `## [X.Y.Z] - YYYY-MM-DD`, and a new empty `## [Unreleased]` goes above it.
- The version's section in `CHANGELOG.md`, from the fragments in `changelog.d/`. From the repository root, with the towncrier venv of the root `RELEASING.md`, Notes: `.venv/bin/towncrier build --dir linux --version X.Y.Z --date YYYY-MM-DD --yes`. It deletes the fragments and stages `CHANGELOG.md`. An X.Y.0 types its lead under the heading, at most 6 plain lines naming what a reader would change their behavior over.
- The `.TH` line of `data/tablepro.1`: the date and `TablePro X.Y.Z`.

The `release-metadata` Meson test checks that they agree once the changelog heading exists, so the pull request fails before the tag does.
The pull request changes only these files and `changelog.d/`; PR title refuses anything else. The `release-metadata` Meson test checks that they agree once the changelog heading exists, so the pull request fails before the tag does.
2. **The tag**: `linux-vX.Y.Z` on the merged commit, pushed the same UTC day. The workflow compares the metainfo date with the commit date.
3. **The workflow** checks the tag, builds and tests both packages on amd64 and arm64, publishes the GitHub release with the tarballs and the version's changelog section as notes, uploads the signed source package to the PPA, and posts the tag to COPR. A reviewer approves each step that uses the environment.
4. **Afterwards**: watch the Launchpad build page of each series and the COPR build the workflow prints. A failed build there does not fail the workflow.

A tag such as `linux-v0.2.0-rc.1` publishes a GitHub pre-release with the tarballs only, and skips the PPA and COPR.
A tag such as `linux-v0.2.0-rc.1` publishes a GitHub pre-release with the tarballs only, and skips the PPA and COPR. Its commit sets the version and the dates but adds no `CHANGELOG.md` section and keeps the fragments, so the stable release lists them; the pre-release notes are a towncrier draft of the fragments at the tag.

## A packaging fix

Expand All @@ -57,7 +57,7 @@ The next version's release commit sets `Release` back to 1.

## A fix release

0.1.1 is a release like any other, from `linux`. When `linux` already holds work that should wait for 0.2.0, branch `linux-0.1` from the `linux-v0.1.0` tag, cherry-pick the fixes and the release commit onto it, set `LINUX_BRANCH` to `linux-0.1` and allow that branch in the environment, push the tag, then set both back. Merge the release commit's version, dates and changelog section into `linux` afterwards.
0.1.1 is a release like any other, from `linux`. When `linux` already holds work that should wait for 0.2.0, branch `linux-0.1` from the `linux-v0.1.0` tag, cherry-pick the fixes onto it with their fragments, and open the release pull request into `linux-0.1`, so towncrier builds the section from those fragments only. Set `LINUX_BRANCH` to `linux-0.1` and allow that branch in the environment, push the tag, then set both back. Afterwards cherry-pick the release commit onto `linux` in a pull request with the same title: it brings the version, the dates and the section, and deletes the fragments 0.1.1 shipped.

## Downgrades

Expand Down
27 changes: 27 additions & 0 deletions linux/towncrier.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[tool.towncrier]
filename = "CHANGELOG.md"
directory = "changelog.d"
start_string = "<!-- towncrier release notes start -->\n"
title_format = "## [{version}](https://github.com/TableProApp/TablePro/releases/tag/linux-v{version}) - {project_date}"
issue_format = "[#{issue}](https://github.com/TableProApp/TablePro/pull/{issue})"
underlines = ["", "", ""]
ignore = []
issue_pattern = "\\d+"

[[tool.towncrier.type]]
name = "Added"

[[tool.towncrier.type]]
name = "Changed"

[[tool.towncrier.type]]
name = "Deprecated"

[[tool.towncrier.type]]
name = "Removed"

[[tool.towncrier.type]]
name = "Fixed"

[[tool.towncrier.type]]
name = "Security"
Loading
Loading