diff --git a/README.md b/README.md index 88c5565..a522bf1 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 ``` \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 49060ed..40b4f4a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -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) @@ -89,6 +91,8 @@ return [ 'skipBump' => false, 'skipTag' => false, 'skipVerify' => false, + 'annotateTag' => false, + 'signTag' => false, 'disableLinks' => false, 'hiddenHash' => false, 'hiddenAuthor' => true, @@ -146,6 +150,28 @@ return [ ]; ``` +#### Example with Annotated and Signed Tags + +To create annotated or GPG-signed tags: + +```php + 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 diff --git a/src/Changelog.php b/src/Changelog.php index d5c7f85..d1aad6f 100644 --- a/src/Changelog.php +++ b/src/Changelog.php @@ -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; @@ -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 { diff --git a/src/Configuration.php b/src/Configuration.php index 165acdf..fd9515a 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -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. * @@ -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(), @@ -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 @@ -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[][] */ diff --git a/src/DefaultCommand.php b/src/DefaultCommand.php index 205111b..b710dcd 100644 --- a/src/DefaultCommand.php +++ b/src/DefaultCommand.php @@ -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'), ]); } diff --git a/src/Git/Repository.php b/src/Git/Repository.php index 624b53c..c2c17b6 100644 --- a/src/Git/Repository.php +++ b/src/Git/Repository.php @@ -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}"); } diff --git a/tests/ChangelogTest.php b/tests/ChangelogTest.php index ba47b65..58f1da7 100644 --- a/tests/ChangelogTest.php +++ b/tests/ChangelogTest.php @@ -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 + // 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()); + } }