Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,35 @@ or to amend at an existing commit you can run:
php vendor/bin/conventional-changelog --amend
```

#### Annotated and Signed Tags

By default, the tool creates lightweight tags. You can create annotated or GPG-signed tags:

**Create an annotated tag:**
```shell
php vendor/bin/conventional-changelog --commit --annotate-tag
```

**Create a GPG-signed tag** (requires GPG configuration):
```shell
php vendor/bin/conventional-changelog --commit --sign-tag
```

**Create an annotated tag with a custom message:**
```shell
php vendor/bin/conventional-changelog --commit --annotate-tag="Release version"
```

You can also configure this in your `.changelog` configuration file:
```php
return [
'annotateTag' => true, // Create annotated tags by default
'signTag' => true, // Create GPG-signed tags by default
];
```

> **Note:** GPG-signed tags (`--sign-tag`) are automatically annotated, so you don't need to use both options together.

#### History

To generate your changelog with the entire history of changes of all releases
Expand Down Expand Up @@ -207,6 +236,7 @@ Options:
--no-tag Disable release auto tagging
--no-change-without-commits Do not apply change if no commits
--annotate-tag[=ANNOTATE-TAG] Make an unsigned, annotated tag object once changelog is generated [default: false]
--sign-tag Make a GPG-signed tag object once changelog is generated
--merged Only include commits whose tips are reachable from HEAD
-h, --help Display help for the given command. When no command is given display help for the changelog command
```
26 changes: 26 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dir or use the `--config` option to specify the location of your configuration f
- **Package Bumps:** Array of files to replace version, defaults to `['ConventionalChangelog\PackageBump\ComposerJson', 'ConventionalChangelog\PackageBump\PackageJson']`
- **Skip Tag:** Skip automatic commit tagging
- **Skip Verify:** Skip the pre-commit and commit-msg hooks
- **Annotate Tag:** Create annotated tags instead of lightweight tags (can also be set via `--annotate-tag` option)
- **Sign Tag:** Create GPG-signed tags (can also be set via `--sign-tag` option)
- **Disable Links:** Render text instead of link in changelog
- **Hidden Hash:** Hide commit hash from changelog
- **Hidden Author:** Hide commit author from changelog (default: true)
Expand Down Expand Up @@ -89,6 +91,8 @@ return [
'skipBump' => false,
'skipTag' => false,
'skipVerify' => false,
'annotateTag' => false,
'signTag' => false,
'disableLinks' => false,
'hiddenHash' => false,
'hiddenAuthor' => true,
Expand Down Expand Up @@ -146,6 +150,28 @@ return [
];
```

#### Example with Annotated and Signed Tags

To create annotated or GPG-signed tags:

```php
<?php

return [
// Create annotated tags instead of lightweight tags
'annotateTag' => true,

// Create GPG-signed tags (requires GPG setup)
'signTag' => true,

// Note: signTag will also create annotated tags automatically
];
```

You can also use command-line options:
- `--annotate-tag` or `--annotate-tag="Custom message"` for annotated tags
- `--sign-tag` for GPG-signed tags

#### Full Example

```php
Expand Down
12 changes: 11 additions & 1 deletion src/Changelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public function generate(string $root, InputInterface $input, SymfonyStyle $outp
$autoTag = !($input->getOption('no-tag') || $this->config->skipTag()); // Tag release once is committed
$noChangeWithoutCommits = $input->getOption('no-change-without-commits');
$annotateTag = $input->getOption('annotate-tag');
$signTag = $input->getOption('sign-tag');
$amend = $input->getOption('amend'); // Amend commit
$hooks = !$input->getOption('no-verify'); // Verify git hooks
$hooks = $hooks && $this->config->skipVerify() ? false : true;
Expand Down Expand Up @@ -483,7 +484,16 @@ public function generate(string $root, InputInterface $input, SymfonyStyle $outp
// Create tag
if ($autoTag) {
$tag = $tagPrefix . $newVersion . $tagSuffix;
$result = Repository::tag($tag, $annotateTag);

// Use configuration values if command line options are not provided
if ($annotateTag === false && $this->config->isAnnotateTag()) {
$annotateTag = true;
}
if (!$signTag && $this->config->isSignTag()) {
$signTag = true;
}

$result = Repository::tag($tag, $annotateTag, $signTag);
if ($result !== false) {
$output->success("Release tagged with success! New version: {$tag}");
} else {
Expand Down
42 changes: 42 additions & 0 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@ class Configuration
*/
protected $skipVerify = false;

/**
* Annotate tag.
*
* @var bool
*/
protected $annotateTag = false;

/**
* Sign tag.
*
* @var bool
*/
protected $signTag = false;

/**
* Render text instead of links.
*
Expand Down Expand Up @@ -320,6 +334,8 @@ public function fromArray(array $array)
'skipBump' => $this->skipBump(),
'skipTag' => $this->skipTag(),
'skipVerify' => $this->skipVerify(),
'annotateTag' => $this->isAnnotateTag(),
'signTag' => $this->isSignTag(),
'disableLinks' => $this->isDisableLinks(),
'hiddenHash' => $this->isHiddenHash(),
'hiddenAuthor' => $this->isHiddenAuthor(),
Expand Down Expand Up @@ -386,6 +402,8 @@ public function fromArray(array $array)
->setSkipBump($params['skipBump'])
->setSkipTag($params['skipTag'])
->setSkipVerify($params['skipVerify'])
->setAnnotateTag($params['annotateTag'])
->setSignTag($params['signTag'])
// Links
->setDisableLinks($params['disableLinks'])
// Hidden
Expand Down Expand Up @@ -624,6 +642,30 @@ public function setSkipVerify(bool $skipVerify): self
return $this;
}

public function isAnnotateTag(): bool
{
return $this->annotateTag;
}

public function setAnnotateTag(bool $annotateTag): self
{
$this->annotateTag = $annotateTag;

return $this;
}

public function isSignTag(): bool
{
return $this->signTag;
}

public function setSignTag(bool $signTag): self
{
$this->signTag = $signTag;

return $this;
}

/**
* @return \string[][]
*/
Expand Down
1 change: 1 addition & 0 deletions src/DefaultCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ protected function configure()
new InputOption('no-tag', null, InputOption::VALUE_NONE, 'Disable release auto tagging'),
new InputOption('no-change-without-commits', null, InputOption::VALUE_NONE, 'Do not apply change if no commits'),
new InputOption('annotate-tag', null, InputOption::VALUE_OPTIONAL, 'Make an unsigned, annotated tag object once changelog is generated', false),
new InputOption('sign-tag', null, InputOption::VALUE_NONE, 'Make a GPG-signed tag object once changelog is generated'),
new InputOption('merged', null, InputOption::VALUE_NONE, 'Only include commits whose tips are reachable from HEAD'),
]);
}
Expand Down
25 changes: 22 additions & 3 deletions src/Git/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,31 @@ public static function commit(string $message, array $files = [], bool $amend =
/**
* Tag.
*
* @param string $name Tag name
* @param bool|string $annotate Whether to create an annotated tag, or a custom annotation message
* @param bool $sign Whether to create a GPG-signed tag
*
* @return string
*/
public static function tag(string $name, $annotation = false)
public static function tag(string $name, $annotate = false, bool $sign = false)
{
$message = $annotation ?: $name;
$flags = $annotation !== false ? "-a -m {$message}" : '';
$flags = '';

// Determine the message to use
$message = $name;
if (is_string($annotate) && !empty($annotate)) {
$message = $annotate;
}

// Build flags based on annotation and signing
if ($sign) {
// GPG-signed tags (-s) are always annotated
$flags = "-s -m \"{$message}\"";
} elseif ($annotate !== false) {
// Annotated tag without signing
$flags = "-a -m \"{$message}\"";
}
// Otherwise, create a lightweight tag (no flags)

return exec("git tag {$flags} {$name}");
}
Expand Down
77 changes: 77 additions & 0 deletions tests/ChangelogTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,81 @@ public function testPostRunHook()
// Verify the postRun hook was executed
$this->assertTrue($called);
}

/** @test */
public function testAnnotateTagConfiguration()
{
$config = new Configuration(['annotateTag' => true]);
$this->assertTrue($config->isAnnotateTag());

$config = new Configuration(['annotateTag' => false]);
$this->assertFalse($config->isAnnotateTag());
}

/** @test */
public function testSignTagConfiguration()
{
$config = new Configuration(['signTag' => true]);
$this->assertTrue($config->isSignTag());

$config = new Configuration(['signTag' => false]);
$this->assertFalse($config->isSignTag());
}

/** @test */
public function testAnnotateTagDefault()
{
$config = new Configuration();
// By default, annotateTag should be false
$this->assertFalse($config->isAnnotateTag());
}

/** @test */
public function testSignTagDefault()
{
$config = new Configuration();
// By default, signTag should be false
$this->assertFalse($config->isSignTag());
}

/** @test */
public function testTagCommandLightweight()
{
// Test that lightweight tags don't have flags
$class = new \ReflectionClass(\ConventionalChangelog\Git\Repository::class);
$method = $class->getMethod('tag');

// For lightweight tags, the command should be: git tag <name>
// We can't easily test exec output, but we can verify the method exists and is callable
$this->assertTrue($method->isStatic());
$this->assertTrue($method->isPublic());
}

/** @test */
public function testTagCommandAnnotated()
{
// Test that annotated tags work with both boolean and string
$config = new Configuration(['annotateTag' => true]);
$this->assertTrue($config->isAnnotateTag());
}

/** @test */
public function testTagCommandSigned()
{
// Test that signed tags can be configured
$config = new Configuration(['signTag' => true]);
$this->assertTrue($config->isSignTag());
}

/** @test */
public function testBothAnnotateAndSignTag()
{
// Test that both can be enabled together
$config = new Configuration([
'annotateTag' => true,
'signTag' => true,
]);
$this->assertTrue($config->isAnnotateTag());
$this->assertTrue($config->isSignTag());
}
}