tempPaths) as $path) { if (is_dir($path)) { $this->removeDirectory($path); } } parent::tearDown(); } public function test_auto_creating_release_branch_keeps_working_tree_untouched(): void { $workspacePath = $this->makeTempDirectory('toolbox-git-workspace-'); $remotePath = $this->makeTempDirectory('toolbox-git-remote-'); $repoPath = $workspacePath.DIRECTORY_SEPARATOR.'demo-repo'; $this->git($remotePath, ['git', 'init', '--bare']); $this->git($workspacePath, ['git', 'clone', $remotePath, 'demo-repo']); $this->git($repoPath, ['git', 'config', 'user.email', 'test@example.com']); $this->git($repoPath, ['git', 'config', 'user.name', 'Test User']); file_put_contents($repoPath.DIRECTORY_SEPARATOR.'version.txt', '1.0.0'); $this->git($repoPath, ['git', 'add', 'version.txt']); $this->git($repoPath, ['git', 'commit', '-m', 'initial']); $this->git($repoPath, ['git', 'branch', '-M', 'master']); $this->git($repoPath, ['git', 'push', '-u', 'origin', 'master']); $this->git($repoPath, ['git', 'checkout', '-b', 'develop']); file_put_contents($repoPath.DIRECTORY_SEPARATOR.'work.txt', "draft\n"); $service = $this->makeGitMonitorService($workspacePath); $method = new \ReflectionMethod($service, 'ensureReleaseBranchExists'); $method->invoke($service, 'demo-repo', ['directory' => 'demo-repo'], 'release/1.1.0', 'next release'); $this->assertSame('develop', $this->git($repoPath, ['git', 'branch', '--show-current'])); $this->assertSame("draft\n", file_get_contents($repoPath.DIRECTORY_SEPARATOR.'work.txt')); $this->assertStringContainsString('?? work.txt', $this->git($repoPath, ['git', 'status', '--short'])); $this->assertNotEmpty($this->git($repoPath, ['git', 'ls-remote', '--heads', 'origin', 'release/1.1.0'])); $this->assertSame('1.1.0', $this->git($repoPath, ['git', 'show', 'origin/release/1.1.0:version.txt'])); } private function makeGitMonitorService(string $workspacePath): GitMonitorService { $reflection = new \ReflectionClass(GitMonitorService::class); $service = $reflection->newInstanceWithoutConstructor(); $this->setProperty($service, 'projectsPath', $workspacePath); $this->setProperty($service, 'gitTimeout', 60); $this->setProperty($service, 'dingTalkService', $this->createMock(DingTalkService::class)); return $service; } private function setProperty(object $object, string $name, mixed $value): void { $property = new \ReflectionProperty($object, $name); $property->setValue($object, $value); } private function makeTempDirectory(string $prefix): string { $path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$prefix.bin2hex(random_bytes(6)); mkdir($path, 0777, true); $this->tempPaths[] = $path; return $path; } private function git(string $workingDirectory, array $command): string { $process = new Process($command, $workingDirectory); $process->setTimeout(60); $process->mustRun(); return trim($process->getOutput()); } private function removeDirectory(string $path): void { $items = scandir($path); if ($items === false) { return; } foreach ($items as $item) { if ($item === '.' || $item === '..') { continue; } $itemPath = $path.DIRECTORY_SEPARATOR.$item; if (is_dir($itemPath) && ! is_link($itemPath)) { $this->removeDirectory($itemPath); continue; } unlink($itemPath); } rmdir($path); } }