ap0llo/changelog

💡 Idea: Entry relationships / footer directives

ap0llo opened this issue · 4 comments

Intro

In some scenarios, just providing a description of a commit is not sufficient to describe a change because the change does not stand on its own.
These Relationships between change log entries could be modeled using special directives in commit messages that define these relationships.
These could then be taken into account when generating the change log and activate additional behavior.

Format

Entry relationships can be modeled as regular footers which keeps compatibility with the Conventional Commits specification.
A entry relationship consists of a type and a referenced entry:

  • The relationship type is modeled as a footer's name
  • The referenced entry is specified in the footer's value using the entry's git commit sha

For example, a bug fix that reverts a previous commit, could model this relationship using a Reverts footer:

fix: fix a bug

Description of the bug fix

Reverts: d81a8dbdb6bbb815f13633cf2be066005846d320

Relationship types

Message-For

Overrides a entry's commit message with a different message

Reverts

Indicates that a commit x reverts a previous commit y:

  • If both x and y belong two the same version, neither entry is included in the change log
  • If x and y belong to different versions, the change log will contain a link between the two entries

Fixes

Indicates that a commit x fixes a bug introduced by commit y.

  • If both x and y belong two the same version, x is omitted from the change log (because the bug was introduced in the same version and hence the bug is not a change compared to the previous version)
  • If x and y belong to different versions, the change log will contain a link between the two entries

Replaces

Indicates that a commit x replaces a different commit y.

All occurrences of commit y in the change log are replaced with the entry for x

Open questions ❓

  • Is there special handling for transitive dependencies required? (e.g. using the commit 1 includes a "Message-For" footer for commit 2 but commit 3 includes a "Message-For" footer for commit 1)
  • Can an entry define multiple relationships?
  • Revert: Can the revert commit itself be reverted?
  • Do "relationship footers" show up in the output or are they removed?

Hey @ap0llo,
my team and I are currently looking into generating our changelogs from conventional commits, using these kinds of relations to manage fixes and extensions to pre-released features. Your tool seems to have 90% of the features we need. I wanted to ask you if you were interested in some Pull Requests that would add the relationships you mention above (and a few more).

In addition, we are looking into changelog generation diffs, that means: what changes were made between the last version (last alpha, last beta, last full release), but not to a file but just as output to the console.

I'd really like to chat, so if you are interested, please let me know.

I'm glad to hear changelog is useful to you.

I wanted to ask you if you were interested in some Pull Requests that would add the relationships you mention above (and a few more).

Yes, I'd accept a PR that adds features along the lines of the idea outlined here.

In addition, we are looking into changelog generation diffs, that means: what changes were made between the last version (last alpha, last beta, last full release), but not to a file but just as output to the console.

I would also accept a console output mode, but we should discuss how that fits into the current mode of putting all log messages to the console.
I guess mixing the log with the change log output would make for a very confusing output.

Hey @ap0llo,

I tried to play a little with the code, but I find it really hard to determine where to put the changes. Would you be available for a brief chat?

Sorry for the late response. I'd prefer to keep the communication in GitHub Issues.

Let me give you a brief overview of the structure of the tool.

There are two core concepts: The Model and Tasks

Model

The model represents the change log being generated.
All the data that should be rendered in the change log is represented here.

  • ApplicationChangeLog is the root object that holds the change log for one or more versions
  • SingleVersionChangeLog represents the changes for a single application version. It hold a collection of ChangeLogEntry objects
  • ChangeLogEntry is a single entry in the change log (corresponding to a single commit with a conventional commit formatted message). It mostly follows the data structure as defined by the Conventional Commits specification

The model is built up and extended/modified by Tasks.

Tasks

A Task is class (that implements IChangeLogTask) that represents one step in the change log generation process.
All tasks are executed in sequence and are passed the current model which they are can modify.

There are various tasks in the application, e.g.

  • LoadCommitsTask determines which commits to include in the change log
  • ParseCommitsTask parses the commit messages and converts them into change log entries
  • RenderTemplateTask takes the model and generates a Markdown file from it

Some tasks are optional and only executed when they are enabled in the configuration (e.g. loading of commit message overrides)

In general, every feature in the application maps to one or more tasks.

Each tasks can also declare when it needs to run in relation to other tasks.
You can see this for example in ParseCommitsTask:

  • It needs to run after LoadCommitsTask (otherwise there would not be any commits to parse)
  • It needs to run before RenderTemplateTask otherwise there would not be any entries to render.

The execution of the tasks (including the correct ordering) is implemented in ChangeLogPipeline.
(Side Note: When the --verbose command line switch is set, the log will contain a graph of all the tasks and their dependencies in the dot format)

To add a new task, you'll need to

  • Implement the task and optionally annotate it with [BeforeTask] and/or [AfterTask]
  • Register the task (and potential new dependencies) with the DI container (this is currently done in Program.cs)
  • Add the task to the pipeline to execute it (this is currently done in Program.cs)

Other structures

The rest of the application mostly deals with cross-cutting concerns and abstractions:

  • The CommandLine namespace is for commandline parsing
  • The Configuration namespace models all the settings (it is built on top of Microsoft.Extensions.Configuration)
  • The ConventionalCommits namespace contains a parser for the Conventional Commits format
  • Filtering deals with filtering of the change log (which entries are included in the outpout, by default this is only fix and feat entries)
  • The Git namespace contains the abstraction of git data structures
  • Integrations contains the GitLab and GitHub specific features (e.g. linking to pull request references)
  • IO implements a virtual file system for the embedded resources (this is used for the templates)
  • Templates contains the implementation of the different supported output formats (templates are based Scriban)

In the case of the "Entry relationship" features, it will be necessary to change both the model (add the data structures to express the relationships) and a task to load the relationships and then remove/change entries according to the rules for a task relationship (it would probably make sense to split this up into multiple tasks).

I hope this helps, if you have any further questions, don't hesitate to ask.