Skip to content

Commit

Permalink
ENH Add option to group commits by contributor
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxime Rainville committed Oct 17, 2023
1 parent ac1dc5a commit 483761d
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 10 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,15 @@ The [cow schema file](cow.schema.json) is in the root of this project.

You can run `cow schema:validate` to check the `.cow.json` configuration file in your project or module to
ensure it matches against the Cow schema.

## Generating for specific timeframe

It can be helpful to generate a changelog by contributor to measure the progress for a given time frame.

This can be done with the `--changelog--since=` flag. A specific date can be provided or a timeframe (`30 days`). When the frag is provided, the "previous versions" from the plan will be ignored.

You can also use the `--changelog--group-by-contributor` to structure the changelog by contributors.

```bash
cow release:changelog 5.2.0 silverstripe/recipe-kitchen-sink --skip-fetch-tags --changelog--since="30 days" --changelog--group-by-contributor
```
22 changes: 22 additions & 0 deletions src/Commands/Release/Release.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ protected function configureOptions()
. ' Include every change, use audit template.'
. '(implicitly activates include-upgrade-only and include-other-changes)'
)
->addOption(
'changelog--since',
null,
InputOption::VALUE_REQUIRED,
'Constrained changelog to a specific date range'
)
->addOption(
'changelog--group-by-contributor',
null,
InputOption::VALUE_NONE,
'Constrained changelog to a specific date range'
)
;
}

Expand Down Expand Up @@ -317,4 +329,14 @@ public function getChangelogUseLegacyFormat(): bool
{
return $this->input->getOption('changelog--use-legacy-format');
}

public function getChangelogSince(): ?string
{
return $this->input->getOption('changelog--since');
}

public function getChangelogGroupByContributor(): ?string
{
return $this->input->getOption('changelog--group-by-contributor');
}
}
139 changes: 129 additions & 10 deletions src/Model/Changelog/Changelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class Changelog
*/
public const FORMAT_FLAT = 'flat';

/**
* Group commits by who made them
*/
public const FORMAT_GROUPED_BY_CONTRIBUTOR = 'grouped-by-contributor';

/**
* @var ChangelogLibrary
*/
Expand All @@ -30,6 +35,11 @@ class Changelog
*/
protected $includeOtherChanges = false;

/**
* Force the commit log range to a specific date.
*/
protected ?string $forceDateRange = null;

/**
* Create a new changelog
*
Expand All @@ -54,18 +64,23 @@ protected function getLibraryLog(
) {
$items = array();

// Get raw log
$fromVersion = $changelogLibrary->getPriorVersion()->getValue();
if ($changelogLibrary->getRelease()->getIsNewRelease()) {
// Since it won't have been tagged yet, we use the current branch head
$toVersion = 'HEAD';
} elseif ($changelogAuditMode) {
// we may have manually cherry-picked security commits after tagging
$toVersion = 'HEAD';
if ($this->forceDateRange) {
$range = '--since="' . $this->forceDateRange . '"';
} else {
$toVersion = $changelogLibrary->getRelease()->getVersion()->getValue();
// Get raw log
$fromVersion = $changelogLibrary->getPriorVersion()->getValue();
if ($changelogLibrary->getRelease()->getIsNewRelease()) {
// Since it won't have been tagged yet, we use the current branch head
$toVersion = 'HEAD';
} elseif ($changelogAuditMode) {
// we may have manually cherry-picked security commits after tagging
$toVersion = 'HEAD';
} else {
$toVersion = $changelogLibrary->getRelease()->getVersion()->getValue();
}
$range = $fromVersion . ".." . $toVersion;
}
$range = $fromVersion . ".." . $toVersion;

try {
$log = $changelogLibrary
->getRelease()
Expand Down Expand Up @@ -138,6 +153,23 @@ protected function getGroupedChanges(OutputInterface $output, ?callable $filter
return $this->sortByType($changes);
}

/**
* Get all changes grouped by contributor
*
* @param OutputInterface $output
* @param ?callable $filter function for array_filter on the list of changes
* @return ChangelogItem[]
*/
protected function getGroupedChangesByContributor(OutputInterface $output, ?callable $filter = null)
{
// Sort by type
$changes = $this->getChanges($output);
if ($filter) {
$changes = array_filter($changes, $filter);
}
return $this->sortByContributor($changes);
}

/**
* Gets all changes in a flat list
*
Expand Down Expand Up @@ -220,6 +252,8 @@ public function getMarkdown(OutputInterface $output, $formatType)
return $this->getMarkdownGrouped($output);
case self::FORMAT_FLAT:
return $this->getMarkdownFlat($output);
case self::FORMAT_GROUPED_BY_CONTRIBUTOR:
return $this->getMarkdownGroupedByContributor($output);
default:
throw new \InvalidArgumentException("Unknown changelog format $formatType");
}
Expand Down Expand Up @@ -280,6 +314,51 @@ protected function getMarkdownGrouped(OutputInterface $output)
return $output;
}

protected function getMarkdownGroupedByContributor(OutputInterface $md)
{
$groupedLog = $this->getGroupedChangesByContributor($md, $this->getLegacyChangelogCommitFilter());

$dataByContributor = [];
$types = ChangelogItem::getTypes();
$types[] = 'Other changes';
$types[] = 'Total';
$emptyContributorRow = array_reduce($types, function ($carry, $value) {
$carry[$value] = 0;
return $carry;
}, []);

$total = $emptyContributorRow;

// Convert to string and generate markdown (add list to beginning of each item)
$md = "\n\n## Change Log\n";
foreach ($groupedLog as $groupName => $commits) {
if (empty($commits)) {
continue;
}

$md .= "\n### $groupName\n\n";
$dataByContributor[$groupName] = $emptyContributorRow;
foreach ($commits as $commit) {
/** @var ChangelogItem $commit */
$dataByContributor[$groupName][$commit->getType()]++;
$dataByContributor[$groupName]['Total']++;
$total[$commit->getType()]++;
$total['Total']++;
$md .= $commit->getMarkdown($this->getLineFormat(), $this->getSecurityFormat());
}
}

$md .= "\n\n### Summary\n";
$md .= "| Contributor | " . implode(' | ', $types) . " |\n";
$md .= "| " . str_repeat(" --- |", sizeof($types) + 1) . "\n";
foreach ($dataByContributor as $contributor => $data) {
$md .= "| $contributor | " . implode(' | ', $data) . " |\n";
}
$md .= "| **Total** | " . implode(' | ', $total) . " |\n";

return $md;
}

/**
* Custom format string for line items
*
Expand Down Expand Up @@ -380,6 +459,29 @@ protected function sortByType($commits)
return $groupedByType;
}

/**
* Sort and filter this list of commits into contributors
* @param ChangelogItem[] $commits Flat list of commits
* @return array
*/
protected function sortByContributor($commits)
{
// List types
$groupedBy = [];

// Group by contributor
foreach ($commits as $commit) {
$contributor = $commit->getAuthor();
if (empty($groupedBy[$contributor])) {
$groupedBy[$contributor] = [];
}

$groupedBy[$contributor][] = $commit;
}

return $groupedBy;
}

/**
* @param array $commits
* @return array
Expand Down Expand Up @@ -436,4 +538,21 @@ public function setRootLibrary($rootLibrary)
$this->rootLibrary = $rootLibrary;
return $this;
}

/**
* Force the commit log range to a specific date.
*/
public function getForceDateRange(): ?string
{
return $this->forceDateRange;
}

/**
* Force the commit log range to a specific date.
*/
public function setForceDateRange(?string $forceDateRange): self
{
$this->forceDateRange = $forceDateRange;
return $this;
}
}
11 changes: 11 additions & 0 deletions src/Steps/Release/CreateChangelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ protected function generateChangelog(OutputInterface $output, LibraryRelease $re
/** @var \SilverStripe\Cow\Commands\Release\Changelog $command */
$command = $this->getCommand();
$changelog->setIncludeOtherChanges($command->getIncludeOtherChanges());

if ($since = $command->getChangelogSince())
{
$changelog->setForceDateRange($since);
}

if ($changelog->getIncludeOtherChanges()) {
$this->log($output, 'Including "other changes" in changelog');
}
Expand All @@ -150,6 +156,11 @@ protected function generateChangelog(OutputInterface $output, LibraryRelease $re
$output,
$changelog->getRootLibrary()->getRelease()->getLibrary()->getChangelogFormat()
);
} else if ($command->getChangelogGroupByContributor()) {
$content = $changelog->getMarkdown(
$output,
Changelog::FORMAT_GROUPED_BY_CONTRIBUTOR
);
} else {
$content = $this->renderChangelogLogs($output, $changelog);
}
Expand Down

0 comments on commit 483761d

Please sign in to comment.