Merge pull request #12254 from appwrite/fix-sateless-git-hints

Fix: Stateless git hints
This commit is contained in:
Matej Bačo
2026-05-10 10:14:51 +02:00
committed by GitHub
2 changed files with 174 additions and 5 deletions
+20 -5
View File
@@ -50,6 +50,8 @@ class Comment
protected string $statePrefix = '[appwrite]: #';
protected ?string $tip = null;
/**
* @var mixed[] $builds
*/
@@ -81,7 +83,14 @@ class Comment
public function generateComment(): string
{
$json = \json_encode($this->builds);
if ($this->tip === null) {
$this->tip = $this->tips[\array_rand($this->tips)];
}
$json = \json_encode([
'builds' => $this->builds,
'tip' => $this->tip,
]);
$text = $this->statePrefix . \base64_encode($json) . "\n\n";
@@ -226,8 +235,7 @@ class Comment
$i++;
}
$tip = $this->tips[array_rand($this->tips)];
$text .= "\n<br>\n\n> [!TIP]\n> $tip\n\n";
$text .= "\n<br>\n\n> [!TIP]\n> {$this->tip}\n\n";
return $text;
}
@@ -252,8 +260,15 @@ class Comment
$json = \base64_decode($state);
$builds = \json_decode($json, true);
$this->builds = \is_array($builds) ? $builds : [];
$data = \json_decode($json, true);
if (\is_array($data) && \array_key_exists('builds', $data)) {
$this->builds = \is_array($data['builds']) ? $data['builds'] : [];
$this->tip = $data['tip'] ?? null;
} else {
// Backward compatibility with old state format (builds array only)
$this->builds = \is_array($data) ? $data : [];
}
return $this;
}
+154
View File
@@ -0,0 +1,154 @@
<?php
namespace Tests\Unit\Vcs;
use Appwrite\Vcs\Comment;
use PHPUnit\Framework\TestCase;
use Utopia\Database\Document;
class CommentTest extends TestCase
{
public function testTipIsPreservedAcrossMultipleGenerations(): void
{
$comment = new Comment(['consoleHostname' => 'localhost']);
$comment->addBuild(
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
new Document(['$id' => 'func1', 'name' => 'Test Function']),
'function',
'ready',
'dep1',
['type' => 'logs'],
''
);
$first = $comment->generateComment();
$firstTip = $this->extractTip($first);
$this->assertNotNull($firstTip);
$this->assertNotEmpty($firstTip);
$second = $comment->generateComment();
$secondTip = $this->extractTip($second);
$this->assertEquals($firstTip, $secondTip);
}
public function testTipIsRestoredFromParsedComment(): void
{
$comment = new Comment(['consoleHostname' => 'localhost']);
$comment->addBuild(
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
new Document(['$id' => 'func1', 'name' => 'Test Function']),
'function',
'ready',
'dep1',
['type' => 'logs'],
''
);
$original = $comment->generateComment();
$originalTip = $this->extractTip($original);
$parsed = new Comment(['consoleHostname' => 'localhost']);
$parsed->parseComment($original);
$parsed->addBuild(
new Document(['$id' => 'project1', 'name' => 'Test Project', 'region' => 'default']),
new Document(['$id' => 'func2', 'name' => 'Another Function']),
'function',
'building',
'dep2',
['type' => 'logs'],
''
);
$regenerated = $parsed->generateComment();
$regeneratedTip = $this->extractTip($regenerated);
$this->assertEquals($originalTip, $regeneratedTip);
}
public function testBackwardCompatibilityWithOldStateFormat(): void
{
$oldBuilds = [
'project1_func1' => [
'projectName' => 'Test Project',
'projectId' => 'project1',
'region' => 'default',
'resourceName' => 'Test Function',
'resourceId' => 'func1',
'resourceType' => 'function',
'buildStatus' => 'ready',
'deploymentId' => 'dep1',
'action' => ['type' => 'logs'],
'previewUrl' => '',
],
];
$oldState = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
$oldState .= "> [!TIP]\n> Old tip that should be ignored\n\n";
$comment = new Comment(['consoleHostname' => 'localhost']);
$comment->parseComment($oldState);
$new = $comment->generateComment();
$newTip = $this->extractTip($new);
$this->assertNotNull($newTip);
$this->assertNotEquals('Old tip that should be ignored', $newTip);
$this->assertContains($newTip, $this->getTips());
}
public function testParseOldStateFormatWithOnlyBuilds(): void
{
$oldBuilds = [
'project1_func1' => [
'projectName' => 'Test Project',
'projectId' => 'project1',
'region' => 'default',
'resourceName' => 'Test Function',
'resourceId' => 'func1',
'resourceType' => 'function',
'buildStatus' => 'ready',
'deploymentId' => 'dep1',
'action' => ['type' => 'logs'],
'previewUrl' => '',
],
];
$state = '[appwrite]: #' . \base64_encode(\json_encode($oldBuilds)) . "\n\n";
$comment = new Comment(['consoleHostname' => 'localhost']);
$comment->parseComment($state);
$this->assertEquals(false, $comment->isEmpty());
$first = $comment->generateComment();
$firstTip = $this->extractTip($first);
$this->assertNotNull($firstTip);
$this->assertNotEmpty($firstTip);
$this->assertContains($firstTip, $this->getTips());
$second = $comment->generateComment();
$secondTip = $this->extractTip($second);
$this->assertEquals($firstTip, $secondTip);
}
private function extractTip(string $comment): ?string
{
if (\preg_match('/> \[!TIP\]\n> (.+)/', $comment, $matches)) {
return $matches[1];
}
return null;
}
private function getTips(): array
{
$reflection = new \ReflectionClass(Comment::class);
$property = $reflection->getProperty('tips');
return $property->getValue(new Comment(['consoleHostname' => 'localhost']));
}
}