Skip to content
Closed
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: 10 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ When working with a branch, do not use rebase or amend - add new commits instead

Do not commit to the master branch. Create a new branch for every task.

For feature-specific work, prefer opening pull requests against the corresponding module branch, such as `parser/...`, instead of merging directly into `master`.

For new development branches, prefer creating them under the corresponding module prefix, such as `parser/...` for parser work.

Development branches must use the `dev-` prefix after the module namespace. Prefer branch names of the form `<module>/dev-<topic>`, for example `parser/dev-gql-query-root-followup`.

Module trunk branches must also use an explicit marker after the module namespace. Prefer names of the form `<module>/main-<topic>` so they are easy to distinguish from development branches.

Commit messages must use the format `[module] kind : summary`, where `module` identifies the affected area, `kind` is a tag such as `fix`, `feat`, `docs`, or `chore`, and `summary` starts with a lowercase word unless it begins with a proper noun. For example: `[parser] feat : add call clause`.

When writing text such as documentation, comments, or commit messages, wrap literal names from ClickHouse SQL language, classes and functions, or literal excerpts from log messages inside inline code blocks, such as: `MergeTree`.

When writing text such as documentation, comments, or commit messages, write names of functions and methods as `f` instead of `f()` - we prefer it for mathematical purity when it refers a function itself rather than its application.
Expand Down
1 change: 1 addition & 0 deletions .claude/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@
- If `graph` grammar generation must stay on `ANTLR` `4.13.2`, prefer downloading the exact complete jar into `/usr/local/lib` instead of relying on Ubuntu's older `antlr4` package.
- For `ClickHouse` devcontainers on macOS, keep the source tree bind-mounted but move the build directory onto a Docker named volume or another container-local path to avoid Docker Desktop bind-mount I/O bottlenecks.
- On macOS devcontainers, the cleanest compromise is often mounting the Docker volume directly at `/workspace/ClickHouse/build`, so editors keep the familiar path while build I/O stays off the host bind mount.
- GitHub rulesets can require PRs and block direct pushes on target branches, but they do not natively express "base branch X only accepts head branches matching Y"; enforce that part with a required `pull_request` workflow.
62 changes: 62 additions & 0 deletions .github/scripts/validate_branch_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3

import os
import re
import sys


BRANCH_PATTERN = re.compile(r"^(?P<module>[^/]+)/(?P<kind>dev|main)-(?P<topic>[A-Za-z0-9._-]+)$")


def fail(message: str) -> int:
print(message, file=sys.stderr)
return 1


def parse_branch(name: str):
match = BRANCH_PATTERN.fullmatch(name)
return match.groupdict() if match else None


def main() -> int:
base = os.environ.get("BASE_REF", "").strip()
head = os.environ.get("HEAD_REF", "").strip()

if not base or not head:
return fail("Missing `BASE_REF` or `HEAD_REF` environment variable.")

if base == "master":
head_branch = parse_branch(head)
if not head_branch or head_branch["kind"] != "main":
return fail(
f"`master` only accepts pull requests from `<module>/main-*` branches. "
f"Received head branch `{head}`."
)

print(f"`master` <- `{head}` is allowed.")
return 0

base_branch = parse_branch(base)
if not base_branch or base_branch["kind"] != "main":
print(f"No branch policy applies to base branch `{base}`.")
return 0

head_branch = parse_branch(head)
if not head_branch or head_branch["kind"] != "dev":
return fail(
f"`{base}` only accepts pull requests from `{base_branch['module']}/dev-*` branches. "
f"Received head branch `{head}`."
)

if head_branch["module"] != base_branch["module"]:
return fail(
f"`{base}` only accepts pull requests from the same module namespace. "
f"Expected `{base_branch['module']}/dev-*`, received `{head}`."
)

print(f"`{base}` <- `{head}` is allowed.")
return 0


if __name__ == "__main__":
sys.exit(main())
27 changes: 27 additions & 0 deletions .github/workflows/branch_policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: branch policy

on:
pull_request:
types:
- opened
- reopened
- synchronize
- edited
- ready_for_review
branches:
- master
- "*/main-*"

jobs:
validate:
name: validate
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Validate source and target branch policy
env:
BASE_REF: ${{ github.base_ref }}
HEAD_REF: ${{ github.head_ref }}
run: python3 .github/scripts/validate_branch_policy.py
53 changes: 53 additions & 0 deletions docs/development/github_branch_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# GitHub Branch Policy

This repository uses two layers of enforcement for branch flow:

1. GitHub rulesets protect the target branches.
2. The `branch policy / validate` workflow checks whether the pull request source branch is allowed for the target branch.

## Branch naming

- Development branches: `<module>/dev-<topic>`
- Module main branches: `<module>/main-<topic>`
- Default branch: `master`

Examples:

- `parser/dev-gql-query-root-followup`
- `parser/main-gql-refactor`

## Allowed pull request flow

- `master` only accepts pull requests from `<module>/main-*`
- `<module>/main-*` only accepts pull requests from the same module's `<module>/dev-*`

Examples:

- `parser/dev-gql-query-root-followup` -> `parser/main-gql-refactor`
- `parser/main-gql-refactor` -> `master`

## Required GitHub rulesets

GitHub rulesets can require pull requests and block direct pushes on protected targets, but the head-branch restriction is enforced by the workflow because GitHub does not provide a native rule for "base branch X only accepts head branches matching Y".

After merging this file and the workflow, configure these repository rulesets in GitHub:

1. Ruleset for `master`
- Target branches: `master`
- Require a pull request before merging
- Restrict updates
- Require status checks to pass before merging
- Required check: `branch policy / validate`

2. Ruleset for module main branches
- Target branches: `*/main-*`
- Require a pull request before merging
- Restrict updates
- Require status checks to pass before merging
- Required check: `branch policy / validate`

Optional hardening:

- Apply rules to administrators
- Require at least one approval before merging
- Require linear history if you want to avoid merge commits on protected branches
Loading