Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
GitClient
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
4 / 4
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConfig
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 show
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 run
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Git;
21
22use Symfony\Component\Process\Process;
23use FastForward\DevTools\Process\ProcessBuilderInterface;
24use FastForward\DevTools\Process\ProcessQueueInterface;
25use RuntimeException;
26use Symfony\Component\Filesystem\Path;
27
28use function rtrim;
29use function str_starts_with;
30use function trim;
31
32/**
33 * Executes semantic Git operations using the local Git binary.
34 */
35  class GitClient implements GitClientInterface
36{
37    /**
38     * @param ProcessBuilderInterface $processBuilder
39     * @param ProcessQueueInterface $processQueue
40     */
41    public function __construct(
42        private ProcessBuilderInterface $processBuilder,
43        private ProcessQueueInterface $processQueue,
44    ) {}
45
46    /**
47     * Returns a Git config value for the selected repository.
48     *
49     * @param string $key
50     * @param ?string $workingDirectory
51     */
52    public function getConfig(string $key, ?string $workingDirectory = null): string
53    {
54        return $this->run(
55            $this->processBuilder
56                ->withArgument('config')
57                ->withArgument('--get')
58                ->withArgument($key)
59                ->build('git'),
60            $workingDirectory,
61        );
62    }
63
64    /**
65     * Returns the file contents shown from a specific Git reference.
66     *
67     * @param string $reference
68     * @param string $path
69     * @param ?string $workingDirectory
70     */
71    public function show(string $reference, string $path, ?string $workingDirectory = null): string
72    {
73        if (null !== $workingDirectory && Path::isAbsolute($path)) {
74            $normalizedWorkingDirectory = rtrim(Path::canonicalize($workingDirectory), '/');
75            $normalizedPath = Path::canonicalize($path);
76
77            if (str_starts_with($normalizedPath, $normalizedWorkingDirectory . '/')) {
78                $path = Path::makeRelative($normalizedPath, $normalizedWorkingDirectory);
79            }
80        }
81
82        return $this->run(
83            $this->processBuilder
84                ->withArgument('show')
85                ->withArgument($reference . ':' . $path)
86                ->build('git'),
87            $workingDirectory,
88        );
89    }
90
91    /**
92     * Executes a Git process and returns trimmed stdout.
93     *
94     * @param Process $process
95     * @param ?string $workingDirectory
96     */
97    private function run(Process $process, ?string $workingDirectory = null): string
98    {
99        if (null !== $workingDirectory) {
100            $process->setWorkingDirectory($workingDirectory);
101        }
102
103        $this->processQueue->add($process);
104
105        if (ProcessQueueInterface::SUCCESS !== $this->processQueue->run()) {
106            throw new RuntimeException(trim($process->getErrorOutput()));
107        }
108
109        return trim($process->getOutput());
110    }
111}