Skip to content

Commit

Permalink
Merge pull request #17 from DigiLive/symfony-process
Browse files Browse the repository at this point in the history
Add symfony/process package
  • Loading branch information
DigiLive authored Mar 21, 2022
2 parents a1f1a60 + 80fe12b commit 123c102
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 82 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
}
],
"require": {
"php": "^7.3 || ^8.0"
"php": "^7.3 || ^8.0",
"symfony/process": "^5"
},
"require-dev": {
"phpunit/phpunit": "^9",
Expand Down
185 changes: 126 additions & 59 deletions src/GitChangelog.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@

namespace DigiLive\GitChangelog;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

/**
* Class GitChangelog
*
Expand Down Expand Up @@ -74,7 +78,7 @@ class GitChangelog
/**
* @var string Path to local git repository.
*/
public $gitPath;
protected $repoPath;
/**
* @var string Base content to append to the generated changelog. If the value is a path which resolves to a file,
* append the content of this file.
Expand Down Expand Up @@ -152,19 +156,27 @@ class GitChangelog
// 'Optimize', // Refactor of performance, e.g. speed up code.
// 'Document', // Refactor of documentation, e.g. help files.
];
/**
* @var string|null Path to the Git executable. If not found automatically, it assumed the path is included in the
* systems' environment variables.
*/
private $gitExecutable;

/**
* GitChangelog constructor.
*
* All git tags are pre-fetched from the repository at the given path.
*
* @param string $gitPath Path to the repository directory.
* @param string $repoPath Path to the repository directory.
*
* @throws \DigiLive\GitChangelog\GitChangelogException When fetching the repository tags fails.
*/
public function __construct(string $gitPath = './')
public function __construct(string $repoPath = './')
{
$this->gitPath = $gitPath;
$this->repoPath = $repoPath;
$executableFinder = new ExecutableFinder();
$this->gitExecutable = $executableFinder->find('git', 'git');

$this->fetchTags();
}

Expand All @@ -189,31 +201,37 @@ public function fetchTags(bool $force = false): array
return $this->gitTags;
}

$gitPath = "--git-dir $this->gitPath.git";

// Get all git tags.
$this->gitTags = [];
$commandResult = 1;
exec("git $gitPath tag --sort=-{$this->options['tagOrderBy']}", $this->gitTags, $commandResult);
if (0 !== $commandResult) {
// @codeCoverageIgnoreStart
throw new GitChangelogException('An error occurred while fetching the tags from the repository!');
// @codeCoverageIgnoreEnd
}

// Add HEAD revision as tag.
if ($this->toTag === null) {
array_unshift($this->gitTags, $this->toTag);
}
try {
$this->gitTags = $this->runCommand(
[
$this->gitExecutable,
'--git-dir',
"$this->repoPath.git",
'tag',
'--sort',
"-{$this->options['tagOrderBy']}",
],
true
);

$toKey = Utilities::arraySearch($this->toTag, $this->gitTags);
$length = null;
if (null !== $this->fromTag) {
$length = Utilities::arraySearch($this->fromTag, $this->gitTags) - $toKey + 1;
}
// Add HEAD revision as tag.
if ($this->toTag === null) {
array_unshift($this->gitTags, $this->toTag);
}

// Cache requested git tags. $this->gitTags = [newest..oldest].
$this->gitTags = array_slice($this->gitTags, $toKey, $length);
$toKey = Utilities::arraySearch($this->toTag, $this->gitTags);
$length = null;
if (null !== $this->fromTag) {
$length = Utilities::arraySearch($this->fromTag, $this->gitTags) - $toKey + 1;
}

// Cache requested git tags. $this->gitTags = [newest..oldest].
$this->gitTags = array_slice($this->gitTags, $toKey, $length);
} catch (GitChangelogException $e) {
throw new GitChangelogException('An error occurred while fetching the tags from the repository!');
}

return $this->gitTags;
}
Expand Down Expand Up @@ -254,37 +272,57 @@ public function fetchCommitData(bool $force = false): array
}

// Re-fetch tags because tag range and order can be altered after pre-fetch.
$gitTags = $this->fetchTags(true);
$commitData = [];

$gitPath = "--git-dir $this->gitPath.git";
$gitTags = $this->fetchTags(true);
$commitData = [];
$includeMerges = $this->options['includeMergeCommits'] ? null : '--no-merges';

// Get tag dates and commit titles from git log for each tag.
$commandResults = [1, 1];
$includeMergeCommits = $this->options['includeMergeCommits'] ? '' : '--no-merges';
// Fetch dates and commit titles/hashes from git log for each tag.
foreach ($gitTags as $tag) {
$rangeStart = next($gitTags);
$tagRange = false !== $rangeStart ? "$rangeStart..$tag" : "$tag";

$commitData[$tag]['date'] =
shell_exec("git $gitPath log -1 --pretty=format:%ad --date=short $tag") ?? 'Error';

exec(
"git $gitPath log $tagRange $includeMergeCommits --pretty=format:%s",
$commitData[$tag]['titles'],
$commandResults[0]
// Fetch tag dates.
$commitData[$tag]['date'] = $this->runCommand(
[
$this->gitExecutable,
'--git-dir',
"$this->repoPath.git",
'log',
'-1',
'--pretty=format:%ad',
'--date=short',
$tag,
],
false
);
exec(
"git $gitPath log $tagRange $includeMergeCommits --pretty=format:%h",
$commitData[$tag]['hashes'],
$commandResults[1]

// Fetch commit titles.
$commitData[$tag]['titles'] = $this->runCommand(
[
$this->gitExecutable,
'--git-dir',
"$this->repoPath.git",
'log',
$tagRange,
'--pretty=format:%s',
$includeMerges,
],
true
);

if (array_sum($commandResults)) {
// @codeCoverageIgnoreStart
throw new \RuntimeException('An error occurred while fetching the commit data from the repository.');
// @codeCoverageIgnoreEnd
}
// Fetch commit hashes.
$commitData[$tag]['hashes'] = $this->runCommand(
[
$this->gitExecutable,
'--git-dir',
"$this->repoPath.git",
'log',
$tagRange,
'--pretty=format:%h',
$includeMerges,
],
true
);
}

// Cache commit data and process it.
Expand Down Expand Up @@ -363,14 +401,12 @@ public function save(string $filePath): void
*
* Optionally the generated changelog is appended with the content of property GitChangelog::$baseContent.
*
* @SuppressWarnings(PHPMD.ErrorControlOperator)
*
* @param bool $append [Optional] Set to true to append the changelog with base content.
* @param bool $append Set to true to append the changelog with base content.
*
* @return string The generated changelog, optionally followed by base content.
* @see GitChangelog::$baseContent
*/
public function get(bool $append = false): string
public function get(bool $append): string
{
return $this->changelog . ($append ? $this->baseContent : '');
}
Expand Down Expand Up @@ -399,11 +435,11 @@ public function setBaseContent(string $content): void
*
* Note: This method does not affect the contents of the pre-fetched tags.
*
* @param mixed $tag The newest tag to include.
* @param ?string $tag The newest tag to include.
*
* @see GitChangelog::fetchTags()
*/
public function setToTag($tag = null): void
public function setToTag(?string $tag = null): void
{
$tag = $tag ?? '';
Utilities::arraySearch($tag, $this->gitTags);
Expand All @@ -417,11 +453,11 @@ public function setToTag($tag = null): void
*
* Note: This method does not affect the contents of the pre-fetched tags.
*
* @param mixed $tag The oldest tag to include.
* @param ?string $tag The oldest tag to include.
*
* @see GitChangelog::fetchTags()
*/
public function setFromTag($tag = null): void
public function setFromTag(?string $tag = null): void
{
if (null !== $tag) {
Utilities::arraySearch($tag, $this->gitTags);
Expand All @@ -447,7 +483,7 @@ public function removeLabel(string ...$labels): void
try {
$key = Utilities::arraySearch($label, $this->labels);
unset($this->labels[$key]);
} catch (\InvalidArgumentException $e) {
} catch (\OutOfBoundsException $e) {
continue;
}
}
Expand Down Expand Up @@ -515,7 +551,7 @@ public function addLabel(...$labels)
* @param mixed $value [Optional] Value of option.
*
* @throws \OutOfBoundsException If the option you're trying to set is invalid.
* @throws \OutOfRangeException When setting option 'headTag' to an invalid value.
* @throws \RangeException When setting option 'headTag' to an invalid value.
* @throws \DigiLive\GitChangelog\GitChangelogException If fetching the repository tags fails.
* @see GitChangelog::$options
* @see GitChangelog::fetchTags()
Expand All @@ -532,10 +568,41 @@ public function setOptions($name, $value = null): void
}

if ('headTagName' == $option && in_array($value, $this->fetchTags())) {
throw new \OutOfRangeException('Attempt to set option headTagName to an already existing tag value!');
throw new \RangeException('Attempt to set option headTagName to an already existing tag value!');
}

$this->options[$option] = $value;
}
}

/**
* Run a command to fetch data from the repository en get the output (STDOUT).
*
* If returning an array, each element will contain one line of the output.
*
* Note: Command arguments with an empty value are removed from the argument list.
*
* @param array $command The command to run and its arguments listed as separate entries
* @param bool $asArray True to return the output as an array, False to return the output as a string.
*
* @return array|string The output (STDOUT) of the process.
* @throws \DigiLive\GitChangelog\GitChangelogException When the process didn't terminate successfully.
*/
private function runCommand(array $command, bool $asArray)
{
$process = new Process(array_values(array_filter($command)));

try {
$process->mustRun();
$output = $process->getOutput();
} catch (ProcessFailedException $e) {
throw new GitChangelogException('An error occurred while fetching data from the repository!');
}

if ($asArray) {
$output = explode("\n", trim($output));
}

return $output;
}
}
1 change: 1 addition & 0 deletions src/GitChangelogException.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@
*/
class GitChangelogException extends \Exception
{
// Intentionally left blank because the class doesn't extend or alters the \Exception class.
}
2 changes: 1 addition & 1 deletion src/Utilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public static function arraySearch($value, array $array, bool $strict = false):
{
$key = array_search($value, $array, $strict);
if (false === $key) {
throw new \OutOfBoundsException("Value $value, does not exist in array!");
throw new \OutOfBoundsException("Value $value does not exist in array!");
}

return $key;
Expand Down
Loading

0 comments on commit 123c102

Please sign in to comment.