Skip to content

Commit 4fa30ca

Browse files
Copilotpetesramek
andauthored
Clean up docs, branch strategy, test naming, benchmarks config, and release workflows (#156)
- [x] Update `docs/branch-strategy.md`: change back-merge step from optional to mandatory, update descriptions of `main` and `release/X.Y` accordingly --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: petesramek <2333452+petesramek@users.noreply.github.com>
1 parent eb72d7d commit 4fa30ca

18 files changed

Lines changed: 259 additions & 190 deletions

.github/workflows/publish-documentation.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
on:
44
workflow_dispatch:
5+
push:
6+
branches:
7+
- 'release/**'
8+
paths:
9+
- 'src/**'
10+
- 'api-reference/**'
511

612
permissions:
713
actions: read
@@ -13,8 +19,20 @@ concurrency:
1319
cancel-in-progress: true
1420

1521
jobs:
22+
validate-branch:
23+
name: 'Validate branch'
24+
runs-on: ubuntu-latest
25+
steps:
26+
- name: 'Ensure documentation is published from a release branch'
27+
if: ${{ !startsWith(github.ref_name, 'release/') }}
28+
run: |
29+
echo "Documentation should only be published from 'release/**' branches."
30+
echo "Current branch: '${{ github.ref_name }}'"
31+
exit 1
32+
1633
workflow-variables:
1734
name: 'Workflow variables'
35+
needs: [validate-branch]
1836
runs-on: ubuntu-latest
1937

2038
outputs:

.github/workflows/release.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ permissions:
1313
pages: write
1414
id-token: write
1515
contents: write
16+
administration: write
1617

1718
concurrency:
1819
group: release-${{ github.head_ref || github.ref }}
@@ -314,3 +315,54 @@ jobs:
314315
else
315316
echo "⏭️ Skipped merge to main: **${{ env.current-branch }}** is not the highest release branch." >> $GITHUB_STEP_SUMMARY
316317
fi
318+
319+
create-support-branch:
320+
name: 'Create support branch for ${{ github.ref_name }}'
321+
needs: [workflow-variables, release, versioning]
322+
if: ${{ needs.workflow-variables.outputs.is-release == 'true' }}
323+
runs-on: ubuntu-latest
324+
env:
325+
current-version: ${{ needs.versioning.outputs.friendly-version }}
326+
steps:
327+
- name: 'Checkout ${{ github.head_ref || github.ref }}'
328+
uses: actions/checkout@v6
329+
with:
330+
fetch-depth: 0
331+
332+
- name: 'Resolve support branch name'
333+
id: resolve-support-branch
334+
run: |
335+
major_minor=$(echo "${{ env.current-version }}" | grep -oP '^\d+\.\d+')
336+
echo "support-branch=support/$major_minor" >> $GITHUB_OUTPUT
337+
338+
- name: 'Check if support branch already exists'
339+
id: check-support-branch
340+
run: |
341+
git fetch origin
342+
if git ls-remote --exit-code --heads origin "${{ steps.resolve-support-branch.outputs.support-branch }}" > /dev/null 2>&1; then
343+
echo "support-branch-exists=true" >> $GITHUB_OUTPUT
344+
else
345+
echo "support-branch-exists=false" >> $GITHUB_OUTPUT
346+
fi
347+
348+
- name: 'Create support branch'
349+
if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }}
350+
run: |
351+
git config user.name "$(git log -n 1 --pretty=format:%an)"
352+
git config user.email "$(git log -n 1 --pretty=format:%ae)"
353+
git checkout -b "${{ steps.resolve-support-branch.outputs.support-branch }}"
354+
git push --set-upstream origin "${{ steps.resolve-support-branch.outputs.support-branch }}"
355+
356+
- name: 'Lock support branch'
357+
if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }}
358+
uses: './.github/actions/github/branch-protection/lock'
359+
with:
360+
branch: ${{ steps.resolve-support-branch.outputs.support-branch }}
361+
362+
- name: 'Write support branch summary'
363+
run: |
364+
if [[ "${{ steps.check-support-branch.outputs.support-branch-exists }}" == "false" ]]; then
365+
echo "✅ Created and locked support branch **${{ steps.resolve-support-branch.outputs.support-branch }}**." >> $GITHUB_STEP_SUMMARY
366+
else
367+
echo "⏭️ Support branch **${{ steps.resolve-support-branch.outputs.support-branch }}** already exists." >> $GITHUB_STEP_SUMMARY
368+
fi

CONTRIBUTING.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ In-depth developer guides are in the [`/docs`](./docs/README.md) folder:
1414
- [Branch Strategy](./docs/branch-strategy.md) — branch lifecycle and environments
1515
- [Versioning](./docs/versioning.md) — branch naming and the version pipeline
1616
- [API Documentation](./docs/api-documentation.md) — DocFX and the API reference site
17-
- [Extensibility](./docs/extensibility.md) — how to add new encoding algorithms
1817

1918
## Guidelines
2019

benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,8 @@
1414
<IsPackable>false</IsPackable>
1515
</PropertyGroup>
1616

17-
<ItemGroup>
18-
<Compile Remove="C:\Users\petesramek\.nuget\packages\microsoft.visualstudio.diagnosticshub.benchmarkdotnetdiagnosers\18.3.36812.1\contentFiles\cs\any\BenchmarkProfilerAgentConfig.g.cs" />
19-
<Compile Remove="C:\Users\petesramek\.nuget\packages\microsoft.visualstudio.diagnosticshub.benchmarkdotnetdiagnosers\18.6.37110.2\contentFiles\cs\any\BenchmarkProfilerAgentConfig.g.cs" />
20-
</ItemGroup>
21-
2217
<ItemGroup>
2318
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
24-
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" />
25-
<PackageReference Include="Microsoft.VisualStudio.DiagnosticsHub.BenchmarkDotNetDiagnosers" Version="18.6.37110.2" />
2619
</ItemGroup>
2720

2821
<ItemGroup>

docs/benchmarks.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ Benchmarks use [BenchmarkDotNet](https://benchmarkdotnet.org/). Key packages:
2525
| Package | Purpose |
2626
|---|---|
2727
| `BenchmarkDotNet` | Core benchmarking framework |
28-
| `BenchmarkDotNet.Diagnostics.Windows` | Windows-specific diagnostics (ETW) |
2928

3029
## Writing a New Benchmark
3130

docs/branch-strategy.md

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,80 @@ This document describes the branch model, the purpose of each branch type, and h
77
| Pattern | Purpose | Protected |
88
|---|---|---|
99
| `main` | Latest stable source of truth | ✅ Yes |
10-
| `develop/**` | Active feature development | ❌ No |
11-
| `support/**` | Maintenance / backport development | ❌ No |
10+
| `develop/X.Y` | Active feature development sink for version X.Y | ✅ Yes (PR only) |
11+
| `support/X.Y` | Maintenance / backport development sink for version X.Y | ✅ Yes (PR only) |
12+
| `feature/<id>-<description>` | Individual feature work, merged into `develop/X.Y` via PR | ❌ No |
13+
| `bugfix/<id>-<description>` | Bug fix work, merged into `support/X.Y` via PR | ❌ No |
1214
| `preview/X.Y` | Pre-release stabilization | ✅ Yes (1 approval required) |
1315
| `release/X.Y` | Release stabilization | ✅ Yes (1 approval required) |
1416

1517
## Change Lifecycle
1618

1719
```
1820
1. Feature work
19-
└─ develop/my-feature (or support/my-fix for backports)
21+
└─ feature/123-my-feature
2022
21-
push to src/ → [build.yml] runs: format, compile, test, pack, publish-dev
23+
PR → develop/X.Y
2224
23-
2. Promote to preview
24-
└─ promote-branch.yml (manual) → creates preview/X.Y + PR: develop → preview/X.Y
25+
2. Bug fix work
26+
└─ bugfix/124-my-fix
27+
28+
│ PR → support/X.Y
29+
30+
3. Promote to preview
31+
└─ promote-branch.yml (manual) → creates preview/X.Y + PR: develop/X.Y → preview/X.Y
2532
2633
│ PR open → [pull-request.yml]: compile, test, pack, benchmark (optional)
2734
│ PR merged → [release.yml]: compile, test, pack, publish-NuGet (pre-release), GitHub release, docs
2835
29-
3. Promote to release
36+
4. Promote to release
3037
└─ promote-branch.yml (manual) → creates release/X.Y + PR: preview/X.Y → release/X.Y
3138
3239
│ PR open → [pull-request.yml]
33-
│ PR merged → [release.yml]: publish-NuGet (stable), GitHub release, docs
40+
│ PR merged → [release.yml]: publish-NuGet (stable), GitHub release, docs, creates support/X.Y (first time only)
3441
35-
4. Back-merge (optional)
36-
└─ Manual PR: release/X.Y → main
42+
5. Back-merge to main (automatic, highest version only)
43+
└─ [release.yml] creates PR: release/X.Y → main (only when X.Y is the highest release branch)
44+
45+
│ PR merged → main is updated to the latest stable source
3746
```
3847

3948
## Rules Per Branch Type
4049

4150
### `main`
4251

43-
- Represents the current stable release.
52+
- Represents the latest stable release — always in sync with the highest released version.
4453
- Direct pushes are not allowed (protected).
45-
- Updated by merging from `release/X.Y` after a stable release.
54+
- Updated automatically via a PR created by `release.yml` whenever the highest `release/X.Y` branch publishes a stable release.
55+
- Serves as the baseline for version bumps: new development versions are derived from the state of `main` at the point the previous release left off.
4656
- The `build.yml` workflow does **not** trigger on `main` pushes (branch-ignore pattern excludes `preview/**` and `release/**`, and `main` does not match `src/**` changes by default in the context of the ignore rules — check the workflow for current specifics).
4757

48-
### `develop/**`
58+
### `develop/X.Y`
4959

50-
- Naming convention: `develop/<description>` (e.g. `develop/async-decoder`, `develop/1.2`).
60+
- Naming convention: `develop/<major>.<minor>` (e.g. `develop/1.2`).
61+
- Protected: all changes are merged via pull request from `feature/**` branches.
5162
- The `build.yml` CI pipeline runs on every push to `src/`.
5263
- When ready for stabilization, use `promote-branch.yml` to create a `preview/X.Y` branch and open a PR.
5364

54-
### `support/**`
65+
### `support/X.Y`
5566

56-
- Used for backport and maintenance work against older versions.
57-
- Same CI behavior as `develop/**`.
67+
- Naming convention: `support/<major>.<minor>` (e.g. `support/1.0`).
68+
- Auto-created when the first stable release from `release/X.Y` is published.
69+
- Protected: all changes are merged via pull request from `bugfix/**` branches.
5870
- Can be promoted to `preview/X.Y` for a patch release.
5971

72+
### `feature/<id>-<description>`
73+
74+
- Short-lived branch for individual feature work (e.g. `feature/123-async-decoder`).
75+
- Merged into the appropriate `develop/X.Y` via pull request.
76+
- Not protected — deleted after merging.
77+
78+
### `bugfix/<id>-<description>`
79+
80+
- Short-lived branch for bug fixes (e.g. `bugfix/124-decode-overflow`).
81+
- Merged into the appropriate `support/X.Y` via pull request.
82+
- Not protected — deleted after merging.
83+
6084
### `preview/X.Y`
6185

6286
- Created automatically by `promote-branch.yml`.
@@ -70,6 +94,8 @@ This document describes the branch model, the purpose of each branch type, and h
7094
- Created automatically by `promote-branch.yml` from `preview/X.Y`.
7195
- Locked immediately: requires at least one PR approval.
7296
- On merge, `release.yml` publishes a **stable** NuGet package and a GitHub release.
97+
- After the first stable release, a corresponding `support/X.Y` branch is auto-created.
98+
- When `X.Y` is the highest release branch, `release.yml` automatically opens a PR to merge back into `main`, keeping `main` in sync with the latest stable state.
7399

74100
## Version in Branch Names
75101

@@ -84,4 +110,4 @@ The `X.Y` in `preview/X.Y` and `release/X.Y` drives the version pipeline. See [V
84110

85111
## Locking and Unlocking Branches
86112

87-
`preview/**` and `release/**` branches are locked via the [`github/branch-protection/lock`](./composite-actions.md#githubbranch-protectionlock) composite action when created. The [`github/branch-protection/unlock`](./composite-actions.md#githubbranch-protectionunlock) action temporarily removes protection when a workflow needs to push directly (e.g., `bump-version.yml`). Branches are always re-locked immediately after.
113+
`preview/**` and `release/**` branches are locked via the [`github/branch-protection/lock`](./composite-actions.md#githubbranch-protectionlock) composite action when created. `develop/X.Y` and `support/X.Y` branches must be manually configured as protected in repository settings (PR required, no direct pushes). The [`github/branch-protection/unlock`](./composite-actions.md#githubbranch-protectionunlock) action temporarily removes protection when a workflow needs to push directly (e.g., `bump-version.yml`). Branches are always re-locked immediately after.

docs/extensibility.md

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Extensibility
22

3-
This guide explains how to add new coordinate types, polyline representations, and encoding schemes to PolylineAlgorithm.
3+
This guide explains how to use PolylineAlgorithm with your own coordinate types and polyline representations.
44

55
## Design Overview
66

@@ -105,14 +105,6 @@ public sealed class TuplePolylineDecoder : AbstractPolylineDecoder<string, (doub
105105
}
106106
```
107107

108-
## Supporting a Different Polyline Format
109-
110-
The encoding algorithm itself (Google's encoded polyline algorithm) is implemented in `AbstractPolylineEncoder` and `AbstractPolylineDecoder`. If you need a completely different algorithm:
111-
112-
1. Create a new class in its own file — do **not** modify the existing abstract base classes.
113-
2. Implement `IPolylineEncoder<TCoordinate, TPolyline>` and/or `IPolylineDecoder<TPolyline, TCoordinate>` directly.
114-
3. If the new algorithm shares coordinate-type logic with an existing encoder/decoder, consider extracting that logic into a shared helper in the `PolylineAlgorithm.Utility` project.
115-
116108
## Encoding Options
117109

118110
`PolylineEncodingOptions` controls shared behavior. Configure it via `PolylineEncodingOptionsBuilder`:
@@ -134,13 +126,3 @@ var decoder = new TuplePolylineDecoder(options);
134126
## Extension Methods
135127

136128
The library provides extension methods for `IPolylineEncoder` and `IPolylineDecoder` to support common collection types (`IEnumerable<T>`, arrays, `ReadOnlyMemory<T>`). These are in `PolylineAlgorithm.Extensions`. Your custom implementations automatically benefit from these extension methods as long as you implement the interfaces.
137-
138-
## Checklist for a New Encoding Scheme
139-
140-
- [ ] Create the encoder class in a new file (one class per file).
141-
- [ ] Create the decoder class in a new file.
142-
- [ ] Add XML doc comments to all public members.
143-
- [ ] Add unit tests in `tests/PolylineAlgorithm.Tests/` following the [testing conventions](./testing.md).
144-
- [ ] Add benchmarks in `benchmarks/PolylineAlgorithm.Benchmarks/` following the [benchmarking guide](./benchmarks.md).
145-
- [ ] Update `PublicAPI.Unshipped.txt` with any new public API surface.
146-
- [ ] Add usage samples in `samples/` if the new type is intended for end users.

docs/testing.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,13 @@ Key NuGet packages:
3939

4040
## Naming Conventions
4141

42-
Follow the existing pattern: `{Subject}_{Scenario}_{ExpectedResult}`.
42+
Follow the pattern: `{Subject}_{With_Context}_{Expected_Result}` — every word separated by an underscore.
4343

4444
Examples:
4545

4646
```
4747
Decode_With_Null_Polyline_Throws_ArgumentNullException
48-
Normalize_ZeroValue_ReturnsZero
48+
Normalize_With_Zero_Value_Returns_Zero
4949
Normalize_With_Value_And_Precision_Returns_Expected_Normalized_Value
5050
```
5151

@@ -75,7 +75,7 @@ public sealed class MyClassTests {
7575
/// Tests that <see cref="MyClass.MyMethod"/> returns the expected result.
7676
/// </summary>
7777
[TestMethod]
78-
public void MyMethod_WithValidInput_ReturnsExpected() {
78+
public void MyMethod_With_Valid_Input_Returns_Expected() {
7979
// Arrange
8080
var sut = new MyClass();
8181

tests/PolylineAlgorithm.Tests/Abstraction/AbstractPolylineDecoderTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public void Decode_With_Invalid_Character_Polyline_Throws_InvalidPolylineExcepti
7070
/// Tests that Decode with a valid polyline returns the expected coordinates.
7171
/// </summary>
7272
[TestMethod]
73-
public void Decode_ValidPolyline_ReturnsExpectedCoordinates() {
73+
public void Decode_With_Valid_Polyline_Returns_Expected_Coordinates() {
7474
// Arrange
7575
TestStringDecoder decoder = new();
7676
string polyline = StaticValueProvider.Valid.GetPolyline();
@@ -91,7 +91,7 @@ public void Decode_ValidPolyline_ReturnsExpectedCoordinates() {
9191
/// Tests that the options constructor with null throws <see cref="ArgumentNullException"/>.
9292
/// </summary>
9393
[TestMethod]
94-
public void Constructor_WithNullOptions_ThrowsArgumentNullException() {
94+
public void Constructor_With_Null_Options_Throws_ArgumentNullException() {
9595
// Act & Assert
9696
ArgumentNullException ex = Assert.ThrowsExactly<ArgumentNullException>(() => new TestStringDecoderWithOptions(null!));
9797
Assert.AreEqual("options", ex.ParamName);
@@ -101,7 +101,7 @@ public void Constructor_WithNullOptions_ThrowsArgumentNullException() {
101101
/// Tests that the Options property returns the configured options.
102102
/// </summary>
103103
[TestMethod]
104-
public void Options_Default_ReturnsDefaultOptions() {
104+
public void Options_With_Default_Returns_Default_Options() {
105105
// Arrange
106106
TestStringDecoder decoder = new();
107107

@@ -114,7 +114,7 @@ public void Options_Default_ReturnsDefaultOptions() {
114114
/// Tests that the options constructor stores the provided options.
115115
/// </summary>
116116
[TestMethod]
117-
public void Constructor_WithOptions_StoresOptions() {
117+
public void Constructor_With_Options_Stores_Options() {
118118
// Arrange
119119
PolylineEncodingOptions options = PolylineEncodingOptionsBuilder.Create()
120120
.WithPrecision(7)
@@ -131,7 +131,7 @@ public void Constructor_WithOptions_StoresOptions() {
131131
/// Tests that Decode with a pre-cancelled token throws <see cref="OperationCanceledException"/>.
132132
/// </summary>
133133
[TestMethod]
134-
public void Decode_PreCancelledToken_ThrowsOperationCanceledException() {
134+
public void Decode_With_Pre_Cancelled_Token_Throws_OperationCanceledException() {
135135
// Arrange
136136
TestStringDecoder decoder = new();
137137
string polyline = StaticValueProvider.Valid.GetPolyline();

0 commit comments

Comments
 (0)