Completed
Pull Request — master (#180)
by
unknown
11:14
created

GitWrapper::addLoggerEventSubscriber()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
namespace GitWrapper;
4
5
use GitWrapper\Event\GitEvents;
6
use GitWrapper\Event\GitLoggerEventSubscriber;
7
use GitWrapper\Event\GitOutputEvent;
8
use GitWrapper\Event\GitOutputListenerInterface;
9
use GitWrapper\Event\GitOutputStreamListener;
10
use Symfony\Component\EventDispatcher\EventDispatcher;
11
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
12
use Symfony\Component\Process\ExecutableFinder;
13
14
/**
15
 * A wrapper class around the Git binary.
16
 *
17
 * A GitWrapper object contains the necessary context to run Git commands such
18
 * as the path to the Git binary and environment variables. It also provides
19
 * helper methods to run Git commands as set up the connection to the GIT_SSH
20
 * wrapper script.
21
 */
22
final class GitWrapper
23
{
24
    /**
25
     * Path to the Git binary.
26
     *
27
     * @var string
28
     */
29
    private $gitBinary;
30
31
    /**
32
     * The timeout of the Git command in seconds.
33
     *
34
     * @var int
35
     */
36
    private $timeout = 60;
37
38
    /**
39
     * Environment variables defined in the scope of the Git command.
40
     *
41
     * @var string[]
42
     */
43
    private $env = [];
44
45
    /**
46
     * @var GitOutputListenerInterface
47
     */
48
    private $gitOutputListener;
49
50
    /**
51
     * @var EventDispatcherInterface
52
     */
53
    private $eventDispatcher;
54
55
    public function __construct(?string $gitBinary = null)
56
    {
57
        if ($gitBinary === null) {
58
            $finder = new ExecutableFinder();
59
            $gitBinary = $finder->find('git');
60
            if (! $gitBinary) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gitBinary of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
61
                throw new GitException('Unable to find the Git executable.');
62
            }
63
        }
64
65
        $this->setGitBinary($gitBinary);
66
    }
67
68
    public function getDispatcher(): EventDispatcherInterface
69
    {
70
        if ($this->eventDispatcher === null) {
71
            $this->eventDispatcher = new EventDispatcher();
72
        }
73
74
        return $this->eventDispatcher;
75
    }
76
77
    public function setDispatcher(EventDispatcherInterface $eventDispatcher): void
78
    {
79
        $this->eventDispatcher = $eventDispatcher;
80
    }
81
82
    public function setGitBinary(string $gitBinary): void
83
    {
84
        $this->gitBinary = $gitBinary;
85
    }
86
87
    public function getGitBinary(): string
88
    {
89
        return $this->gitBinary;
90
    }
91
92
    /**
93
     * @param mixed $value
94
     */
95
    public function setEnvVar(string $var, $value): void
96
    {
97
        $this->env[$var] = $value;
98
    }
99
100
    public function unsetEnvVar(string $var): void
101
    {
102
        unset($this->env[$var]);
103
    }
104
105
    /**
106
     * Returns an environment variable that is defined only in the scope of the
107
     * Git command.
108
     *
109
     * @param string $var The name of the environment variable, e.g. "HOME", "GIT_SSH".
110
     * @param mixed $default The value returned if the environment variable is not set, defaults to
111
     *   null.
112
     *
113
     * @return mixed
114
     */
115
    public function getEnvVar(string $var, $default = null)
116
    {
117
        return $this->env[$var] ?? $default;
118
    }
119
120
    /**
121
     * @return mixed[]
122
     */
123
    public function getEnvVars(): array
124
    {
125
        return $this->env;
126
    }
127
128
    public function setTimeout(int $timeout): void
129
    {
130
        $this->timeout = $timeout;
131
    }
132
133
    public function getTimeout(): int
134
    {
135
        return $this->timeout;
136
    }
137
138
    /**
139
     * Set an alternate private key used to connect to the repository.
140
     *
141
     * This method sets the GIT_SSH environment variable to use the wrapper
142
     * script included with this library. It also sets the custom GIT_SSH_KEY
143
     * and GIT_SSH_PORT environment variables that are used by the script.
144
     *
145
     * @param string|null $wrapper Path the the GIT_SSH wrapper script, defaults to null which uses the
146
     *   script included with this library.
147
     */
148
    public function setPrivateKey(string $privateKey, int $port = 22, ?string $wrapper = null): void
149
    {
150
        if ($wrapper === null) {
151
            $wrapper = __DIR__ . '/../bin/git-ssh-wrapper.sh';
152
        }
153
154
        if (! $wrapperPath = realpath($wrapper)) {
155
            throw new GitException('Path to GIT_SSH wrapper script could not be resolved: ' . $wrapper);
156
        }
157
158
        if (! $privateKeyPath = realpath($privateKey)) {
159
            throw new GitException('Path private key could not be resolved: ' . $privateKey);
160
        }
161
162
        $this->setEnvVar('GIT_SSH', $wrapperPath);
163
        $this->setEnvVar('GIT_SSH_KEY', $privateKeyPath);
164
        $this->setEnvVar('GIT_SSH_PORT', $port);
165
    }
166
167
    /**
168
     * Unsets the private key by removing the appropriate environment variables.
169
     */
170
    public function unsetPrivateKey(): void
171
    {
172
        $this->unsetEnvVar('GIT_SSH');
173
        $this->unsetEnvVar('GIT_SSH_KEY');
174
        $this->unsetEnvVar('GIT_SSH_PORT');
175
    }
176
177
    public function addOutputListener(GitOutputListenerInterface $gitOutputListener): void
178
    {
179
        $this->getDispatcher()
180
            ->addListener(GitEvents::GIT_OUTPUT, [$gitOutputListener, 'handleOutput']);
181
    }
182
183
    public function addLoggerEventSubscriber(GitLoggerEventSubscriber $gitLoggerEventSubscriber): void
184
    {
185
        $this->getDispatcher()
186
            ->addSubscriber($gitLoggerEventSubscriber);
187
    }
188
189
    public function removeOutputListener(GitOutputListenerInterface $gitOutputListener): void
190
    {
191
        $this->getDispatcher()
192
            ->removeListener(GitEvents::GIT_OUTPUT, [$gitOutputListener, 'handleOutput']);
193
    }
194
195
    /**
196
     * Set whether or not to stream real-time output to STDOUT and STDERR.
197
     */
198
    public function streamOutput(bool $streamOutput = true): void
199
    {
200
        if ($streamOutput && ! isset($this->gitOutputListener)) {
201
            $this->gitOutputListener = new GitOutputStreamListener();
202
            $this->addOutputListener($this->gitOutputListener);
203
        }
204
205
        if (! $streamOutput && isset($this->gitOutputListener)) {
206
            $this->removeOutputListener($this->gitOutputListener);
207
            unset($this->gitOutputListener);
208
        }
209
    }
210
211
    /**
212
     * Returns an object that interacts with a working copy.
213
     *
214
     * @param string $directory Path to the directory containing the working copy.
215
     */
216
    public function workingCopy(string $directory): GitWorkingCopy
217
    {
218
        return new GitWorkingCopy($this, $directory);
219
    }
220
221
    /**
222
     * Returns the version of the installed Git client.
223
     */
224
    public function version(): string
225
    {
226
        return $this->git('--version');
227
    }
228
229
    /**
230
     * For example, passing the "[email protected]:cpliakas/git-wrapper.git"
231
     * repository would return "git-wrapper".
232
     */
233
    public static function parseRepositoryName(string $repositoryUrl): string
234
    {
235
        $scheme = parse_url($repositoryUrl, PHP_URL_SCHEME);
236
237
        if ($scheme === null) {
238
            $parts = explode('/', $repositoryUrl);
239
            $path = end($parts);
240
        } else {
241
            $strpos = strpos($repositoryUrl, ':');
242
            $path = substr($repositoryUrl, $strpos + 1);
243
        }
244
245
        /** @var string $path */
246
        return basename($path, '.git');
247
    }
248
249
    /**
250
     * Executes a `git init` command.
251
     *
252
     * Create an empty git repository or reinitialize an existing one.
253
     *
254
     * @param mixed[] $options An associative array of command line options.
255
     */
256
    public function init(string $directory, array $options = []): GitWorkingCopy
257
    {
258
        $git = $this->workingCopy($directory);
259
        $git->init($options);
260
        $git->setCloned(true);
261
262
        return $git;
263
    }
264
265
    /**
266
     * Executes a `git clone` command and returns a working copy object.
267
     *
268
     * Clone a repository into a new directory. Use @see GitWorkingCopy::cloneRepository()
269
     * instead for more readable code.
270
     *
271
     * @param string $repository The Git URL of the repository being cloned.
272
     * @param string $directory The directory that the repository will be cloned into. If null is
273
     *   passed, the directory will automatically be generated from the URL via
274
     *   the GitWrapper::parseRepositoryName() method.
275
     * @param mixed[] $options An associative array of command line options.
276
     */
277
    public function cloneRepository(string $repository, ?string $directory = null, array $options = []): GitWorkingCopy
278
    {
279
        if ($directory === null) {
280
            $directory = self::parseRepositoryName($repository);
281
        }
282
283
        $git = $this->workingCopy($directory);
284
        $git->cloneRepository($repository, $options);
285
        $git->setCloned(true);
286
        return $git;
287
    }
288
289
    /**
290
     * The command is simply a raw command line entry for everything after the Git binary.
291
     * For example, a `git config -l` command would be passed as `config -l` via the first argument of this method.
292
     *
293
     * @return string The STDOUT returned by the Git command.
294
     */
295
    public function git(string $commandLine, ?string $cwd = null): string
296
    {
297
        $command = new GitCommand($commandLine);
298
        $command->executeRaw(is_string($commandLine));
299
        $command->setDirectory($cwd);
300
        return $this->run($command);
301
    }
302
303
    /**
304
     * @return string The STDOUT returned by the Git command.
305
     */
306
    public function run(GitCommand $gitCommand, ?string $cwd = null): string
307
    {
308
        $process = new GitProcess($this, $gitCommand, $cwd);
309
        $process->run(function ($type, $buffer) use ($process, $gitCommand): void {
310
            $event = new GitOutputEvent($this, $process, $gitCommand, $type, $buffer);
311
            $this->getDispatcher()->dispatch(GitEvents::GIT_OUTPUT, $event);
312
        });
313
314
        return $gitCommand->notBypassed() ? $process->getOutput() : '';
315
    }
316
}
317