diff --git a/.chronicle.yaml b/.chronicle.yaml index be7e28b..010b184 100644 --- a/.chronicle.yaml +++ b/.chronicle.yaml @@ -1,4 +1,2 @@ -github: - include-prs: true log: - level: trace \ No newline at end of file + level: trace diff --git a/Makefile b/Makefile index 0ed509e..8df2a4c 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ COVER_REPORT = $(RESULTSDIR)/unit-coverage-details.txt COVER_TOTAL = $(RESULTSDIR)/unit-coverage-summary.txt LINTCMD = $(TEMPDIR)/golangci-lint run --tests=false --timeout=2m --config .golangci.yaml # the quality gate lower threshold for unit test total % coverage (by function statements) -COVERAGE_THRESHOLD := 25 +COVERAGE_THRESHOLD := 50 # CI cache busting values; change these if you want CI to not use previous stored cache FIXTURE_CACHE_BUSTER = "88738d2f" diff --git a/README.md b/README.md index feddc2d..756feaf 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,34 @@ [![Slack Invite](https://img.shields.io/badge/Slack-Join-blue?logo=slack)](https://anchore.com/slack) -A fast changelog generator that sources changes from GitHub PRs and issues, organized by labels. +**A fast changelog generator that sources changes from GitHub PRs and issues, organized by labels.** + +Create a changelog from the last GitHib release until the current git HEAD tag/commit for the git repo in the current directory: ```bash -# create a changelog from the last GitHib release until the current git HEAD tag/commit -# for the git repo in the current directory chronicle +``` -# create a changelog with all changes from v0.16.0 until current git HEAD tag/commit -# for the git repo in the current directory +Create a changelog with all changes from v0.16.0 until current git HEAD tag/commit for the git repo in the current directory: +```bash chronicle --since-tag v0.16.0 +``` -# create a changelog between two specific tags for a repo at the given path +Create a changelog between two specific tags for a repo at the given path +```bash chronicle --since-tag v0.16.0 --until-tag v0.18.0 ./path/to/git/repo ``` +Create a changelog and guess the release version from the set of changes in the changelog +```bash +chronicle --speculate-next-version +``` + +Just guess the next release version based on the set of changes (don't create a changelog) +```bash +chronicle next-version +``` + ## Installation ```bash @@ -35,3 +48,154 @@ curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b ``` +## Configuration + +Configuration search paths: + - `.chronicle.yaml` + - `.chronicle/config.yaml` + - `~/.chronicle.yaml` + - `/chronicle/config.yaml` + +### Default values + +Configuration options (example values are the default): + +```yaml +# the output format of the changelog +# same as -o, --output, and CHRONICLE_OUTPUT env var +output: md + +# suppress all logging output +# same as -q ; CHRONICLE_QUIET env var +quiet: false + +# all logging options +log: + # use structured logging + # same as CHRONICLE_LOG_STRUCTURED env var + structured: false + + # the log level + # same as CHRONICLE_LOG_LEVEL env var + level: "warn" + + # location to write the log file (default is not to have a log file) + # same as CHRONICLE_LOG_FILE env var + file: "" + +# guess what the next release version is based on the current version and set of changes (cannot be used with --until-tag) +# same as --speculate-next-version / -n ; CHRONICLE_SPECULATE_NEXT_VERSION env var +speculate-next-version: true + +# override the starting git tag for the changelog (default is to detect the last release automatically) +# same as --since-tag / -s ; CHRONICLE_SINCE_TAG env var +since-tag: "" + +# override the ending git tag for the changelog (default is to use the tag or commit at git HEAD) +# same as --until-tag / -u ; CHRONICLE_SINCE_TAG env var +until-tag: "" + +# if the current release version is < v1.0 then breaking changes will bump the minor version field +# same as CHRONICLE_ENFORCE_V0 env var +enforce-v0: false + +# the title used for the changelog +# same as CHRONICLE_TITLE +title: Changelog + +# all github-related settings +github: + + # the github host to use (override for github enterprise deployments) + # same as CHRONICLE_GITHUB_HOST env var + host: github.com + + # do not consider any issues or PRs with any of the given labels + # same as CHRONICLE_GITHUB_EXCLUDE_LABELS env var + exclude-labels: + - duplicate + - question + - invalid + - wontfix + - wont-fix + - release-ignore + - changelog-ignore + - ignore + + # consider merged PRs as candidate changelog entries (must have a matching label from a 'github.changes' entry) + # same as CHRONICLE_GITHUB_INCLUDE_PRS env var + include-prs: true + + # consider closed issues as candidate changelog entries (must have a matching label from a 'github.changes' entry) + # same as CHRONICLE_GITHUB_INCLUDE_ISSUES env var + include-issues: true + + # issues can only be considered for changelog candidates if they have linked PRs that are merged (note: does NOT require github.include-issues to be set) + # same as CHRONICLE_GITHUB_ISSUES_REQUIRE_LINKED_PRS env var + issues-require-linked-prs: false + + # list of definitions of what labels applied to issues or PRs constitute a changelog entry. These entries also dictate + # the changelog section, the changelog title, and the semver field that best represents the class of change. + # note: cannot be set via environment variables + changes: [......] # See "Default GitHub change definitions" section for more details + +``` + +### Default GitHub change definitions + +The `github.changes` configurable is a list of mappings, each that take the following fields: + +- `name`: _[string]_ singular, lowercase, hyphen-separated (no spaces) name that best represents the change (e.g. "breaking-change", "security", "added-feature", "enhancement", "new-feature", etc). +- `title`: _[string]_ title of the section in the changelog listing all entries. +- `semver-field`: _[string]_ change entries will bump the respective semver field when guessing the next release version. Allowable values: `major`, `minor`, or `patch`. +- `labels`: _[list of strings]_ all issue or PR labels that should match this change section. + +The default value for `github.changes` is: + +```yaml +- name: security-fixes + title: Security Fixes + semver-field: patch + labels: + - security + - vulnerability + +- name: added-feature + title: Added Features + semver-field: minor + labels: + - enhancement + - feature + - minor + +- name: bug-fix + title: Bug Fixes + semver-field: patch + labels: + - bug + - fix + - bug-fix + - patch + +- name: breaking-feature + title: Breaking Changes + semver-field: major + labels: + - breaking + - backwards-incompatible + - breaking-change + - breaking-feature + - major + +- name: removed-feature + title: Removed Features + semver-field: major + labels: + - removed + +- name: deprecated-feature + title: Deprecated Features + semver-field: minor + labels: + - deprecated +``` diff --git a/chronicle/release/summarizer/github/gh_pull_request.go b/chronicle/release/summarizer/github/gh_pull_request.go index 64a8ec6..c6053dc 100644 --- a/chronicle/release/summarizer/github/gh_pull_request.go +++ b/chronicle/release/summarizer/github/gh_pull_request.go @@ -99,6 +99,18 @@ func prsWithoutClosedLinkedIssue() prFilter { } } +func prsWithClosedLinkedIssue() prFilter { + return func(pr ghPullRequest) bool { + for _, i := range pr.LinkedIssues { + if i.Closed { + return true + } + } + log.Tracef("PR #%d filtered out: does not have a closed linked issue", pr.Number) + return false + } +} + func prsWithoutOpenLinkedIssue() prFilter { return func(pr ghPullRequest) bool { for _, i := range pr.LinkedIssues { diff --git a/chronicle/release/summarizer/github/summarizer.go b/chronicle/release/summarizer/github/summarizer.go index 2977ad9..12dcbe1 100644 --- a/chronicle/release/summarizer/github/summarizer.go +++ b/chronicle/release/summarizer/github/summarizer.go @@ -15,11 +15,12 @@ import ( var _ release.Summarizer = (*Summarizer)(nil) type Config struct { - Host string - IncludeIssues bool - IncludePRs bool - ExcludeLabels []string - ChangeTypesByLabel change.TypeSet + Host string + IncludeIssues bool + IncludePRs bool + ExcludeLabels []string + ChangeTypesByLabel change.TypeSet + IssuesRequireLinkedPR bool } type Summarizer struct { @@ -87,53 +88,106 @@ func (s *Summarizer) LastRelease() (*release.Release, error) { func (s *Summarizer) Changes(sinceRef, untilRef string) ([]change.Change, error) { var changes []change.Change - if s.config.IncludePRs { - prChanges, err := s.changesFromPRs(sinceRef, untilRef) + sinceTag, err := git.SearchForTag(s.repoPath, sinceRef) + if err != nil { + return nil, err + } + + var untilTag *git.Tag + if untilRef != "" { + untilTag, err = git.SearchForTag(s.repoPath, untilRef) if err != nil { return nil, err } - changes = append(changes, prChanges...) } - if s.config.IncludeIssues { - issueChanges, err := s.changesFromIssues(sinceRef, untilRef) + if s.config.IncludePRs || (s.config.IssuesRequireLinkedPR && s.config.IncludeIssues) { + allMergedPRs, err := fetchMergedPRs(s.userName, s.repoName) if err != nil { return nil, err } - changes = append(changes, issueChanges...) + + log.Debugf("total merged PRs discovered: %d", len(allMergedPRs)) + + if s.config.IncludePRs { + changes = append(changes, changesFromPRs(s.config, allMergedPRs, sinceTag, untilTag)...) + } + if s.config.IssuesRequireLinkedPR && s.config.IncludeIssues { + // extract closed linked issues with closed PRs from the PR list. Why do this here? + // githubs ontology has PRs as the source of truth for issue linking. Linked PR information + // is not available on the issue itself. + extractedIssues := issuesExtractedFromPRs(s.config, allMergedPRs, sinceTag, untilTag) + changes = append(changes, createChangesFromIssues(s.config, extractedIssues)...) + } + } + + if s.config.IncludeIssues && !s.config.IssuesRequireLinkedPR { + allClosedIssues, err := fetchClosedIssues(s.userName, s.repoName) + if err != nil { + return nil, err + } + + log.Debugf("total closed issues discovered: %d", len(allClosedIssues)) + + changes = append(changes, changesFromIssues(s.config, allClosedIssues, sinceTag, untilTag)...) } return changes, nil } -func (s *Summarizer) changesFromPRs(sinceRef, untilRef string) ([]change.Change, error) { - allMergedPRs, err := fetchMergedPRs(s.userName, s.repoName) - if err != nil { - return nil, err +func issuesExtractedFromPRs(config Config, allMergedPRs []ghPullRequest, sinceTag, untilTag *git.Tag) []ghIssue { + // this represents the traits we wish to filter down to (not out). + prFilters := []prFilter{ + prsAfter(sinceTag.Timestamp.UTC()), + // PRs with these labels should explicitly be used in the changelog directly (not the corresponding linked issue) + prsWithoutLabel(config.ChangeTypesByLabel.Names()...), + prsWithClosedLinkedIssue(), + } + + if untilTag != nil { + prFilters = append(prFilters, prsAtOrBefore(untilTag.Timestamp.UTC())) } - log.Debugf("total merged PRs discovered: %d", len(allMergedPRs)) + filteredPRs := filterPRs(allMergedPRs, prFilters...) + extractedIssues := uniqueIssuesFromPRs(filteredPRs) - sinceTag, err := git.SearchForTag(s.repoPath, sinceRef) - if err != nil { - return nil, err + // this represents the traits we wish to filter down to (not out). + issueFilters := []issueFilter{ + issuesAfter(sinceTag.Timestamp), + issuesWithLabel(config.ChangeTypesByLabel.Names()...), + issuesWithoutLabel(config.ExcludeLabels...), } - var untilTag *git.Tag - if untilRef != "" { - untilTag, err = git.SearchForTag(s.repoPath, untilRef) - if err != nil { - return nil, err + if untilTag != nil { + issueFilters = append(issueFilters, issuesAtOrBefore(untilTag.Timestamp)) + } + + return filterIssues(extractedIssues, issueFilters...) +} + +func uniqueIssuesFromPRs(prs []ghPullRequest) []ghIssue { + issueNumbers := make(map[int]struct{}) + var issues []ghIssue + for _, pr := range prs { + for _, issue := range pr.LinkedIssues { + if _, ok := issueNumbers[issue.Number]; ok { + continue + } + issues = append(issues, issue) + issueNumbers[issue.Number] = struct{}{} } } + return issues +} - filteredPRs := filterPRs(allMergedPRs, standardPrFilters(s.config, sinceTag, untilTag)...) +func changesFromPRs(config Config, allMergedPRs []ghPullRequest, sinceTag, untilTag *git.Tag) []change.Change { + filteredPRs := filterPRs(allMergedPRs, standardPrFilters(config, sinceTag, untilTag)...) log.Debugf("PRs contributing to changelog: %d", len(filteredPRs)) var summaries []change.Change for _, pr := range filteredPRs { - changeTypes := s.config.ChangeTypesByLabel.ChangeTypes(pr.Labels...) + changeTypes := config.ChangeTypesByLabel.ChangeTypes(pr.Labels...) if len(changeTypes) > 0 { summaries = append(summaries, change.Change{ Text: pr.Title, @@ -146,7 +200,7 @@ func (s *Summarizer) changesFromPRs(sinceRef, untilRef string) ([]change.Change, }, { Text: pr.Author, - URL: fmt.Sprintf("https://%s/%s", s.config.Host, pr.Author), + URL: fmt.Sprintf("https://%s/%s", config.Host, pr.Author), }, }, EntryType: "githubPR", @@ -154,39 +208,22 @@ func (s *Summarizer) changesFromPRs(sinceRef, untilRef string) ([]change.Change, }) } } - return summaries, nil + return summaries } -func (s *Summarizer) changesFromIssues(sinceRef, untilRef string) ([]change.Change, error) { - allClosedIssues, err := fetchClosedIssues(s.userName, s.repoName) - if err != nil { - return nil, err - } - - log.Debugf("total closed issues discovered: %d", len(allClosedIssues)) - - sinceTag, err := git.SearchForTag(s.repoPath, sinceRef) - if err != nil { - return nil, err - } - - var untilTag *git.Tag - if untilRef != "" { - untilTag, err = git.SearchForTag(s.repoPath, untilRef) - if err != nil { - return nil, err - } - } - - filteredIssues := filterIssues(allClosedIssues, standardIssueFilters(s.config, sinceTag, untilTag)...) +func changesFromIssues(config Config, allClosedIssues []ghIssue, sinceTag, untilTag *git.Tag) []change.Change { + filteredIssues := filterIssues(allClosedIssues, standardIssueFilters(config, sinceTag, untilTag)...) log.Debugf("issues contributing to changelog: %d", len(filteredIssues)) - var summaries []change.Change - for _, issue := range filteredIssues { - changeTypes := s.config.ChangeTypesByLabel.ChangeTypes(issue.Labels...) + return createChangesFromIssues(config, filteredIssues) +} + +func createChangesFromIssues(config Config, issues []ghIssue) (changes []change.Change) { + for _, issue := range issues { + changeTypes := config.ChangeTypesByLabel.ChangeTypes(issue.Labels...) if len(changeTypes) > 0 { - summaries = append(summaries, change.Change{ + changes = append(changes, change.Change{ Text: issue.Title, ChangeTypes: changeTypes, Timestamp: issue.ClosedAt, @@ -202,7 +239,7 @@ func (s *Summarizer) changesFromIssues(sinceRef, untilRef string) ([]change.Chan }) } } - return summaries, nil + return changes } func extractGithubUserAndRepo(u string) (string, string) { diff --git a/chronicle/release/summarizer/github/summarizer_test.go b/chronicle/release/summarizer/github/summarizer_test.go index 39022b9..009cd1e 100644 --- a/chronicle/release/summarizer/github/summarizer_test.go +++ b/chronicle/release/summarizer/github/summarizer_test.go @@ -316,7 +316,7 @@ func Test_prFilters(t *testing.T) { input := []ghPullRequest{ // keep prBugAfterLastRelease, - prBugAtEndTag, + prBugAtEndTag, // edge case // filter out prAfterLastRelease, @@ -385,3 +385,222 @@ func Test_prFilters(t *testing.T) { }) } } + +func Test_changesFromIssuesExtractedFromPRs(t *testing.T) { + //log.Log = logger.NewLogrusLogger(logger.LogrusConfig{ + // EnableConsole: true, + // EnableFile: false, + // Structured: false, + // Level: logrus.TraceLevel, + //}) + patch := change.NewType("patch", change.SemVerPatch) + feature := change.NewType("added-feature", change.SemVerMinor) + breaking := change.NewType("breaking-change", change.SemVerMajor) + + changeTypeSet := change.TypeSet{ + "bug": patch, + "fix": patch, + "feature": feature, + "breaking": breaking, + "removed": breaking, + "breaking-change": breaking, + } + + timeStart := time.Date(2021, time.September, 16, 19, 34, 0, 0, time.UTC) + timeAfter := timeStart.Add(2 * time.Hour) + timeBefore := timeStart.Add(-2 * time.Hour) + timeEnd := timeStart.Add(5 * time.Hour) + timeAfterEnd := timeEnd.Add(3 * time.Hour) + + sinceTag := &git.Tag{ + Name: "v0.1.0", + Timestamp: timeStart, + } + + untilTag := &git.Tag{ + Name: "v0.2.0", + Timestamp: timeEnd, + } + + prBugAfterLastRelease := ghPullRequest{ + Title: "pr bug after starting tag", + Number: 1, + MergedAt: timeAfter, + Labels: []string{"bug"}, + } + + prBugAtLastRelease := ghPullRequest{ + Title: "pr bug at (within) last release", + Number: 10, + MergedAt: timeStart, + Labels: []string{"bug"}, + } + + prBugAtEndTag := ghPullRequest{ + Title: "pr bug at (within) end tag", + Number: 11, + MergedAt: timeEnd, + Labels: []string{"bug"}, + } + + prAfterLastRelease := ghPullRequest{ + Title: "pr after starting tag", + Number: 9, + MergedAt: timeAfter, + } + + prBugBeforeLastRelease := ghPullRequest{ + Title: "pr bug before starting tag", + Number: 2, + MergedAt: timeBefore, + Labels: []string{"bug"}, + } + + issueClosedAfterLastRelease := ghIssue{ + Title: "issue bug (closed after last release)", + Number: 4, + ClosedAt: timeAfter, + Closed: true, + Labels: []string{"bug"}, + } + + issueClosedAfterLastRelease2 := ghIssue{ + Title: "issue bug (closed after last release) -- 2", + Number: 13, + ClosedAt: timeAfter, + Closed: true, + Labels: []string{"bug"}, + } + + issueClosedAfterLastRelease3 := ghIssue{ + Title: "issue feature (closed after last release) -- 3", + Number: 15, + ClosedAt: timeAfterEnd, + Closed: true, + Labels: []string{"feature"}, + } + + issueOpen := ghIssue{ + Title: "issue bug (open)", + Number: 5, + Closed: false, + Labels: []string{"bug"}, + } + + prBugAfterLastReleaseWithOpenLinkedIssue := ghPullRequest{ + Title: "pr bug after starting tag (w/ open linked issue)", + Number: 3, + MergedAt: timeAfter, + Labels: []string{"bug"}, + LinkedIssues: []ghIssue{issueOpen}, + } + + prAfterLastReleaseWithOpenLinkedIssue := ghPullRequest{ + Title: "pr after starting tag (w/ open linked issue)", + Number: 6, + MergedAt: timeAfter, + LinkedIssues: []ghIssue{issueOpen}, + } + + prBugAfterLastReleaseWithClosedLinkedIssue := ghPullRequest{ + Title: "pr bug after starting tag (w/ closed linked issue)", + Number: 7, + MergedAt: timeAfter, + Labels: []string{"bug"}, + LinkedIssues: []ghIssue{issueClosedAfterLastRelease}, + } + + prAfterLastReleaseWithClosedLinkedIssue := ghPullRequest{ + Title: "pr after starting tag (w/ closed linked issue)", + Number: 8, + MergedAt: timeAfter, + LinkedIssues: []ghIssue{issueClosedAfterLastRelease2}, + } + + prFeatureAfterEndTag := ghPullRequest{ + Title: "pr feature after end tag", + Number: 12, + MergedAt: timeAfterEnd, + Labels: []string{"feature"}, + } + + prAfterEndTagWithClosedLinkedIssue := ghPullRequest{ + Title: "pr after end tag (w/ closed linked issue)", + Number: 14, + MergedAt: timeAfterEnd, + Labels: nil, + LinkedIssues: []ghIssue{issueClosedAfterLastRelease3}, + } + + input := []ghPullRequest{ + // keep + prAfterLastReleaseWithClosedLinkedIssue, // = issue "issueClosedAfterLastRelease2" + prAfterEndTagWithClosedLinkedIssue, // = issue "issueClosedAfterLastRelease3" + // filter out + prAfterLastRelease, + prBugAtLastRelease, // edge case + prBugBeforeLastRelease, + prBugAfterLastReleaseWithOpenLinkedIssue, + prAfterLastReleaseWithOpenLinkedIssue, + prBugAfterLastRelease, + prBugAtEndTag, // edge case + prAfterLastReleaseWithClosedLinkedIssue, + prFeatureAfterEndTag, + // why not this one? PRs with these labels should explicitly be used in the changelog directly (not the corresponding linked issue) + prBugAfterLastReleaseWithClosedLinkedIssue, // = issue "issueClosedAfterLastRelease", + } + + tests := []struct { + name string + since *git.Tag + until *git.Tag + config Config + inputPrs []ghPullRequest + expectedIssues []ghIssue + }{ + { + name: "keep changes between the tags for only closed PRs with linked closed issues", + since: sinceTag, + until: untilTag, + config: Config{ + ExcludeLabels: nil, + ChangeTypesByLabel: changeTypeSet, + }, + inputPrs: input, + expectedIssues: []ghIssue{ + issueClosedAfterLastRelease2, + }, + }, + { + name: "keep changes after start tag", + since: sinceTag, + config: Config{ + ExcludeLabels: nil, + ChangeTypesByLabel: changeTypeSet, + }, + inputPrs: input, + expectedIssues: []ghIssue{ + issueClosedAfterLastRelease2, + issueClosedAfterLastRelease3, + }, + }, + { + name: "keep only added features after start tag", + since: sinceTag, + config: Config{ + ExcludeLabels: []string{"bug"}, + ChangeTypesByLabel: changeTypeSet, + }, + inputPrs: input, + expectedIssues: []ghIssue{ + issueClosedAfterLastRelease3, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.ElementsMatch(t, tt.expectedIssues, issuesExtractedFromPRs(tt.config, tt.inputPrs, tt.since, tt.until)) + }) + } +} diff --git a/internal/config/github.go b/internal/config/github.go index 8f2ea65..0d22171 100644 --- a/internal/config/github.go +++ b/internal/config/github.go @@ -9,18 +9,19 @@ import ( ) type githubSummarizer struct { - Host string `yaml:"host" json:"host" mapstructure:"host"` - Changes []githubChange `yaml:"changes" json:"changes" mapstructure:"changes"` - ExcludeLabels []string `yaml:"exclude-labels" json:"exclude-labels" mapstructure:"exclude-labels"` - IncludePRs bool `yaml:"include-prs" json:"include-prs" mapstructure:"include-prs"` - IncludeIssues bool `yaml:"include-issues" json:"include-issues" mapstructure:"include-issues"` + Host string `yaml:"host" json:"host" mapstructure:"host"` + ExcludeLabels []string `yaml:"exclude-labels" json:"exclude-labels" mapstructure:"exclude-labels"` + IncludePRs bool `yaml:"include-prs" json:"include-prs" mapstructure:"include-prs"` + IncludeIssues bool `yaml:"include-issues" json:"include-issues" mapstructure:"include-issues"` + IssuesRequireLinkedPR bool `yaml:"issues-require-linked-prs" json:"issues-require-linked-prs" mapstructure:"issues-require-linked-prs"` + Changes []githubChange `yaml:"changes" json:"changes" mapstructure:"changes"` } type githubChange struct { Type string `yaml:"name" json:"name" mapstructure:"name"` Title string `yaml:"title" json:"title" mapstructure:"title"` - Labels []string `yaml:"labels" json:"labels" mapstructure:"labels"` SemVerKind string `yaml:"semver-field" json:"semver-field" mapstructure:"semver-field"` + Labels []string `yaml:"labels" json:"labels" mapstructure:"labels"` } func (cfg githubSummarizer) ToGithubConfig() (github.Config, error) { @@ -36,16 +37,18 @@ func (cfg githubSummarizer) ToGithubConfig() (github.Config, error) { } } return github.Config{ - Host: cfg.Host, - IncludeIssues: cfg.IncludeIssues, - IncludePRs: cfg.IncludePRs, - ExcludeLabels: cfg.ExcludeLabels, - ChangeTypesByLabel: typeSet, + Host: cfg.Host, + IncludeIssues: cfg.IncludeIssues, + IncludePRs: cfg.IncludePRs, + ExcludeLabels: cfg.ExcludeLabels, + IssuesRequireLinkedPR: cfg.IssuesRequireLinkedPR, + ChangeTypesByLabel: typeSet, }, nil } func (cfg githubSummarizer) loadDefaultValues(v *viper.Viper) { v.SetDefault("github.host", "github.com") + v.SetDefault("github.issues-require-linked-prs", false) v.SetDefault("github.include-prs", true) v.SetDefault("github.include-issues", true) v.SetDefault("github.exclude-labels", []string{"duplicate", "question", "invalid", "wontfix", "wont-fix", "release-ignore", "changelog-ignore", "ignore"})