feat: enhance changelog generator to accept version parameter for PR processing

## CHANGES

- Pass version parameter to changelog generation workflow
- Update ProcessIncomingPRs method to accept version string
- Add commit SHA tracking to prevent duplicate entries
- Modify process-prs flag to require version parameter
- Improve changelog formatting with proper spacing
- Update configuration to use ProcessPRsVersion string field
- Enhance direct commit filtering with SHA exclusion
- Update documentation to reflect version parameter requirement
This commit is contained in:
Kayvan Sylvan
2025-07-21 07:36:30 -07:00
parent b8008a34fb
commit 91c1aca0dd
6 changed files with 59 additions and 48 deletions

View File

@@ -87,7 +87,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
go run ./cmd/generate_changelog --process-prs
go run ./cmd/generate_changelog --process-prs ${{ env.new_tag }}
- name: Commit changes
run: |
# These files are modified by the version bump process

View File

@@ -62,7 +62,7 @@ func (g *Generator) ProcessIncomingPR(prNumber int) error {
}
// ProcessIncomingPRs aggregates all incoming PR files for release and includes direct commits
func (g *Generator) ProcessIncomingPRs() error {
func (g *Generator) ProcessIncomingPRs(version string) error {
files, err := filepath.Glob(filepath.Join(g.cfg.IncomingDir, "*.txt"))
if err != nil {
return fmt.Errorf("failed to scan incoming directory: %w", err)
@@ -86,31 +86,39 @@ func (g *Generator) ProcessIncomingPRs() error {
return fmt.Errorf("encountered errors while processing incoming files: %s", strings.Join(processingErrors, "; "))
}
// Extract PR numbers from processed files to avoid including their commits as "direct"
// Extract PR numbers and their commit SHAs from processed files to avoid including their commits as "direct"
processedPRs := make(map[int]bool)
processedCommitSHAs := make(map[string]bool)
for _, file := range files {
// Extract PR number from filename (e.g., "1640.txt" -> 1640)
filename := filepath.Base(file)
if prNumStr := strings.TrimSuffix(filename, ".txt"); prNumStr != filename {
if prNum, err := strconv.Atoi(prNumStr); err == nil {
processedPRs[prNum] = true
// Fetch the PR to get its commit SHAs
if pr, err := g.ghClient.GetPRWithCommits(prNum); err == nil {
for _, commit := range pr.Commits {
processedCommitSHAs[commit.SHA] = true
}
}
}
}
}
// Now add direct commits since the last release, excluding commits from processed PRs
directCommitsContent, err := g.getDirectCommitsSinceLastRelease(processedPRs)
directCommitsContent, err := g.getDirectCommitsSinceLastRelease(processedPRs, processedCommitSHAs)
if err != nil {
return fmt.Errorf("failed to get direct commits since last release: %w", err)
}
if directCommitsContent != "" {
if content.Len() > 0 {
content.WriteString("\n")
content.WriteString("\n\n")
}
content.WriteString(directCommitsContent)
}
// Check if we have any content at all
if content.Len() == 0 {
if len(files) == 0 {
@@ -121,11 +129,6 @@ func (g *Generator) ProcessIncomingPRs() error {
return nil
}
version, err := g.detectVersion()
if err != nil {
return fmt.Errorf("failed to detect version: %w", err)
}
entry := fmt.Sprintf("## %s (%s)\n\n%s",
version, time.Now().Format("2006-01-02"), content.String())
@@ -163,7 +166,7 @@ func (g *Generator) ProcessIncomingPRs() error {
}
// getDirectCommitsSinceLastRelease gets all direct commits (not part of PRs) since the last release
func (g *Generator) getDirectCommitsSinceLastRelease(processedPRs map[int]bool) (string, error) {
func (g *Generator) getDirectCommitsSinceLastRelease(processedPRs map[int]bool, processedCommitSHAs map[string]bool) (string, error) {
// Get the latest tag to determine what commits are unreleased
latestTag, err := g.gitWalker.GetLatestTag()
if err != nil {
@@ -189,16 +192,23 @@ func (g *Generator) getDirectCommitsSinceLastRelease(processedPRs map[int]bool)
continue
}
// Skip commits that belong to PRs we've already processed from incoming files
// Skip commits that belong to PRs we've already processed from incoming files (by PR number)
if commit.PRNumber > 0 && processedPRs[commit.PRNumber] {
continue
}
// Skip commits whose SHA is already included in processed PRs (this catches commits
// that might not have been detected as part of a PR but are actually in the PR)
if processedCommitSHAs[commit.SHA] {
continue
}
// Only include commits that are NOT part of any PR (direct commits)
if commit.PRNumber == 0 {
directCommits = append(directCommits, commit)
}
}
if len(directCommits) == 0 {
return "", nil // No direct commits
}
@@ -353,7 +363,8 @@ func (g *Generator) insertVersionAtTop(entry string) error {
for insertionPoint < len(contentStr) && (contentStr[insertionPoint] == '\n' || contentStr[insertionPoint] == '\r') {
insertionPoint++
}
newContent = contentStr[:loc[1]] + "\n" + entry + "\n" + contentStr[insertionPoint:]
// Insert with proper spacing: single newline after header, then entry, then newline before existing content
newContent = contentStr[:loc[1]] + "\n\n" + entry + "\n\n" + contentStr[insertionPoint:]
} else {
// Header not found, prepend everything.
newContent = fmt.Sprintf("%s\n\n%s\n\n%s", header, entry, contentStr)

View File

@@ -1,19 +1,19 @@
package config
type Config struct {
RepoPath string
OutputFile string
Limit int
Version string
SaveData bool
CacheFile string
NoCache bool
RebuildCache bool
GitHubToken string
ForcePRSync bool
EnableAISummary bool
IncomingPR int
ProcessPRs bool
IncomingDir string
Push bool
RepoPath string
OutputFile string
Limit int
Version string
SaveData bool
CacheFile string
NoCache bool
RebuildCache bool
GitHubToken string
ForcePRSync bool
EnableAISummary bool
IncomingPR int
ProcessPRsVersion string
IncomingDir string
Push bool
}

View File

@@ -38,13 +38,13 @@ func init() {
rootCmd.Flags().BoolVar(&cfg.ForcePRSync, "force-pr-sync", false, "Force a full PR sync from GitHub (ignores cache age)")
rootCmd.Flags().BoolVar(&cfg.EnableAISummary, "ai-summarize", false, "Generate AI-enhanced summaries using Fabric")
rootCmd.Flags().IntVar(&cfg.IncomingPR, "incoming-pr", 0, "Pre-process PR for changelog (provide PR number)")
rootCmd.Flags().BoolVar(&cfg.ProcessPRs, "process-prs", false, "Process all incoming PR files for release")
rootCmd.Flags().StringVar(&cfg.ProcessPRsVersion, "process-prs", "", "Process all incoming PR files for release (provide version like v1.4.262)")
rootCmd.Flags().StringVar(&cfg.IncomingDir, "incoming-dir", "./cmd/generate_changelog/incoming", "Directory for incoming PR files")
rootCmd.Flags().BoolVar(&cfg.Push, "push", false, "Enable automatic git push after creating an incoming entry")
}
func run(cmd *cobra.Command, args []string) error {
if cfg.IncomingPR > 0 && cfg.ProcessPRs {
if cfg.IncomingPR > 0 && cfg.ProcessPRsVersion != "" {
return fmt.Errorf("--incoming-pr and --process-prs are mutually exclusive flags")
}
@@ -61,8 +61,8 @@ func run(cmd *cobra.Command, args []string) error {
return generator.ProcessIncomingPR(cfg.IncomingPR)
}
if cfg.ProcessPRs {
return generator.ProcessIncomingPRs()
if cfg.ProcessPRsVersion != "" {
return generator.ProcessIncomingPRs(cfg.ProcessPRsVersion)
}
output, err := generator.Generate()

View File

@@ -156,9 +156,9 @@ Add to `internal/config/config.go`:
```go
type Config struct {
// ... existing fields
IncomingPR int // PR number for --incoming-pr
ProcessPRs bool // Flag for --process-prs
IncomingDir string // Directory for incoming files (default: ./cmd/generate_changelog/incoming/)
IncomingPR int // PR number for --incoming-pr
ProcessPRsVersion string // Flag for --process-prs (new version string)
IncomingDir string // Directory for incoming files (default: ./cmd/generate_changelog/incoming/)
}
```
@@ -166,7 +166,7 @@ type Config struct {
```go
rootCmd.Flags().IntVar(&cfg.IncomingPR, "incoming-pr", 0, "Pre-process PR for changelog (provide PR number)")
rootCmd.Flags().BoolVar(&cfg.ProcessPRs, "process-prs", false, "Process all incoming PR files for release")
rootCmd.Flags().StringVar(&cfg.ProcessPRsVersion, "process-prs", "", "Process all incoming PR files for release (provide version like v1.4.262)")
rootCmd.Flags().StringVar(&cfg.IncomingDir, "incoming-dir", "./cmd/generate_changelog/incoming", "Directory for incoming PR files")
```
@@ -314,22 +314,22 @@ Update `.github/workflows/update-version-and-create-tag.yml`.
### Phase 1: Implement Developer Tooling
- [ ] Add new command line flags and configuration
- [ ] Implement `--incoming-pr` functionality
- [ ] Add validation for PR states and git status
- [ ] Create auto-commit logic
- [x] Add new command line flags and configuration
- [x] Implement `--incoming-pr` functionality
- [x] Add validation for PR states and git status
- [x] Create auto-commit logic
### Phase 2: Integration (CI/CD) Readiness
- [ ] Implement `--process-prs` functionality
- [ ] Add CHANGELOG.md insertion logic
- [ ] Update database storage for version entries
- [x] Implement `--process-prs` functionality
- [x] Add CHANGELOG.md insertion logic
- [x] Update database storage for version entries
### Phase 3: Deployment
- [ ] Update GitHub Actions workflow
- [ ] Create developer documentation in ./docs/ directory
- [ ] Test full end-to-end workflow (the PR that includes these modifications can be its first production test)
- [x] Update GitHub Actions workflow
- [x] Create developer documentation in ./docs/ directory
- [x] Test full end-to-end workflow (the PR that includes these modifications can be its first production test)
### Phase 4: Adoption

View File

@@ -94,7 +94,7 @@ Specify custom directory for incoming PR files (default: `./cmd/generate_changel
Process all incoming PR files for release aggregation. Used by CI/CD during release creation.
**Usage**: `./generate_changelog --process-prs`
**Usage**: `./generate_changelog --process-prs {new_version_string}`
**Mutual Exclusivity**: Cannot be used with `--incoming-pr` flag