diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100644 index 0000000..cfe0fdc --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,16 @@ +#!/usr/bin/env sh +set -eu + +message_file="$1" +tmp_file="${message_file}.tmp" + +# Remove auto-injected co-author trailers we do not want in commit history. +awk ' +{ + if ($0 ~ /^Co-authored-by:[[:space:]]*Copilot[[:space:]]*<223556219\+Copilot@users\.noreply\.github\.com>[[:space:]]*$/) next + if ($0 ~ /^Co-authored-by:[[:space:]]*dexcompiler([[:space:]]*<[^>]+>)?[[:space:]]*$/) next + print $0 +} +' "$message_file" > "$tmp_file" + +mv "$tmp_file" "$message_file" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..df90c05 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Restore + run: dotnet restore Clockworks.sln + + - name: Build + run: dotnet build Clockworks.sln -c Release --no-restore + + - name: Test + run: dotnet test tests/Clockworks.Tests.csproj -c Release --no-build --nologo diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c23189a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,99 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + version: + description: "Package/release version (e.g. 1.3.1)" + required: true + type: string + +permissions: + contents: write + +concurrency: + group: release-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Resolve version + id: vars + shell: bash + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + VERSION="${{ github.event.inputs.version }}" + TAG="v${VERSION}" + else + TAG="${GITHUB_REF_NAME}" + VERSION="${TAG#v}" + fi + + if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+([-+].*)?$ ]]; then + echo "Invalid version: ${VERSION}" + exit 1 + fi + + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "tag=${TAG}" >> "${GITHUB_OUTPUT}" + + - name: Ensure tag exists for manual runs + if: github.event_name == 'workflow_dispatch' + shell: bash + run: | + TAG="${{ steps.vars.outputs.tag }}" + git fetch --tags --force + if ! git rev-parse "${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} does not exist in repository." + echo "Create and push it first (for example: git tag ${TAG} && git push origin ${TAG})." + exit 1 + fi + + - name: Restore + run: dotnet restore Clockworks.sln + + - name: Build + run: dotnet build Clockworks.sln -c Release --no-restore + + - name: Test + run: dotnet test tests/Clockworks.Tests.csproj -c Release --no-build --nologo + + - name: Pack + run: > + dotnet pack src/Clockworks.csproj + -c Release + --no-build + -p:PackageVersion=${{ steps.vars.outputs.version }} + -o artifacts/packages + + - name: Push package to NuGet + run: > + dotnet nuget push artifacts/packages/*.nupkg + --api-key ${{ secrets.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.vars.outputs.tag }} + generate_release_notes: true + files: | + artifacts/packages/*.nupkg + artifacts/packages/*.snupkg diff --git a/README.md b/README.md index f44b3fb..8869a7a 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,13 @@ var header = new VectorClockMessageHeader( clock: clockA, correlationId: Guid.NewGuid() ); -var headerString = header.ToString(); // "1:1;{correlationId}" (example) +var headerString = header.ToString(); // "1:1;{correlationId:N}" (example) ``` ### Hybrid Logical Clock (HLC) propagation In Clockworks, a "remote timestamp" is the `HlcTimestamp` produced on a different node and carried over the wire -via `HlcMessageHeader` (format: `walltime.counter@node`). The receiver should call `BeforeReceive(...)` with that +via `HlcMessageHeader` (format: `walltime.counter@node[;correlationId;causationId]`). The receiver should call `BeforeReceive(...)` with that timestamp to preserve causality. Note: `HlcTimestamp.ToPackedInt64()`/`FromPackedInt64()` is an optimization encoding with a 48-bit wall time and a 4-bit node id (node id is truncated). Use `WriteTo`/`ReadFrom` when you need a full-fidelity representation. @@ -290,6 +290,10 @@ Clockworks follows Semantic Versioning (SemVer). - Package version is defined in `src/Clockworks.csproj`. - Release tags use the format `vX.Y.Z` (example: `v1.2.0`). - See `CHANGELOG.md` for notable changes. +- CI runs on pushes/PRs to `main` via `.github/workflows/ci.yml`. +- Publishing is automated by `.github/workflows/release.yml`: + - Trigger by pushing a tag like `v1.3.1` to publish to NuGet and create a GitHub Release. + - Or run the workflow manually with `version` input (the matching `vX.Y.Z` tag must already exist). Build-wide defaults are centralized in `Directory.Build.props`.