Skip to content

Commit

Permalink
Add git-notify support
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianfeldmann committed Mar 5, 2021
1 parent c91b567 commit 0e8c8b9
Show file tree
Hide file tree
Showing 10 changed files with 503 additions and 28 deletions.
30 changes: 3 additions & 27 deletions src/Hook/Condition/FileChanged.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CaptainHook\App\Console\IO;
use CaptainHook\App\Hook\Restriction;
use CaptainHook\App\Hook\Util;
use CaptainHook\App\Hooks;
use SebastianFeldmann\Git\Repository;

Expand Down Expand Up @@ -74,37 +75,12 @@ abstract public function isTrue(IO $io, Repository $repository): bool;
* @param \SebastianFeldmann\Git\Repository $repository
* @return array<string>
*/
protected function getChangedFiles(IO $io, Repository $repository)
protected function getChangedFiles(IO $io, Repository $repository): array
{

$oldHash = $this->findPreviousHead($io);
$oldHash = Util::findPreviousHead($io);
$newHash = $io->getArgument('newHead', 'HEAD');

return $repository->getDiffOperator()->getChangedFiles($oldHash, $newHash);
}

/**
* Detects the previous head commit hash
*
* @param \CaptainHook\App\Console\IO $io
* @return string
*/
private function findPreviousHead(IO $io): string
{
// Check if a list of rewritten commits is supplied via stdIn.
// This happens if the 'post-rewrite' hook is triggered.
// The stdIn is formatted like this:
//
// old-hash new-hash extra-info
// old-hash new-hash extra-info
// ...
$stdIn = $io->getStandardInput();
if (!empty($stdIn)) {
$info = explode(' ', $stdIn[0]);
// If we find a rewritten commit, we return the first commit before the rewritten one.
// If we do not find any rewritten commits (awkward) we use the last ref-log position.
return isset($info[1]) ? $info[1] . '^' : 'HEAD@{1}';
}
return $io->getArgument('previousHead', 'HEAD@{1}');
}
}
105 changes: 105 additions & 0 deletions src/Hook/Notify/Action/Notify.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

/**
* This file is part of CaptainHook
*
* (c) Sebastian Feldmann <sf@sebastian-feldmann.info>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CaptainHook\App\Hook\Notify\Action;

use CaptainHook\App\Config;
use CaptainHook\App\Console\IO;
use CaptainHook\App\Exception\ActionFailed;
use CaptainHook\App\Hook\Action;
use CaptainHook\App\Hook\Constrained;
use CaptainHook\App\Hook\Notify\Extractor;
use CaptainHook\App\Hook\Notify\Notification;
use CaptainHook\App\Hook\Restriction;
use CaptainHook\App\Hook\Util;
use CaptainHook\App\Hooks;
use SebastianFeldmann\Cli\Processor\ProcOpen as Processor;
use SebastianFeldmann\Git\Repository;

/**
* Class Notify
*
* @package CaptainHook
* @author Sebastian Feldmann <sf@sebastian-feldmann.info>
* @link https://github.com/captainhookphp/captainhook
* @since Class available since Release 5.4.5
*/
class Notify implements Action, Constrained
{
private const DEFAULT_PREFIX = 'git-notify:';

/**
* git-notify trigger
*
* @var string
*/
private $prefix;

/**
* Returns a list of applicable hooks
*
* @return \CaptainHook\App\Hook\Restriction
*/
public static function getRestriction(): Restriction
{
return Restriction::fromArray([Hooks::POST_CHECKOUT, Hooks::POST_MERGE, Hooks::POST_REWRITE]);
}

/**
* Executes the action
*
* @param \CaptainHook\App\Config $config
* @param \CaptainHook\App\Console\IO $io
* @param \SebastianFeldmann\Git\Repository $repository
* @param \CaptainHook\App\Config\Action $action
* @return void
* @throws \Exception
*/
public function execute(Config $config, IO $io, Repository $repository, Config\Action $action): void
{
$this->prefix = $action->getOptions()->get('prefix', self::DEFAULT_PREFIX);
$oldHash = Util::findPreviousHead($io);
$newHash = $io->getArgument('newHead', 'HEAD');

$logOp = $repository->getLogOperator();
$log = $logOp->getCommitsBetween($oldHash, $newHash);

foreach ($log as $commit) {
$message = $commit->getSubject() . PHP_EOL . $commit->getBody();
if ($this->containsNotification($message)) {
$notification = Extractor::extractNotification($message, $this->prefix);
$this->notify($io, $notification);
}
}
}

/**
* Checks if the commit message contains the notification prefix 'git-notify:'
*
* @param string $message
* @return bool
*/
private function containsNotification(string $message): bool
{
return strpos($message, $this->prefix) !== false;
}

/**
* Write the notification to the
*
* @param \CaptainHook\App\Console\IO $io
* @param \CaptainHook\App\Hook\Notify\Notification $notification
*/
private function notify(IO $io, Notification $notification)
{
$io->write($notification->banner());
}
}
39 changes: 39 additions & 0 deletions src/Hook/Notify/Extractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/**
* This file is part of CaptainHook
*
* (c) Sebastian Feldmann <sf@sebastian-feldmann.info>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CaptainHook\App\Hook\Notify;

/**
* Class Extractor
*
* @package CaptainHook
* @author Sebastian Feldmann <sf@sebastian-feldmann.info>
* @link https://github.com/captainhookphp/captainhook
* @since Class available since Release 5.4.5
*/
class Extractor
{
public static function extractNotification(string $message, string $prefix = 'git-notify:'): Notification
{
return new Notification(self::getLines($message, $prefix));
}

private static function getLines(string $message, string $prefix): array
{
$matches = [];
if (preg_match('#' . $prefix . '(.*)#is', $message, $matches)) {
$split = preg_split("/\r\n|\n|\r/", $matches[1]);

return is_array($split) ? array_map('trim', $split) : [];
}
return [];
}
}
95 changes: 95 additions & 0 deletions src/Hook/Notify/Notification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

/**
* This file is part of CaptainHook
*
* (c) Sebastian Feldmann <sf@sebastian-feldmann.info>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CaptainHook\App\Hook\Notify;

/**
* Class Notification
*
* @package CaptainHook
* @author Sebastian Feldmann <sf@sebastian-feldmann.info>
* @link https://github.com/captainhookphp/captainhook
* @since Class available since Release 5.4.5
*/
class Notification
{
/**
* List of rules to check
*
* @var string[]
*/
private $lines = [];

/**
* Max line length
*
* @var int
*/
private $maxLineLength = 0;

/**
* Constructor
*
* @param string[] $lines
*/
public function __construct(array $lines)
{
$this->lines = $lines;
foreach ($this->lines as $line) {
$lineLength = mb_strlen($line);
if ($lineLength > $this->maxLineLength) {
$this->maxLineLength = $lineLength;
}
}
}

/**
* Return line count
*
* @return int
*/
public function length(): int
{
return count($this->lines);
}

/**
* Returns the string to display
*
* @return string
*/
public function banner(): string
{
$text = [];
$text[] = '<error>' . str_repeat(' ', $this->maxLineLength + 6) . '</error>';
$text[] = '<error> </error> ' . str_repeat(' ', $this->maxLineLength) . ' <error> </error>';
foreach ($this->lines as $line) {
$text[] = $this->formatLine($line);
}
$text[] = '<error> </error> ' . str_repeat(' ', $this->maxLineLength) . ' <error> </error>';
$text[] = '<error>' . str_repeat(' ', $this->maxLineLength + 6) . '</error>';

return PHP_EOL . implode(PHP_EOL, $text) . PHP_EOL;
}

private function formatLine(string $line): string
{
$length = mb_strlen($line);
$left = '';
$right = '';
if ($length < $this->maxLineLength) {
$space = $this->maxLineLength - $length;
$left = str_repeat(' ', (int) floor($space / 2));
$right = str_repeat(' ', (int) ceil($space / 2));
}
return '<error> </error> ' . $left . $line . $right . ' <error> </error>';
}
}
26 changes: 26 additions & 0 deletions src/Hook/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace CaptainHook\App\Hook;

use CaptainHook\App\Console\IO;
use CaptainHook\App\Hooks;
use RuntimeException;

Expand Down Expand Up @@ -68,4 +69,29 @@ public static function getHooks(): array
{
return array_keys(Hooks::getValidHooks());
}

/**
* Detects the previous head commit hash
*
* @param \CaptainHook\App\Console\IO $io
* @return string
*/
public static function findPreviousHead(IO $io): string
{
// Check if a list of rewritten commits is supplied via stdIn.
// This happens if the 'post-rewrite' hook is triggered.
// The stdIn is formatted like this:
//
// old-hash new-hash extra-info
// old-hash new-hash extra-info
// ...
$stdIn = $io->getStandardInput();
if (!empty($stdIn)) {
$info = explode(' ', $stdIn[0]);
// If we find a rewritten commit, we return the first commit before the rewritten one.
// If we do not find any rewritten commits (awkward) we use the last ref-log position.
return isset($info[1]) ? $info[1] . '^' : 'HEAD@{1}';
}
return $io->getArgument('previousHead', 'HEAD@{1}');
}
}
Loading

0 comments on commit 0e8c8b9

Please sign in to comment.