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;
27use function rtrim;
28use function str_starts_with;
29use function trim;
30
31/**
32 * Executes semantic Git operations using the local Git binary.
33 */
34  class GitClient implements GitClientInterface
35{
36    /**
37     * @param ProcessBuilderInterface $processBuilder
38     * @param ProcessQueueInterface $processQueue
39     */
40    public function __construct(
41        private ProcessBuilderInterface $processBuilder,
42        private ProcessQueueInterface $processQueue,
43    ) {}
44
45    /**
46     * Returns a Git config value for the selected repository.
47     *
48     * @param string $key
49     * @param ?string $workingDirectory
50     */
51    public function getConfig(string $key, ?string $workingDirectory = null): string
52    {
53        return $this->run(
54            $this->processBuilder
55                ->withArgument('config')
56                ->withArgument('--get')
57                ->withArgument($key)
58                ->build('git'),
59            $workingDirectory,
60        );
61    }
62
63    /**
64     * Returns the file contents shown from a specific Git reference.
65     *
66     * @param string $reference
67     * @param string $path
68     * @param ?string $workingDirectory
69     */
70    public function show(string $reference, string $path, ?string $workingDirectory = null): string
71    {
72        if (null !== $workingDirectory && Path::isAbsolute($path)) {
73            $normalizedWorkingDirectory = rtrim(Path::canonicalize($workingDirectory), '/');
74            $normalizedPath = Path::canonicalize($path);
75
76            if (str_starts_with($normalizedPath, $normalizedWorkingDirectory . '/')) {
77                $path = Path::makeRelative($normalizedPath, $normalizedWorkingDirectory);
78            }
79        }
80
81        return $this->run(
82            $this->processBuilder
83                ->withArgument('show')
84                ->withArgument($reference . ':' . $path)
85                ->build('git'),
86            $workingDirectory,
87        );
88    }
89
90    /**
91     * Executes a Git process and returns trimmed stdout.
92     *
93     * @param Process $process
94     * @param ?string $workingDirectory
95     */
96    private function run(Process $process, ?string $workingDirectory = null): string
97    {
98        if (null !== $workingDirectory) {
99            $process->setWorkingDirectory($workingDirectory);
100        }
101
102        $this->processQueue->add($process);
103
104        if (ProcessQueueInterface::SUCCESS !== $this->processQueue->run()) {
105            throw new RuntimeException(trim($process->getErrorOutput()));
106        }
107
108        return trim($process->getOutput());
109    }
110}