|
2 | 2 |
|
3 | 3 | This sub-skill defines rules for filtering commits to include in the changelog. |
4 | 4 |
|
5 | | -## Filter Out Commits Already in Start Tag |
| 5 | +## Understanding the Branch Model |
6 | 6 |
|
7 | | -**CRITICAL: Skip commits that are already included in the start tag to avoid duplicates.** |
| 7 | +PowerToys uses a release branch model where fixes are cherry-picked from main: |
8 | 8 |
|
9 | | -This happens when: |
10 | | -- Cherry-picks from main to stable branch |
11 | | -- Backported fixes that were already released |
12 | | -- Commits included in a previous release |
13 | | - |
14 | | -### Method 1: Git Merge-Base (Local, Most Reliable) |
15 | | - |
16 | | -```powershell |
17 | | -$startTag = "v0.96.0" |
18 | | -$commitSha = "abc1234" |
19 | | -
|
20 | | -# Check if commit is ancestor of (already included in) start tag |
21 | | -# Returns exit code 0 if true, 1 if false |
22 | | -git merge-base --is-ancestor $commitSha $startTag |
23 | | -if ($LASTEXITCODE -eq 0) { |
24 | | - Write-Host "SKIP: Commit $commitSha already in $startTag" |
25 | | -} else { |
26 | | - Write-Host "PROCESS: Commit $commitSha is new" |
27 | | -} |
| 9 | +``` |
| 10 | +main: A---B---C---D---E---F---G---H (HEAD) |
| 11 | + \ |
| 12 | +release: X---Y---Z (v0.96.1 tag) |
| 13 | + ↑ |
| 14 | + (X, Y are cherry-picks of C, E from main) |
28 | 15 | ``` |
29 | 16 |
|
30 | | -### Method 2: GitHub API Compare (Auto-Filtered) |
31 | | - |
32 | | -```powershell |
33 | | -# The compare API already handles this - it returns only commits |
34 | | -# that are in the "head" but NOT in the "base" |
35 | | -$compare = gh api repos/microsoft/PowerToys/compare/v0.96.0...v0.96.1 | ConvertFrom-Json |
| 17 | +**Key insight:** When comparing `v0.96.1...main`: |
| 18 | +- The release tag (v0.96.1) is on a **different branch** than main |
| 19 | +- GitHub compare finds the merge-base and returns commits on main after that point |
| 20 | +- Commits C and E appear in the results even though they were cherry-picked to release as X and Y |
| 21 | +- **The SHAs are different**, so SHA-based filtering won't work! |
36 | 22 |
|
37 | | -# These commits are guaranteed to be NEW (not in v0.96.0) |
38 | | -$newCommits = $compare.commits |
39 | | -Write-Host "Found $($newCommits.Count) new commits not in start tag" |
40 | | -``` |
| 23 | +## ⚠️ CRITICAL: Filter by PR Number, Not SHA |
41 | 24 |
|
42 | | -### Method 3: Check Tags Containing Commit |
| 25 | +Since cherry-picks have different SHAs, you **MUST** check by PR number: |
43 | 26 |
|
44 | 27 | ```powershell |
45 | | -$commitSha = "abc1234" |
46 | | -$tagsContaining = git tag --contains $commitSha |
47 | | -if ($tagsContaining -match "v0\.96\.0") { |
48 | | - Write-Host "SKIP: Commit already released in v0.96.0" |
| 28 | +# Extract PR number from commit message and check if it exists in the release tag |
| 29 | +$prNumber = "43785" |
| 30 | +$startTag = "v0.96.1" |
| 31 | +
|
| 32 | +# Search the release branch for this PR number in commit messages |
| 33 | +$cherryPicked = git log $startTag --oneline --grep="#$prNumber" |
| 34 | +if ($cherryPicked) { |
| 35 | + Write-Host "SKIP: PR #$prNumber was cherry-picked to $startTag" |
| 36 | +} else { |
| 37 | + Write-Host "INCLUDE: PR #$prNumber is new since $startTag" |
49 | 38 | } |
50 | 39 | ``` |
51 | 40 |
|
52 | | -## Batch Filter Script |
| 41 | +## Complete Filtering Workflow |
53 | 42 |
|
54 | 43 | ```powershell |
55 | | -$startTag = "v0.96.0" |
56 | | -$endTag = "v0.96.1" |
57 | | -
|
58 | | -# Get commits from compare (already filtered - only new commits) |
59 | | -$newCommits = gh api repos/microsoft/PowerToys/compare/$startTag...$endTag --jq '.commits[] | {sha: .sha, message: .commit.message}' | ConvertFrom-Json |
60 | | -
|
61 | | -# Additional validation: filter out any merge commits that reference old PRs |
62 | | -$filteredCommits = $newCommits | Where-Object { |
63 | | - $sha = $_.sha |
64 | | - # Double-check: ensure commit is not ancestor of start tag |
65 | | - git merge-base --is-ancestor $sha $startTag 2>$null |
66 | | - $LASTEXITCODE -ne 0 # Keep only if NOT an ancestor |
| 44 | +$startTag = "v0.96.1" # Release tag (on release branch) |
| 45 | +$endRef = "main" # Target (main branch) |
| 46 | +
|
| 47 | +# Step 1: Get all commits from main since the merge-base with release tag |
| 48 | +$commits = gh api "repos/microsoft/PowerToys/compare/$startTag...$endRef" ` |
| 49 | + --jq '.commits[] | {sha: .sha, message: .commit.message}' | ConvertFrom-Json |
| 50 | +
|
| 51 | +# Step 2: Build list of PR numbers already in the release tag |
| 52 | +$releasePRs = git log $startTag --oneline | Select-String -Pattern '#(\d+)' -AllMatches | |
| 53 | + ForEach-Object { $_.Matches.Groups[1].Value } | Sort-Object -Unique |
| 54 | +
|
| 55 | +Write-Host "PRs already in $startTag : $($releasePRs.Count)" |
| 56 | +
|
| 57 | +# Step 3: Filter commits - skip if PR was cherry-picked to release |
| 58 | +$newCommits = @() |
| 59 | +foreach ($commit in $commits) { |
| 60 | + if ($commit.message -match '#(\d+)') { |
| 61 | + $prNumber = $matches[1] |
| 62 | + if ($releasePRs -contains $prNumber) { |
| 63 | + Write-Host "SKIP: PR #$prNumber already in $startTag (cherry-picked)" |
| 64 | + continue |
| 65 | + } |
| 66 | + } |
| 67 | + $newCommits += $commit |
67 | 68 | } |
68 | 69 |
|
69 | | -Write-Host "After filtering: $($filteredCommits.Count) commits to process" |
| 70 | +Write-Host "New commits to process: $($newCommits.Count)" |
70 | 71 | ``` |
71 | 72 |
|
| 73 | +## Why SHA-Based Methods Don't Work Here |
| 74 | + |
| 75 | +| Method | Works for same branch? | Works for cross-branch (cherry-picks)? | |
| 76 | +|--------|------------------------|----------------------------------------| |
| 77 | +| `git merge-base --is-ancestor` | ✅ Yes | ❌ No - different SHAs | |
| 78 | +| `git tag --contains` | ✅ Yes | ❌ No - tag is on different branch | |
| 79 | +| GitHub Compare API | ✅ Yes | ❌ No - returns commits by SHA | |
| 80 | +| **PR number matching** | ✅ Yes | ✅ **Yes** | |
| 81 | + |
72 | 82 | ## Skip Rules Summary |
73 | 83 |
|
74 | | -| Condition | Action | |
75 | | -|-----------|--------| |
76 | | -| `git merge-base --is-ancestor $sha $startTag` returns 0 | SKIP - already in start tag | |
77 | | -| Commit message contains "cherry-pick" + old PR number | SKIP - likely backport | |
78 | | -| PR was merged before start tag date | SKIP - old PR | |
79 | | -| Same PR number already processed | SKIP - duplicate | |
| 84 | +| Priority | Condition | Action | |
| 85 | +|----------|-----------|--------| |
| 86 | +| 1 | PR number found in `git log $startTag --grep="#$prNumber"` | **SKIP** - cherry-picked | |
| 87 | +| 2 | Same PR number already processed in this run | **SKIP** - duplicate | |
| 88 | +| 3 | Bot author (dependabot, etc.) | **SKIP** - unless user-visible | |
| 89 | +| 4 | Internal-only change (CI, tests, refactor) | Move to **Development** section | |
80 | 90 |
|
81 | 91 | ## User-Facing vs Non-User-Facing |
82 | 92 |
|
|
0 commit comments