Issues (106)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/GitElephant/Repository.php (15 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * GitElephant - An abstraction layer for git written in PHP
5
 * Copyright (C) 2013  Matteo Giachino
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see [http://www.gnu.org/licenses/].
19
 */
20
21
namespace GitElephant;
22
23
use GitElephant\Command\BranchCommand;
24
use GitElephant\Command\Caller\Caller;
25
use GitElephant\Command\Caller\CallerInterface;
26
use GitElephant\Command\CatFileCommand;
27
use GitElephant\Command\FetchCommand;
28
use GitElephant\Command\LogCommand;
29
use GitElephant\Command\LsTreeCommand;
30
use GitElephant\Command\MainCommand;
31
use GitElephant\Command\MergeCommand;
32
use GitElephant\Command\PullCommand;
33
use GitElephant\Command\PushCommand;
34
use GitElephant\Command\RemoteCommand;
35
use GitElephant\Command\ResetCommand;
36
use GitElephant\Command\RevParseCommand;
37
use GitElephant\Command\StashCommand;
38
use GitElephant\Command\SubmoduleCommand;
39
use GitElephant\Command\TagCommand;
40
use GitElephant\Objects\Author;
41
use GitElephant\Objects\Branch;
42
use GitElephant\Objects\Commit;
43
use GitElephant\Objects\Diff\Diff;
44
use GitElephant\Objects\Log;
45
use GitElephant\Objects\LogRange;
46
use GitElephant\Objects\NodeObject;
47
use GitElephant\Objects\Remote;
48
use GitElephant\Objects\Tag;
49
use GitElephant\Objects\Tree;
50
use GitElephant\Objects\TreeishInterface;
51
use GitElephant\Objects\TreeObject;
52
use GitElephant\Status\Status;
53
use GitElephant\Status\StatusIndex;
54
use GitElephant\Status\StatusWorkingTree;
55
use Symfony\Component\Filesystem\Filesystem;
56
use Symfony\Component\Finder\Finder;
57
use Symfony\Component\Finder\SplFileInfo;
58
use Symfony\Component\Process\Exception\InvalidArgumentException;
59
60
/**
61
 * Repository
62
 *
63
 * Base Class for repository operations
64
 *
65
 * @author Matteo Giachino <[email protected]>
66
 * @author Dhaval Patel <[email protected]>
67
 * @author Kirk Madera <[email protected]>
68
 */
69
class Repository
70
{
71
    /**
72
     * the repository path
73
     *
74
     * @var string
75
     */
76
    private $path;
77
78
    /**
79
     * the caller instance
80
     *
81
     * @var \GitElephant\Command\Caller\CallerInterface
82
     */
83
    private $caller;
84
85
    /**
86
     * A general repository name
87
     *
88
     * @var string the repository name
89
     */
90
    private $name;
91
92
    /**
93
     * A list of global configs to apply to every command
94
     *
95
     * @var array
96
     */
97
    private $globalConfigs = [];
98
99
    /**
100
     * A list of global options to apply to every command
101
     *
102
     * @var array
103
     */
104
    private $globalOptions = [];
105
106
    /**
107
     * A list of global arguments to apply to every command
108
     *
109
     * @var array
110
     */
111
    private $globalCommandArguments = [];
112
113
    /**
114
     * Class constructor
115
     *
116
     * @param string         $repositoryPath the path of the git repository
117
     * @param string|null $binary         the path to the git binary
118
     * @param string         $name           a repository name
119
     *
120
     * @throws Exception\InvalidRepositoryPathException
121
     */
122 108
    public function __construct($repositoryPath, string $binary = null, $name = null)
123
    {
124 108
        $this->path = $repositoryPath;
125 108
        $this->caller = new Caller($binary, $repositoryPath);
126 108
        $this->name = $name;
127 108
    }
128
129
    /**
130
     * Factory method
131
     *
132
     * @param string         $repositoryPath the path of the git repository
133
     * @param string|null $binary         the path to the git binary
134
     * @param string         $name           a repository name
135
     *
136
     * @return \GitElephant\Repository
137
     */
138 107
    public static function open($repositoryPath, string $binary = null, $name = null): \GitElephant\Repository
139
    {
140 107
        return new self($repositoryPath, $binary, $name);
141
    }
142
143
    /**
144
     * create a repository from a remote git url, or a local filesystem
145
     * and save it in a temp folder
146
     *
147
     * @param string|Repository $git            the git remote url, or the filesystem path
148
     * @param string|null             $repositoryPath path
149
     * @param string|null $binary         the path to the git binary
150
     * @param string|null             $name           repository name
151
     *
152
     * @throws \RuntimeException
153
     * @throws \Symfony\Component\Filesystem\Exception\IOException
154
     * @return Repository
155
     */
156 1
    public static function createFromRemote(
157
        $git,
158
        $repositoryPath = null,
159
        string $binary = null,
160
        $name = null
161
    ): \GitElephant\Repository {
162 1
        if (null === $repositoryPath) {
163 1
            $tempDir = realpath(sys_get_temp_dir());
164 1
            $repositoryPath = sprintf('%s%s%s', $tempDir, DIRECTORY_SEPARATOR, sha1(uniqid()));
165 1
            $fs = new Filesystem();
166 1
            $fs->mkdir($repositoryPath);
167
        }
168 1
        $repository = new Repository($repositoryPath, $binary, $name);
169 1
        if ($git instanceof Repository) {
170
            $git = $git->getPath();
171
        }
172 1
        $repository->cloneFrom($git, $repositoryPath);
173 1
        $repository->checkoutAllRemoteBranches();
174
175 1
        return $repository;
176
    }
177
178
    /**
179
     * Init the repository
180
     *
181
     * @param bool $bare created a bare repository
182
     *
183
     * @throws \RuntimeException
184
     * @throws \Symfony\Component\Process\Exception\LogicException
185
     * @throws InvalidArgumentException
186
     * @throws \Symfony\Component\Process\Exception\RuntimeException
187
     * @return Repository
188
     */
189 95
    public function init($bare = false): self
190
    {
191 95
        $this->caller->execute(MainCommand::getInstance($this)->init($bare));
192
193 95
        return $this;
194
    }
195
196
    /**
197
     * Stage the working tree content
198
     *
199
     * @param string|NodeObject $path the path to store
200
     *
201
     * @throws \RuntimeException
202
     * @throws \Symfony\Component\Process\Exception\LogicException
203
     * @throws InvalidArgumentException
204
     * @throws \Symfony\Component\Process\Exception\RuntimeException
205
     * @return Repository
206
     */
207 91
    public function stage($path = '.'): self
208
    {
209 91
        $this->caller->execute(MainCommand::getInstance($this)->add($path));
0 ignored issues
show
It seems like $path defined by parameter $path on line 207 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::add() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
210
211 91
        return $this;
212
    }
213
214
    /**
215
     * Unstage a tree content
216
     *
217
     * @param string|NodeObject $path the path to unstage
218
     *
219
     * @throws \RuntimeException
220
     * @throws \Symfony\Component\Process\Exception\LogicException
221
     * @throws InvalidArgumentException
222
     * @throws \Symfony\Component\Process\Exception\RuntimeException
223
     * @return Repository
224
     */
225 2
    public function unstage($path): self
226
    {
227 2
        if ($this->caller instanceof Caller) {
228 2
            $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null, [0, 1]);
0 ignored issues
show
It seems like $path defined by parameter $path on line 225 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::unstage() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
229
        } else {
230
            $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null);
0 ignored issues
show
It seems like $path defined by parameter $path on line 225 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::unstage() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
231
        }
232
233 2
        return $this;
234
    }
235
236
    /**
237
     * Move a file/directory
238
     *
239
     * @param string|NodeObject $from source path
240
     * @param string|NodeObject $to   destination path
241
     *
242
     * @throws \RuntimeException
243
     * @throws \Symfony\Component\Process\Exception\LogicException
244
     * @throws \InvalidArgumentException
245
     * @throws InvalidArgumentException
246
     * @throws \Symfony\Component\Process\Exception\RuntimeException
247
     * @return Repository
248
     */
249 1
    public function move($from, $to): self
250
    {
251 1
        $this->caller->execute(MainCommand::getInstance($this)->move($from, $to));
252
253 1
        return $this;
254
    }
255
256
    /**
257
     * Remove a file/directory
258
     *
259
     * @param string|NodeObject $path      the path to remove
260
     * @param bool              $recursive recurse
261
     * @param bool              $force     force
262
     *
263
     * @throws \RuntimeException
264
     * @throws \Symfony\Component\Process\Exception\LogicException
265
     * @throws \InvalidArgumentException
266
     * @throws InvalidArgumentException
267
     * @throws \Symfony\Component\Process\Exception\RuntimeException
268
     * @return Repository
269
     */
270 1
    public function remove($path, $recursive = false, $force = false): self
271
    {
272 1
        $this->caller->execute(MainCommand::getInstance($this)->remove($path, $recursive, $force));
273
274 1
        return $this;
275
    }
276
277
    /**
278
     * Commit content to the repository, eventually staging all unstaged content
279
     *
280
     * @param string                  $message    the commit message
281
     * @param bool                    $stageAll   whether to stage on not everything before commit
282
     * @param string|null             $ref        the reference to commit to (checkout -> commit -> checkout previous)
283
     * @param string|Author           $author     override the author for this commit
284
     * @param bool                    $allowEmpty override the author for this commit
285
     * @param \DateTimeInterface|null $date
286
     *
287
     * @throws \RuntimeException
288
     * @throws \InvalidArgumentException
289
     * @throws \Symfony\Component\Process\Exception\RuntimeException
290
     * @return Repository
291
     */
292 86
    public function commit(
293
        string $message,
294
        $stageAll = false,
295
        $ref = null,
296
        $author = null,
297
        $allowEmpty = false,
298
        \DateTimeInterface $date = null
299
    ): self {
300 86
        $currentBranch = null;
301 86
        if (!is_null($ref)) {
302 1
            $currentBranch = $this->getMainBranch();
303 1
            $this->checkout($ref);
304
        }
305 86
        if ($stageAll) {
306 84
            $this->stage();
307
        }
308 86
        $this->caller->execute(
309 86
            MainCommand::getInstance($this)->commit($message, $stageAll, $author, $allowEmpty, $date)
310
        );
311 86
        if (!is_null($ref)) {
312 1
            $this->checkout($currentBranch);
0 ignored issues
show
It seems like $currentBranch defined by null on line 300 can be null; however, GitElephant\Repository::checkout() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
313
        }
314
315 86
        return $this;
316
    }
317
318
    /**
319
     * rev-parse command - often used to return a commit tag.
320
     *
321
     * @param array                    $options the options to apply to rev-parse
322
     * @param string|NodeObject|Commit $arg     the argument (may be a branch head, etc)
323
     *
324
     * @throws \RuntimeException
325
     * @throws \InvalidArgumentException
326
     * @throws \Symfony\Component\Process\Exception\RuntimeException
327
     * @return array
328
     */
329 1
    public function revParse($arg = null, array $options = []): array
330
    {
331 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse($arg, $options));
0 ignored issues
show
It seems like $arg defined by parameter $arg on line 329 can also be of type object<GitElephant\Objects\Commit> or object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\RevParseCommand::revParse() does only seem to accept object<GitElephant\Objects\Branch>|string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
332
333 1
        return array_map('trim', $this->caller->getOutputLines(true));
334
    }
335
336
    /**
337
     * Check if this is a bare repository
338
     *
339
     * @return boolean
340
     */
341 1
    public function isBare(): bool
342
    {
343 1
        $options = [RevParseCommand::OPTION_IS_BARE_REPOSIORY];
344 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse(null, $options));
345
346 1
        return trim($this->caller->getOutput()) === 'true';
347
    }
348
349
    /**
350
     * @param TreeishInterface|Commit|string $arg
351
     * @param array                          $options
352
     */
353 2
    public function reset($arg, $options): void
354
    {
355 2
        $this->caller->execute(ResetCommand::getInstance($this)->reset($arg, $options));
356 2
    }
357
358
    /**
359
     * Get the repository status
360
     *
361
     * @return Status
362
     */
363 6
    public function getStatus(): \GitElephant\Status\Status
364
    {
365 6
        return Status::get($this);
366
    }
367
368
    /**
369
     * @return Status
370
     */
371 1
    public function getWorkingTreeStatus(): \GitElephant\Status\Status
372
    {
373 1
        return StatusWorkingTree::get($this);
374
    }
375
376
    /**
377
     * @return Status
378
     */
379 4
    public function getIndexStatus(): \GitElephant\Status\Status
380
    {
381 4
        return StatusIndex::get($this);
382
    }
383
384
    /**
385
     * isClean Return true if the repository is not dirty.
386
     *
387
     * @return boolean
388
     */
389
    public function isClean(): bool
390
    {
391
        return $this->getStatus()->all()->isEmpty();
392
    }
393
394
    /**
395
     * isDirty Return true if the repository has some modified files.
396
     *
397
     * @return boolean
398
     */
399
    public function isDirty(): bool
400
    {
401
        return !$this->isClean();
402
    }
403
404
    /**
405
     * Get the repository status as a string
406
     *
407
     * @throws \RuntimeException
408
     * @throws \Symfony\Component\Process\Exception\LogicException
409
     * @throws InvalidArgumentException
410
     * @throws \Symfony\Component\Process\Exception\RuntimeException
411
     * @return array
412
     */
413 4
    public function getStatusOutput(): array
414
    {
415 4
        $this->caller->execute(MainCommand::getInstance($this)->status());
416
417 4
        return array_map('trim', $this->caller->getOutputLines());
418
    }
419
420
    /**
421
     * Create a new branch
422
     *
423
     * @param string $name       the new branch name
424
     * @param string|null  $startPoint the reference to create the branch from
425
     *
426
     * @throws \RuntimeException
427
     * @throws \Symfony\Component\Process\Exception\RuntimeException
428
     * @return Repository
429
     */
430 27
    public function createBranch(string $name, $startPoint = null): self
431
    {
432 27
        Branch::create($this, $name, $startPoint);
433
434 27
        return $this;
435
    }
436
437
    /**
438
     * Delete a branch by its name
439
     * This function change the state of the repository on the filesystem
440
     *
441
     * @param string $name  The branch to delete
442
     * @param bool   $force Force the delete
443
     *
444
     * @throws \RuntimeException
445
     * @throws \Symfony\Component\Process\Exception\LogicException
446
     * @throws InvalidArgumentException
447
     * @throws \Symfony\Component\Process\Exception\RuntimeException
448
     * @return Repository
449
     */
450 1
    public function deleteBranch(string $name, bool $force = false): self
451
    {
452 1
        $this->caller->execute(BranchCommand::getInstance($this)->delete($name, $force));
453
454 1
        return $this;
455
    }
456
457
    /**
458
     * An array of Branch objects
459
     *
460
     * @param bool $namesOnly return an array of branch names as a string
461
     * @param bool $all       lists also remote branches
462
     *
463
     * @throws \RuntimeException
464
     * @throws InvalidArgumentException
465
     * @throws \Symfony\Component\Process\Exception\LogicException
466
     * @throws \InvalidArgumentException
467
     * @throws \Symfony\Component\Process\Exception\RuntimeException
468
     * @return array
469
     */
470 17
    public function getBranches(bool $namesOnly = false, bool $all = false): array
471
    {
472 17
        $branches = [];
473 17
        if ($namesOnly) {
474 6
            $outputLines = $this->caller
475 6
                ->execute(BranchCommand::getInstance($this)->listBranches($all, true))
476 6
                ->getOutputLines(true);
477
478 6
            $branches = array_map(
479
                function ($v) {
480 6
                    return ltrim($v, '* ');
481 6
                },
482 6
                $outputLines
483
            );
484
        } else {
485 14
            $outputLines = $this->caller
486 14
                ->execute(BranchCommand::getInstance($this)->listBranches($all))
487 14
                ->getOutputLines(true);
488
489 14
            foreach ($outputLines as $branchLine) {
490 14
                $branches[] = Branch::createFromOutputLine($this, $branchLine);
491
            }
492
        }
493
494 17
        return $branches;
495
    }
496
497
    /**
498
     * Return the actually checked out branch
499
     *
500
     * @throws \RuntimeException
501
     * @throws \InvalidArgumentException
502
     * @throws \Symfony\Component\Process\Exception\RuntimeException
503
     * @return Objects\Branch
504
     */
505 5
    public function getMainBranch(): \GitElephant\Objects\Branch
506
    {
507 5
        $filtered = array_filter(
508 5
            $this->getBranches(),
509
            function (Branch $branch) {
510 5
                return $branch->getCurrent();
511 5
            }
512
        );
513 5
        sort($filtered);
514
515 5
        return $filtered[0];
516
    }
517
518
    /**
519
     * Retrieve a Branch object by a branch name
520
     *
521
     * @param string $name The branch name
522
     *
523
     * @throws \RuntimeException
524
     * @throws \InvalidArgumentException
525
     * @throws \Symfony\Component\Process\Exception\RuntimeException
526
     * @return null|Branch
527
     */
528 9
    public function getBranch(string $name): ?\GitElephant\Objects\Branch
529
    {
530
        /** @var Branch $branch */
531 9
        foreach ($this->getBranches() as $branch) {
532 9
            if ($branch->getName() === $name) {
533 9
                return $branch;
534
            }
535
        }
536
537 1
        return null;
538
    }
539
540
    /**
541
     * Checkout all branches from the remote and make them local
542
     *
543
     * @param string $remote remote to fetch from
544
     *
545
     * @throws \RuntimeException
546
     * @throws \InvalidArgumentException
547
     * @throws \Symfony\Component\Process\Exception\RuntimeException
548
     * @return Repository
549
     */
550 1
    public function checkoutAllRemoteBranches($remote = 'origin'): self
551
    {
552 1
        $actualBranch = $this->getMainBranch();
553 1
        $actualBranches = $this->getBranches(true, false);
554 1
        $allBranches = $this->getBranches(true, true);
555
556 1
        $realBranches = array_filter(
557 1
            $allBranches,
558
            function (string $branch) use ($actualBranches) {
559 1
                return !in_array($branch, $actualBranches)
560 1
                    && preg_match('/^remotes(.+)$/', $branch)
561 1
                    && !preg_match('/^(.+)(HEAD)(.*?)$/', $branch);
562 1
            }
563
        );
564
565 1
        foreach ($realBranches as $realBranch) {
566 1
            $this->checkout(str_replace(sprintf('remotes/%s/', $remote), '', $realBranch));
567
        }
568
569 1
        $this->checkout($actualBranch);
570
571 1
        return $this;
572
    }
573
574
    /**
575
     * Merge a Branch in the current checked out branch
576
     *
577
     * @param Objects\Branch $branch  The branch to merge in the current checked out branch
578
     * @param string         $message The message for the merge commit, if merge is 3-way
579
     * @param string         $mode    The merge mode: ff-only, no-ff or auto
580
     *
581
     * @throws \RuntimeException
582
     * @throws \Symfony\Component\Process\Exception\LogicException
583
     * @throws InvalidArgumentException
584
     * @throws \Symfony\Component\Process\Exception\RuntimeException
585
     * @return Repository
586
     */
587 2
    public function merge(Branch $branch, string $message = '', string $mode = 'auto'): self
588
    {
589
        $valid_modes = [
590 2
            'auto', // deafult git behavior
591
            'ff-only', // force fast forward merge
592
            'no-ff', // force 3-way merge
593
        ];
594
595 2
        if (!in_array($mode, $valid_modes)) {
596
            throw new InvalidArgumentException("Invalid merge mode: $mode.");
597
        }
598
599 2
        $options = [];
600 2
        switch ($mode) {
601 2
            case 'ff-only':
602 1
                $options[] = MergeCommand::MERGE_OPTION_FF_ONLY;
603 1
                break;
604 2
            case 'no-ff':
605 1
                $options[] = MergeCommand::MERGE_OPTION_NO_FF;
606 1
                break;
607
        }
608
609 2
        $this->caller->execute(MergeCommand::getInstance($this)->merge($branch, $message, $options));
610
611 2
        return $this;
612
    }
613
614
    /**
615
     * Create a new tag
616
     * This function change the state of the repository on the filesystem
617
     *
618
     * @param string $name       The new tag name
619
     * @param string|null  $startPoint The reference to create the tag from
620
     * @param string|null  $message    the tag message
621
     *
622
     * @throws \RuntimeException
623
     * @throws \Symfony\Component\Process\Exception\RuntimeException
624
     * @return Repository
625
     */
626 25
    public function createTag(string $name, $startPoint = null, string $message = null): self
627
    {
628 25
        Tag::create($this, $name, $startPoint, $message);
629
630 25
        return $this;
631
    }
632
633
    /**
634
     * Delete a tag by it's name or by passing a Tag object
635
     * This function change the state of the repository on the filesystem
636
     *
637
     * @param string|Tag $tag The tag name or the Tag object
638
     *
639
     * @throws \RuntimeException
640
     * @throws \Symfony\Component\Process\Exception\RuntimeException
641
     * @return Repository
642
     */
643 2
    public function deleteTag($tag): self
644
    {
645 2
        if ($tag instanceof Tag) {
646 1
            $tag->delete();
647
        } else {
648 1
            Tag::pick($this, $tag)->delete();
649
        }
650
651 2
        return $this;
652
    }
653
654
    /**
655
     * add a git submodule to the repository
656
     *
657
     * @param string $gitUrl git url of the submodule
658
     * @param string $path   path to register the submodule to
659
     *
660
     * @throws \RuntimeException
661
     * @throws \Symfony\Component\Process\Exception\LogicException
662
     * @throws InvalidArgumentException
663
     * @throws \Symfony\Component\Process\Exception\RuntimeException
664
     * @return Repository
665
     */
666 1
    public function addSubmodule(string $gitUrl, $path = null): self
667
    {
668 1
        $this->caller->execute(SubmoduleCommand::getInstance($this)->add($gitUrl, $path));
669
670 1
        return $this;
671
    }
672
673
    /**
674
     * initialize submodules
675
     *
676
     * @param  string $path init only submodules at the specified path
677
     *
678
     * @return Repository
679
     */
680
    public function initSubmodule($path = null): self
681
    {
682
        $this->caller->execute(SubmoduleCommand::getInstance($this)->init($path));
683
684
        return $this;
685
    }
686
687
    /**
688
     * update submodules
689
     *
690
     * @param  bool   $recursive update recursively
691
     * @param  bool   $init      init before update
692
     * @param  bool   $force     force the checkout as part of update
693
     * @param  string $path      update only a specific submodule path
694
     *
695
     * @return Repository
696
     */
697
    public function updateSubmodule(
698
        bool $recursive = false,
699
        bool $init = false,
700
        bool $force = false,
701
        $path = null
702
    ): self {
703
        $this->caller->execute(SubmoduleCommand::getInstance($this)->update($recursive, $init, $force, $path));
704
705
        return $this;
706
    }
707
708
    /**
709
     * Gets an array of Tag objects
710
     *
711
     * @throws \RuntimeException
712
     * @throws \Symfony\Component\Process\Exception\LogicException
713
     * @throws InvalidArgumentException
714
     * @throws \Symfony\Component\Process\Exception\RuntimeException
715
     * @return array
716
     */
717 4
    public function getTags(): array
718
    {
719 4
        $tags = [];
720 4
        $this->caller->execute(TagCommand::getInstance($this)->listTags());
721
722 4
        foreach ($this->caller->getOutputLines() as $tagString) {
723 4
            if ($tagString != '') {
724 3
                $tags[] = new Tag($this, trim($tagString));
725
            }
726
        }
727
728 4
        return $tags;
729
    }
730
731
    /**
732
     * Return a tag object
733
     *
734
     * @param string $name The tag name
735
     *
736
     * @throws \RuntimeException
737
     * @throws \Symfony\Component\Process\Exception\RuntimeException
738
     * @return Tag|null
739
     */
740 28
    public function getTag(string $name): ?\GitElephant\Objects\Tag
741
    {
742 28
        $tagFinderOutput = $this->caller
743 28
            ->execute(TagCommand::getInstance()->listTags())
744 28
            ->getOutputLines(true);
745
746 28
        foreach ($tagFinderOutput as $line) {
747 28
            if ($line === $name) {
748 28
                return new Tag($this, $name);
749
            }
750
        }
751
752 1
        return null;
753
    }
754
755
    /**
756
     * Return the last created tag
757
     *
758
     * @throws \LogicException
759
     * @throws \RuntimeException
760
     * @throws \InvalidArgumentException
761
     * @return Tag|null
762
     */
763 1
    public function getLastTag(): ?\GitElephant\Objects\Tag
764
    {
765 1
        $finder = Finder::create()
766 1
            ->files()
767 1
            ->in(sprintf('%s/.git/refs/tags', $this->path))
768 1
            ->sortByChangedTime();
769
770 1
        if ($finder->count() === 0) {
771
            return null;
772
        }
773
774 1
        $files = iterator_to_array($finder->getIterator(), false);
775 1
        $files = array_reverse($files);
776
        /** @var SplFileInfo $firstFile */
777 1
        $firstFile = $files[0];
778 1
        $tagName = $firstFile->getFilename();
779
780 1
        return Tag::pick($this, $tagName);
781
    }
782
783
    /**
784
     * Try to get a branch or a tag by its name.
785
     *
786
     * @param string $name the reference name (a tag name or a branch name)
787
     *
788
     * @throws \RuntimeException
789
     * @throws \InvalidArgumentException
790
     * @throws \Symfony\Component\Process\Exception\RuntimeException
791
     * @return \GitElephant\Objects\Tag|\GitElephant\Objects\Branch|null
792
     */
793 1
    public function getBranchOrTag(string $name)
794
    {
795 1
        if (in_array($name, $this->getBranches(true))) {
796 1
            return new Branch($this, $name);
797
        }
798
799 1
        $tagFinderOutput = $this->caller
800 1
            ->execute(TagCommand::getInstance($this)->listTags())
801 1
            ->getOutputLines(true);
802
803 1
        foreach ($tagFinderOutput as $line) {
804 1
            if ($line === $name) {
805 1
                return new Tag($this, $name);
806
            }
807
        }
808
809 1
        return null;
810
    }
811
812
    /**
813
     * Return a Commit object
814
     *
815
     * @param string $ref The commit reference
816
     *
817
     * @throws \RuntimeException
818
     * @return Objects\Commit
819
     */
820 15
    public function getCommit($ref = 'HEAD'): \GitElephant\Objects\Commit
821
    {
822 15
        return Commit::pick($this, $ref);
823
    }
824
825
    /**
826
     * count the commit to arrive to the given treeish
827
     *
828
     * @param string $start
829
     *
830
     * @throws \RuntimeException
831
     * @throws \Symfony\Component\Process\Exception\RuntimeException
832
     * @return int
833
     */
834 3
    public function countCommits($start = 'HEAD'): int
835
    {
836 3
        $commit = Commit::pick($this, $start);
837
838 3
        return $commit->count();
839
    }
840
841
    /**
842
     * Get a log for a ref
843
     *
844
     * @param string|TreeishInterface|array $ref         the treeish to check, as a string, as an object or as an array
845
     * @param string|NodeObject             $path        the physical path to the tree relative to the repository root
846
     * @param int                      $limit       limit to n entries
847
     * @param int|null                      $offset      skip n entries
848
     * @param boolean|false                 $firstParent skip commits brought in to branch by a merge
849
     *
850
     * @return \GitElephant\Objects\Log
851
     */
852 20
    public function getLog(
853
        $ref = 'HEAD',
854
        $path = null,
855
        int $limit = 10,
856
        ?int $offset = null,
857
        bool $firstParent = false
858
    ): \GitElephant\Objects\Log {
859 20
        return new Log($this, $ref, $path, $limit, $offset, $firstParent);
0 ignored issues
show
It seems like $path defined by parameter $path on line 854 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Objects\Log::__construct() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
860
    }
861
862
    /**
863
     * Get a log for a range ref
864
     *
865
     * @param string            $refStart
866
     * @param string            $refEnd
867
     * @param string|NodeObject $path        the physical path to the tree relative to the repository root
868
     * @param int          $limit       limit to n entries
869
     * @param int|null          $offset      skip n entries
870
     * @param boolean|false     $firstParent skip commits brought in to branch by a merge
871
     *
872
     * @return \GitElephant\Objects\LogRange|\GitElephant\Objects\Log
873
     */
874
    public function getLogRange(
875
        $refStart,
876
        $refEnd,
877
        $path = null,
878
        int $limit = 10,
879
        int $offset = null,
880
        bool $firstParent = false
881
    ) {
882
        // Handle when clients provide bad start reference on branch creation
883
        if (preg_match('~^[0]+$~', $refStart)) {
884
            return new Log($this, $refEnd, $path, $limit, $offset, $firstParent);
0 ignored issues
show
It seems like $path defined by parameter $path on line 877 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Objects\Log::__construct() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
885
        }
886
887
        // Handle when clients provide bad end reference on branch deletion
888
        if (preg_match('~^[0]+$~', $refEnd)) {
889
            $refEnd = $refStart;
890
        }
891
892
        return new LogRange($this, $refStart, $refEnd, $path, $limit, $offset, $firstParent);
0 ignored issues
show
It seems like $path defined by parameter $path on line 877 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Objects\LogRange::__construct() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
893
    }
894
895
    /**
896
     * Get a log for an object
897
     *
898
     * @param \GitElephant\Objects\NodeObject         $obj    The Object instance
899
     * @param null|string|\GitElephant\Objects\Branch $branch The branch to read from
900
     * @param int                                     $limit  Limit to n entries
901
     * @param int|null                                $offset Skip n entries
902
     *
903
     * @throws \RuntimeException
904
     * @throws \Symfony\Component\Process\Exception\LogicException
905
     * @throws InvalidArgumentException
906
     * @throws \Symfony\Component\Process\Exception\RuntimeException
907
     * @return \GitElephant\Objects\Log
908
     */
909 3
    public function getObjectLog(
910
        NodeObject $obj,
911
        $branch = null,
912
        int $limit = 1,
913
        int $offset = null
914
    ): \GitElephant\Objects\Log {
915 3
        $command = LogCommand::getInstance($this)->showObjectLog($obj, $branch, $limit, $offset);
916
917 3
        return Log::createFromOutputLines($this, $this->caller->execute($command)->getOutputLines());
918
    }
919
920
    /**
921
     * Checkout a branch
922
     * This function change the state of the repository on the filesystem
923
     *
924
     * @param string|TreeishInterface $ref    the reference to checkout
925
     * @param bool                    $create like -b on the command line
926
     *
927
     * @throws \RuntimeException
928
     * @throws \Symfony\Component\Process\Exception\LogicException
929
     * @throws InvalidArgumentException
930
     * @throws \Symfony\Component\Process\Exception\RuntimeException
931
     * @return Repository
932
     */
933 24
    public function checkout($ref, bool $create = false): self
934
    {
935 24
        if ($create && is_null($this->getBranch($ref))) {
0 ignored issues
show
It seems like $ref defined by parameter $ref on line 933 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Repository::getBranch() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
936
            $this->createBranch($ref);
0 ignored issues
show
It seems like $ref defined by parameter $ref on line 933 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Repository::createBranch() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
937
        }
938 24
        $this->caller->execute(MainCommand::getInstance($this)->checkout($ref));
939
940 24
        return $this;
941
    }
942
943
    /**
944
     * Retrieve an instance of Tree
945
     * Tree Object is Countable, Iterable and has ArrayAccess for easy manipulation
946
     *
947
     * @param string|TreeishInterface $ref  the treeish to check
948
     * @param string|NodeObject       $path Object or null for root
949
     *
950
     * @throws \RuntimeException
951
     * @throws \Symfony\Component\Process\Exception\LogicException
952
     * @throws InvalidArgumentException
953
     * @throws \Symfony\Component\Process\Exception\RuntimeException
954
     * @return Objects\Tree
955
     */
956 15
    public function getTree($ref = 'HEAD', $path = null): \GitElephant\Objects\Tree
957
    {
958 15
        if (is_string($path) && '' !== $path) {
959
            $outputLines = $this
960 9
                ->getCaller()
961 9
                ->execute(LsTreeCommand::getInstance($this)->tree($ref, $path))
962 9
                ->getOutputLines(true);
963
964 9
            $path = TreeObject::createFromOutputLine($this, $outputLines[0]);
965
        }
966
967 15
        return new Tree($this, $ref, $path);
0 ignored issues
show
It seems like $ref defined by parameter $ref on line 956 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Objects\Tree::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $path defined by parameter $path on line 956 can also be of type string; however, GitElephant\Objects\Tree::__construct() does only seem to accept null|object<GitElephant\Objects\NodeObject>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
968
    }
969
970
    /**
971
     * Get a Diff object for a commit with its parent, by default the diff is between the current head and its parent
972
     *
973
     * @param \GitElephant\Objects\Commit|TreeishInterface|string|null $commit1 The first commit to compare
974
     * @param \GitElephant\Objects\Commit|TreeishInterface|string|null $commit2 The commit to compare to
975
     * @param null|string|NodeObject                  $path    The path to get the diff for or a Object instance
976
     *
977
     * @throws \RuntimeException
978
     * @throws \InvalidArgumentException
979
     * @return Objects\Diff\Diff
980
     */
981 2
    public function getDiff(
982
        $commit1 = null,
983
        $commit2 = null,
984
        $path = null
985
    ): \GitElephant\Objects\Diff\Diff {
986 2
        return Diff::create($this, $commit1, $commit2, $path);
0 ignored issues
show
It seems like $commit1 defined by parameter $commit1 on line 982 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Objects\Diff\Diff::create() does only seem to accept null|string|object<GitElephant\Objects\Commit>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $commit2 defined by parameter $commit2 on line 983 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Objects\Diff\Diff::create() does only seem to accept null|string|object<GitElephant\Objects\Commit>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
It seems like $path defined by parameter $path on line 984 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Objects\Diff\Diff::create() does only seem to accept null|string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
987
    }
988
989
    /**
990
     * Clone a repository
991
     *
992
     * @param string      $url           the repository url (i.e. git://github.com/matteosister/GitElephant.git)
993
     * @param string|null       $to            where to clone the repo
994
     * @param string|null $repoReference Repo reference to clone. Required if performing a shallow clone.
995
     * @param int|null    $depth         Depth to clone repo. Specify 1 to perform a shallow clone
996
     * @param bool        $recursive     Whether to recursively clone child repos.
997
     *
998
     * @throws \RuntimeException
999
     * @throws \Symfony\Component\Process\Exception\LogicException
1000
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1001
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1002
     * @return Repository
1003
     */
1004 2
    public function cloneFrom(
1005
        string $url,
1006
        string $to = null,
1007
        string $repoReference = null,
1008
        int $depth = null,
1009
        bool $recursive = false
1010
    ): self {
1011 2
        $command = Command\CloneCommand::getInstance($this)
1012 2
            ->cloneUrl($url, $to, $repoReference, $depth, $recursive);
1013 2
        $this->caller->execute($command);
1014
1015 2
        return $this;
1016
    }
1017
1018
    /**
1019
     * @param string $name remote name
1020
     * @param string $url  remote url
1021
     *
1022
     * @throws \RuntimeException
1023
     * @throws \Symfony\Component\Process\Exception\LogicException
1024
     * @throws InvalidArgumentException
1025
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1026
     * @return Repository
1027
     */
1028 7
    public function addRemote(string $name, string $url): self
1029
    {
1030 7
        $this->caller->execute(RemoteCommand::getInstance($this)->add($name, $url));
1031
1032 7
        return $this;
1033
    }
1034
1035
    /**
1036
     * @param string $name         remote name
1037
     * @param bool   $queryRemotes Fetch new information from remotes
1038
     *
1039
     * @return \GitElephant\Objects\Remote
1040
     */
1041 1
    public function getRemote(string $name, bool $queryRemotes = true): \GitElephant\Objects\Remote
1042
    {
1043 1
        return Remote::pick($this, $name, $queryRemotes);
1044
    }
1045
1046
    /**
1047
     * gets a list of remote objects
1048
     *
1049
     * @param bool $queryRemotes Fetch new information from remotes
1050
     *
1051
     * @throws \RuntimeException
1052
     * @throws \Symfony\Component\Process\Exception\LogicException
1053
     * @throws InvalidArgumentException
1054
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1055
     * @return array
1056
     */
1057 1
    public function getRemotes(bool $queryRemotes = true): array
1058
    {
1059 1
        $remoteNames = $this->caller
1060 1
            ->execute(RemoteCommand::getInstance($this)->show(null, $queryRemotes))
1061 1
            ->getOutputLines(true);
1062
1063 1
        $remotes = [];
1064 1
        foreach ($remoteNames as $remoteName) {
1065 1
            $remotes[] = $this->getRemote($remoteName, $queryRemotes);
1066
        }
1067
1068 1
        return $remotes;
1069
    }
1070
1071
    /**
1072
     * Download objects and refs from another repository
1073
     *
1074
     * @param string $from
1075
     * @param string $ref
1076
     * @param bool   $tags
1077
     *
1078
     * @throws \RuntimeException
1079
     * @throws \Symfony\Component\Process\Exception\LogicException
1080
     * @throws InvalidArgumentException
1081
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1082
     */
1083 1
    public function fetch($from = null, $ref = null, bool $tags = false): void
1084
    {
1085 1
        $options = [];
1086 1
        if ($tags) {
1087 1
            $options = ['--tags'];
1088
        }
1089 1
        $this->caller->execute(FetchCommand::getInstance($this)->fetch($from, $ref, $options));
1090 1
    }
1091
1092
    /**
1093
     * Fetch from and merge with another repository or a local branch
1094
     *
1095
     * @param string $from
1096
     * @param string $ref
1097
     * @param bool   $rebase
1098
     *
1099
     * @throws \RuntimeException
1100
     * @throws \Symfony\Component\Process\Exception\LogicException
1101
     * @throws InvalidArgumentException
1102
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1103
     */
1104 2
    public function pull($from = null, $ref = null, bool $rebase = true): void
1105
    {
1106 2
        $this->caller->execute(PullCommand::getInstance($this)->pull($from, $ref, $rebase));
1107 2
    }
1108
1109
    /**
1110
     * Push changes to remote repository
1111
     *
1112
     * @param string $to
1113
     * @param string $ref
1114
     * @param string $args
1115
     *
1116
     * @throws \RuntimeException
1117
     * @throws \Symfony\Component\Process\Exception\LogicException
1118
     * @throws InvalidArgumentException
1119
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1120
     */
1121 1
    public function push($to = null, $ref = null, string $args = null): void
1122
    {
1123 1
        $this->caller->execute(PushCommand::getInstance($this)->push($to, $ref, $args));
1124 1
    }
1125
1126
    /**
1127
     * get the humanish name of the repository
1128
     *
1129
     * @return string
1130
     */
1131 2
    public function getHumanishName(): string
1132
    {
1133 2
        $name = substr($this->getPath(), strrpos($this->getPath(), '/') + 1);
1134 2
        $name = str_replace('.git', '.', $name);
1135
1136 2
        return str_replace('.bundle', '.', $name);
1137
    }
1138
1139
    /**
1140
     * output a node content as an array of lines
1141
     *
1142
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1143
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1144
     *
1145
     * @throws \RuntimeException
1146
     * @throws \Symfony\Component\Process\Exception\LogicException
1147
     * @throws InvalidArgumentException
1148
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1149
     * @return array
1150
     */
1151 1
    public function outputContent(NodeObject $obj, $treeish): array
1152
    {
1153 1
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1154
1155 1
        return $this->caller->execute($command)->getOutputLines();
1156
    }
1157
1158
    /**
1159
     * output a node raw content
1160
     *
1161
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1162
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1163
     *
1164
     * @throws \RuntimeException
1165
     * @throws \Symfony\Component\Process\Exception\LogicException
1166
     * @throws InvalidArgumentException
1167
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1168
     * @return string
1169
     */
1170
    public function outputRawContent(NodeObject $obj, $treeish): string
1171
    {
1172
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1173
1174
        return $this->caller->execute($command)->getRawOutput();
1175
    }
1176
1177
    /**
1178
     * Get the path
1179
     *
1180
     * @return string
1181
     */
1182 64
    public function getPath(): string
1183
    {
1184 64
        return $this->path;
1185
    }
1186
1187
    /**
1188
     * Get the repository name
1189
     *
1190
     * @return string
1191
     */
1192 1
    public function getName(): string
1193
    {
1194 1
        return $this->name;
1195
    }
1196
1197
    /**
1198
     * Set the repository name
1199
     *
1200
     * @param string $name the repository name
1201
     */
1202 1
    public function setName(string $name): void
1203
    {
1204 1
        $this->name = $name;
1205 1
    }
1206
1207
    /**
1208
     * Caller setter
1209
     *
1210
     * @param CallerInterface $caller the caller variable
1211
     */
1212
    public function setCaller(CallerInterface $caller): void
1213
    {
1214
        $this->caller = $caller;
1215
    }
1216
1217
    /**
1218
     * Caller getter
1219
     *
1220
     * @return CallerInterface the caller to use to call commands
1221
     */
1222 92
    public function getCaller(): CallerInterface
1223
    {
1224 92
        return $this->caller;
1225
    }
1226
1227
    /**
1228
     * get global config list
1229
     *
1230
     * @return array Global config list
1231
     */
1232 98
    public function getGlobalConfigs(): array
1233
    {
1234 98
        return $this->globalConfigs;
1235
    }
1236
1237
    /**
1238
     * add a key/value pair to the global config list
1239
     *
1240
     * @param string $name  The config name
1241
     * @param mixed  $value The config value
1242
     */
1243 1
    public function addGlobalConfig(string $name, $value): void
1244
    {
1245 1
        $this->globalConfigs[$name] = $value;
1246 1
    }
1247
1248
    /**
1249
     * remove an element form the global config list, identified by key
1250
     *
1251
     * @param  string $name The config name
1252
     */
1253 1
    public function removeGlobalConfig(string $name): void
1254
    {
1255 1
        if (isset($this->globalConfigs[$name])) {
1256 1
            unset($this->globalConfigs[$name]);
1257
        }
1258 1
    }
1259
1260
    /**
1261
     * get global options list
1262
     *
1263
     * @return array Global options list
1264
     */
1265 98
    public function getGlobalOptions(): array
1266
    {
1267 98
        return $this->globalOptions;
1268
    }
1269
1270
    /**
1271
     * add a key/value pair to the global option list
1272
     *
1273
     * @param string $name  The option name
1274
     * @param mixed  $value The option value
1275
     */
1276 1
    public function addGlobalOption(string $name, $value): void
1277
    {
1278 1
        $this->globalOptions[$name] = $value;
1279 1
    }
1280
1281
    /**
1282
     * remove an element form the global option list, identified by key
1283
     *
1284
     * @param  string $name The option name
1285
     */
1286 1
    public function removeGlobalOption(string $name): void
1287
    {
1288 1
        if (isset($this->globalOptions[$name])) {
1289 1
            unset($this->globalOptions[$name]);
1290
        }
1291 1
    }
1292
1293
    /**
1294
     * get global command arguments list
1295
     *
1296
     * @return array Global command arguments list
1297
     */
1298 98
    public function getGlobalCommandArguments(): array
1299
    {
1300 98
        return $this->globalCommandArguments;
1301
    }
1302
1303
    /**
1304
     * add a value to the global command argument list
1305
     *
1306
     * @param string $value The command argument
1307
     */
1308 1
    public function addGlobalCommandArgument($value): void
1309
    {
1310 1
        if (!in_array($value, $this->globalCommandArguments, true)) {
1311 1
            $this->globalCommandArguments[] = $value;
1312
        }
1313 1
    }
1314
1315
    /**
1316
     * remove an element form the global command argument list, identified by
1317
     * value
1318
     *
1319
     * @param  string $value The command argument
1320
     */
1321 1
    public function removeGlobalCommandArgument($value): void
1322
    {
1323 1
        if (in_array($value, $this->globalCommandArguments, true)) {
1324 1
            $index = array_search($value, $this->globalCommandArguments);
1325 1
            unset($this->globalCommandArguments[$index]);
1326
        }
1327 1
    }
1328
1329
    /**
1330
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1331
     *
1332
     * @param string|null $message
1333
     * @param boolean     $includeUntracked
1334
     * @param boolean     $keepIndex
1335
     */
1336 2
    public function stash(string $message = null, bool $includeUntracked = false, bool $keepIndex = false): void
1337
    {
1338 2
        $stashCommand = StashCommand::getInstance($this);
1339 2
        $command = $stashCommand->save($message, $includeUntracked, $keepIndex);
1340 2
        $this->caller->execute($command);
1341 1
    }
1342
1343
    /**
1344
     * Shows stash list
1345
     *
1346
     * @param array|null $options
1347
     *
1348
     * @return array
1349
     */
1350 1
    public function stashList(array $options = null): array
1351
    {
1352 1
        $stashCommand = StashCommand::getInstance($this);
1353 1
        $command = $stashCommand->listStashes($options);
1354 1
        $this->caller->execute($command);
1355
1356 1
        return array_map('trim', $this->caller->getOutputLines(true));
1357
    }
1358
1359
    /**
1360
     * Shows details for a stash
1361
     *
1362
     * @param string|int $stash
1363
     *
1364
     * @return string
1365
     */
1366 1
    public function stashShow($stash): string
1367
    {
1368 1
        $stashCommand = StashCommand::getInstance($this);
1369 1
        $command = $stashCommand->show($stash);
1370 1
        $this->caller->execute($command);
1371
1372 1
        return $this->caller->getOutput();
1373
    }
1374
1375
    /**
1376
     * Drops a stash
1377
     *
1378
     * @param string|int $stash
1379
     */
1380 1
    public function stashDrop($stash): void
1381
    {
1382 1
        $stashCommand = StashCommand::getInstance($this);
1383 1
        $command = $stashCommand->drop($stash);
1384 1
        $this->caller->execute($command);
1385 1
    }
1386
1387
    /**
1388
     * Applies a stash
1389
     *
1390
     * @param string|int $stash
1391
     * @param boolean $index
1392
     */
1393 1
    public function stashApply($stash, bool $index = false): void
1394
    {
1395 1
        $stashCommand = StashCommand::getInstance($this);
1396 1
        $command = $stashCommand->apply($stash, $index);
1397 1
        $this->caller->execute($command);
1398 1
    }
1399
1400
    /**
1401
     *  Applies a stash, then removes it from the stash
1402
     *
1403
     * @param string|int $stash
1404
     * @param boolean $index
1405
     */
1406 1
    public function stashPop($stash, bool $index = false): void
1407
    {
1408 1
        $stashCommand = StashCommand::getInstance($this);
1409 1
        $command = $stashCommand->pop($stash, $index);
1410 1
        $this->caller->execute($command);
1411 1
    }
1412
1413
    /**
1414
     *  Creates and checks out a new branch named <branchname> starting from the commit at which the <stash> was originally created
1415
     *
1416
     * @param string $branch
1417
     * @param string|int $stash
1418
     */
1419 1
    public function stashBranch(string $branch, $stash): void
1420
    {
1421 1
        $stashCommand = StashCommand::getInstance($this);
1422 1
        $command = $stashCommand->branch($branch, $stash);
1423 1
        $this->caller->execute($command);
1424 1
    }
1425
1426
    /**
1427
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1428
     */
1429
    public function stashClear(): void
1430
    {
1431
        $stashCommand = StashCommand::getInstance($this);
1432
        $command = $stashCommand->clear();
1433
        $this->caller->execute($command);
1434
    }
1435
1436
    /**
1437
     *  Create a stash (which is a regular commit object) and return its object name, without storing it anywhere in the
1438
     *  ref namespace.
1439
     *
1440
     * @return string
1441
     */
1442 1
    public function stashCreate(): string
1443
    {
1444 1
        $stashCommand = StashCommand::getInstance($this);
1445 1
        $command = $stashCommand->clear();
1446 1
        $this->caller->execute($command);
1447
1448 1
        return $this->caller->getOutput();
1449
    }
1450
}
1451