Completed
Pull Request — develop (#168)
by
unknown
01:46
created

Repository::getSubmodules()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 0
cts 8
cp 0
rs 9.7666
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 12
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\Caller
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 null              $repositoryPath path
149
     * @param string|null $binary         the path to the git binary
150
     * @param 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
Bug introduced by
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
        $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null, [0, 1]);
0 ignored issues
show
Bug introduced by
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...
228
229 2
        return $this;
230
    }
231
232
    /**
233
     * Move a file/directory
234
     *
235
     * @param string|NodeObject $from source path
236
     * @param string|NodeObject $to   destination path
237
     *
238
     * @throws \RuntimeException
239
     * @throws \Symfony\Component\Process\Exception\LogicException
240
     * @throws \InvalidArgumentException
241
     * @throws InvalidArgumentException
242
     * @throws \Symfony\Component\Process\Exception\RuntimeException
243
     * @return Repository
244
     */
245 1
    public function move($from, $to): self
246
    {
247 1
        $this->caller->execute(MainCommand::getInstance($this)->move($from, $to));
248
249 1
        return $this;
250
    }
251
252
    /**
253
     * Remove a file/directory
254
     *
255
     * @param string|NodeObject $path      the path to remove
256
     * @param bool              $recursive recurse
257
     * @param bool              $force     force
258
     *
259
     * @throws \RuntimeException
260
     * @throws \Symfony\Component\Process\Exception\LogicException
261
     * @throws \InvalidArgumentException
262
     * @throws InvalidArgumentException
263
     * @throws \Symfony\Component\Process\Exception\RuntimeException
264
     * @return Repository
265
     */
266 1
    public function remove($path, $recursive = false, $force = false): self
267
    {
268 1
        $this->caller->execute(MainCommand::getInstance($this)->remove($path, $recursive, $force));
269
270 1
        return $this;
271
    }
272
273
    /**
274
     * Commit content to the repository, eventually staging all unstaged content
275
     *
276
     * @param string        $message    the commit message
277
     * @param bool          $stageAll   whether to stage on not everything before commit
278
     * @param string|null   $ref        the reference to commit to (checkout -> commit -> checkout previous)
279
     * @param string|Author $author     override the author for this commit
280
     * @param bool          $allowEmpty override the author for this commit
281
     *
282
     * @throws \RuntimeException
283
     * @throws \InvalidArgumentException
284
     * @throws \Symfony\Component\Process\Exception\RuntimeException
285
     * @return Repository
286
     */
287 86
    public function commit(string $message, $stageAll = false, $ref = null, $author = null, $allowEmpty = false): self
288
    {
289 86
        $currentBranch = null;
290 86
        if (!is_null($ref)) {
291 1
            $currentBranch = $this->getMainBranch();
292 1
            $this->checkout($ref);
293
        }
294 86
        if ($stageAll) {
295 84
            $this->stage();
296
        }
297 86
        $this->caller->execute(MainCommand::getInstance($this)->commit($message, $stageAll, $author, $allowEmpty));
298 86
        if (!is_null($ref)) {
299 1
            $this->checkout($currentBranch);
0 ignored issues
show
Bug introduced by
It seems like $currentBranch defined by null on line 289 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...
300
        }
301
302 86
        return $this;
303
    }
304
305
    /**
306
     * rev-parse command - often used to return a commit tag.
307
     *
308
     * @param array                    $options the options to apply to rev-parse
309
     * @param string|NodeObject|Commit $arg     the argument (may be a branch head, etc)
310
     *
311
     * @throws \RuntimeException
312
     * @throws \InvalidArgumentException
313
     * @throws \Symfony\Component\Process\Exception\RuntimeException
314
     * @return array
315
     */
316 1
    public function revParse(string $arg = null, array $options = []): array
317
    {
318 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse($arg, $options));
319
320 1
        return array_map('trim', $this->caller->getOutputLines(true));
321
    }
322
323
    /**
324
     * Check if this is a bare repository
325
     *
326
     * @return boolean
327
     */
328 1
    public function isBare(): bool
329
    {
330 1
        $options = [RevParseCommand::OPTION_IS_BARE_REPOSIORY];
331 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse(null, $options));
332
333 1
        return trim($this->caller->getOutput()) === 'true';
334
    }
335
336
    /**
337
     * @param TreeishInterface|Commit|string $arg
338
     * @param array                          $options
339
     */
340 2
    public function reset($arg, $options): void
341
    {
342 2
        $this->caller->execute(ResetCommand::getInstance($this)->reset($arg, $options));
343 2
    }
344
345
    /**
346
     * Get the repository status
347
     *
348
     * @return Status
349
     */
350 6
    public function getStatus(): \GitElephant\Status\Status
351
    {
352 6
        return Status::get($this);
353
    }
354
355
    /**
356
     * @return Status
357
     */
358 1
    public function getWorkingTreeStatus(): \GitElephant\Status\Status
359
    {
360 1
        return StatusWorkingTree::get($this);
361
    }
362
363
    /**
364
     * @return Status
365
     */
366 4
    public function getIndexStatus(): \GitElephant\Status\Status
367
    {
368 4
        return StatusIndex::get($this);
369
    }
370
371
    /**
372
     * isClean Return true if the repository is not dirty.
373
     *
374
     * @return boolean
375
     */
376
    public function isClean(): bool
377
    {
378
        return $this->getStatus()->all()->isEmpty();
379
    }
380
381
    /**
382
     * isDirty Return true if the repository has some modified files.
383
     *
384
     * @return boolean
385
     */
386
    public function isDirty(): bool
387
    {
388
        return !$this->isClean();
389
    }
390
391
    /**
392
     * Get the repository status as a string
393
     *
394
     * @throws \RuntimeException
395
     * @throws \Symfony\Component\Process\Exception\LogicException
396
     * @throws InvalidArgumentException
397
     * @throws \Symfony\Component\Process\Exception\RuntimeException
398
     * @return array
399
     */
400 4
    public function getStatusOutput(): array
401
    {
402 4
        $this->caller->execute(MainCommand::getInstance($this)->status());
403
404 4
        return array_map('trim', $this->caller->getOutputLines());
405
    }
406
407
    /**
408
     * Create a new branch
409
     *
410
     * @param string $name       the new branch name
411
     * @param null   $startPoint the reference to create the branch from
412
     *
413
     * @throws \RuntimeException
414
     * @throws \Symfony\Component\Process\Exception\RuntimeException
415
     * @return Repository
416
     */
417 27
    public function createBranch(string $name, $startPoint = null): self
418
    {
419 27
        Branch::create($this, $name, $startPoint);
420
421 27
        return $this;
422
    }
423
424
    /**
425
     * Delete a branch by its name
426
     * This function change the state of the repository on the filesystem
427
     *
428
     * @param string $name  The branch to delete
429
     * @param bool   $force Force the delete
430
     *
431
     * @throws \RuntimeException
432
     * @throws \Symfony\Component\Process\Exception\LogicException
433
     * @throws InvalidArgumentException
434
     * @throws \Symfony\Component\Process\Exception\RuntimeException
435
     * @return Repository
436
     */
437 1
    public function deleteBranch(string $name, bool $force = false): self
438
    {
439 1
        $this->caller->execute(BranchCommand::getInstance($this)->delete($name, $force));
440
441 1
        return $this;
442
    }
443
444
    /**
445
     * An array of Branch objects
446
     *
447
     * @param bool $namesOnly return an array of branch names as a string
448
     * @param bool $all       lists also remote branches
449
     *
450
     * @throws \RuntimeException
451
     * @throws InvalidArgumentException
452
     * @throws \Symfony\Component\Process\Exception\LogicException
453
     * @throws \InvalidArgumentException
454
     * @throws \Symfony\Component\Process\Exception\RuntimeException
455
     * @return array
456
     */
457 17
    public function getBranches(bool $namesOnly = false, bool $all = false): array
458
    {
459 17
        $branches = [];
460 17
        if ($namesOnly) {
461 6
            $outputLines = $this->caller
462 6
                ->execute(BranchCommand::getInstance($this)->listBranches($all, true))
463 6
                ->getOutputLines(true);
464
465 6
            $branches = array_map(
466
                function ($v) {
467 6
                    return ltrim($v, '* ');
468 6
                },
469 6
                $outputLines
470
            );
471
        } else {
472 14
            $outputLines = $this->caller
473 14
                ->execute(BranchCommand::getInstance($this)->listBranches($all))
474 14
                ->getOutputLines(true);
475
476 14
            foreach ($outputLines as $branchLine) {
477 14
                $branches[] = Branch::createFromOutputLine($this, $branchLine);
478
            }
479
        }
480
481 17
        return $branches;
482
    }
483
484
    /**
485
     * Return the actually checked out branch
486
     *
487
     * @throws \RuntimeException
488
     * @throws \InvalidArgumentException
489
     * @throws \Symfony\Component\Process\Exception\RuntimeException
490
     * @return Objects\Branch
491
     */
492 5
    public function getMainBranch(): \GitElephant\Objects\Branch
493
    {
494 5
        $filtered = array_filter(
495 5
            $this->getBranches(),
496
            function (Branch $branch) {
497 5
                return $branch->getCurrent();
498 5
            }
499
        );
500 5
        sort($filtered);
501
502 5
        return $filtered[0];
503
    }
504
505
    /**
506
     * Retrieve a Branch object by a branch name
507
     *
508
     * @param string $name The branch name
509
     *
510
     * @throws \RuntimeException
511
     * @throws \InvalidArgumentException
512
     * @throws \Symfony\Component\Process\Exception\RuntimeException
513
     * @return null|Branch
514
     */
515 9
    public function getBranch(string $name): ?\GitElephant\Objects\Branch
516
    {
517
        /** @var Branch $branch */
518 9
        foreach ($this->getBranches() as $branch) {
519 9
            if ($branch->getName() === $name) {
520 9
                return $branch;
521
            }
522
        }
523
524 1
        return null;
525
    }
526
527
    /**
528
     * Checkout all branches from the remote and make them local
529
     *
530
     * @param string $remote remote to fetch from
531
     *
532
     * @throws \RuntimeException
533
     * @throws \InvalidArgumentException
534
     * @throws \Symfony\Component\Process\Exception\RuntimeException
535
     * @return Repository
536
     */
537 1
    public function checkoutAllRemoteBranches($remote = 'origin'): self
538
    {
539 1
        $actualBranch = $this->getMainBranch();
540 1
        $actualBranches = $this->getBranches(true, false);
541 1
        $allBranches = $this->getBranches(true, true);
542
543 1
        $realBranches = array_filter(
544 1
            $allBranches,
545
            function (string $branch) use ($actualBranches) {
546 1
                return !in_array($branch, $actualBranches)
547 1
                    && preg_match('/^remotes(.+)$/', $branch)
548 1
                    && !preg_match('/^(.+)(HEAD)(.*?)$/', $branch);
549 1
            }
550
        );
551
552 1
        foreach ($realBranches as $realBranch) {
553 1
            $this->checkout(str_replace(sprintf('remotes/%s/', $remote), '', $realBranch));
554
        }
555
556 1
        $this->checkout($actualBranch);
557
558 1
        return $this;
559
    }
560
561
    /**
562
     * Merge a Branch in the current checked out branch
563
     *
564
     * @param Objects\Branch $branch  The branch to merge in the current checked out branch
565
     * @param string         $message The message for the merge commit, if merge is 3-way
566
     * @param string         $mode    The merge mode: ff-only, no-ff or auto
567
     *
568
     * @throws \RuntimeException
569
     * @throws \Symfony\Component\Process\Exception\LogicException
570
     * @throws InvalidArgumentException
571
     * @throws \Symfony\Component\Process\Exception\RuntimeException
572
     * @return Repository
573
     */
574 2
    public function merge(Branch $branch, string $message = '', string $mode = 'auto'): self
575
    {
576
        $valid_modes = [
577 2
            'auto', // deafult git behavior
578
            'ff-only', // force fast forward merge
579
            'no-ff', // force 3-way merge
580
        ];
581
582 2
        if (!in_array($mode, $valid_modes)) {
583
            throw new InvalidArgumentException("Invalid merge mode: $mode.");
584
        }
585
586 2
        $options = [];
587 2
        switch ($mode) {
588 2
            case 'ff-only':
589 1
                $options[] = MergeCommand::MERGE_OPTION_FF_ONLY;
590 1
                break;
591 2
            case 'no-ff':
592 1
                $options[] = MergeCommand::MERGE_OPTION_NO_FF;
593 1
                break;
594
        }
595
596 2
        $this->caller->execute(MergeCommand::getInstance($this)->merge($branch, $message, $options));
597
598 2
        return $this;
599
    }
600
601
    /**
602
     * Create a new tag
603
     * This function change the state of the repository on the filesystem
604
     *
605
     * @param string $name       The new tag name
606
     * @param null   $startPoint The reference to create the tag from
607
     * @param null   $message    the tag message
608
     *
609
     * @throws \RuntimeException
610
     * @throws \Symfony\Component\Process\Exception\RuntimeException
611
     * @return Repository
612
     */
613 25
    public function createTag(string $name, $startPoint = null, string $message = null): self
614
    {
615 25
        Tag::create($this, $name, $startPoint, $message);
616
617 25
        return $this;
618
    }
619
620
    /**
621
     * Delete a tag by it's name or by passing a Tag object
622
     * This function change the state of the repository on the filesystem
623
     *
624
     * @param string|Tag $tag The tag name or the Tag object
625
     *
626
     * @throws \RuntimeException
627
     * @throws \Symfony\Component\Process\Exception\RuntimeException
628
     * @return Repository
629
     */
630 2
    public function deleteTag($tag): self
631
    {
632 2
        if ($tag instanceof Tag) {
633 1
            $tag->delete();
634
        } else {
635 1
            Tag::pick($this, $tag)->delete();
636
        }
637
638 2
        return $this;
639
    }
640
641
    /**
642
     * add a git submodule to the repository
643
     *
644
     * @param string $gitUrl git url of the submodule
645
     * @param string $path   path to register the submodule to
646
     *
647
     * @throws \RuntimeException
648
     * @throws \Symfony\Component\Process\Exception\LogicException
649
     * @throws InvalidArgumentException
650
     * @throws \Symfony\Component\Process\Exception\RuntimeException
651
     * @return Repository
652
     */
653 1
    public function addSubmodule(string $gitUrl, $path = null): self
654
    {
655 1
        $this->caller->execute(SubmoduleCommand::getInstance($this)->add($gitUrl, $path));
656
657 1
        return $this;
658
    }
659
660
    /**
661
     * initialize submodules
662
     *
663
     * @param  string $path init only submodules at the specified path
664
     *
665
     * @return Repository
666
     */
667
    public function initSubmodule($path = null): self
668
    {
669
        $this->caller->execute(SubmoduleCommand::getInstance($this)->init($path));
670
671
        return $this;
672
    }
673
674
    public function getSubmodules(): array
675
    {
676
        $submodules = [];
677
        $this->caller->execute(SubmoduleCommand::getInstance($this)->listSubmodules());
678
679
        var_dump($this->caller->getOutputLines());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($this->caller->getOutputLines()); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
680
681
        foreach ($this->caller->getOutputLines() as $submoduleString) {
682
            if ($submoduleString != '') {
683
                $submodules[] = new Submodule($this, trim($submoduleString));
684
            }
685
        }
686
687
        return $submodules;
688
    }
689
690
    /**
691
     * update submodules
692
     *
693
     * @param  bool   $recursive update recursively
694
     * @param  bool   $init      init before update
695
     * @param  bool   $force     force the checkout as part of update
696
     * @param  string $path      update only a specific submodule path
697
     *
698
     * @return Repository
699
     */
700
    public function updateSubmodule(
701
        bool $recursive = false,
702
        bool $init = false,
703
        bool $force = false,
704
        $path = null
705
    ): self {
706
        $this->caller->execute(SubmoduleCommand::getInstance($this)->update($recursive, $init, $force, $path));
707
708
        return $this;
709
    }
710
711
    /**
712
     * Gets an array of Tag objects
713
     *
714
     * @throws \RuntimeException
715
     * @throws \Symfony\Component\Process\Exception\LogicException
716
     * @throws InvalidArgumentException
717
     * @throws \Symfony\Component\Process\Exception\RuntimeException
718
     * @return array
719
     */
720 4
    public function getTags(): array
721
    {
722 4
        $tags = [];
723 4
        $this->caller->execute(TagCommand::getInstance($this)->listTags());
724
725 4
        foreach ($this->caller->getOutputLines() as $tagString) {
726 4
            if ($tagString != '') {
727 3
                $tags[] = new Tag($this, trim($tagString));
728
            }
729
        }
730
731 4
        return $tags;
732
    }
733
734
    /**
735
     * Return a tag object
736
     *
737
     * @param string $name The tag name
738
     *
739
     * @throws \RuntimeException
740
     * @throws \Symfony\Component\Process\Exception\RuntimeException
741
     * @return Tag|null
742
     */
743 28
    public function getTag(string $name): ?\GitElephant\Objects\Tag
744
    {
745 28
        $tagFinderOutput = $this->caller
746 28
            ->execute(TagCommand::getInstance()->listTags())
747 28
            ->getOutputLines(true);
748
749 28
        foreach ($tagFinderOutput as $line) {
750 28
            if ($line === $name) {
751 28
                return new Tag($this, $name);
752
            }
753
        }
754
755 1
        return null;
756
    }
757
758
    /**
759
     * Return the last created tag
760
     *
761
     * @throws \LogicException
762
     * @throws \RuntimeException
763
     * @throws \InvalidArgumentException
764
     * @return Tag|null
765
     */
766 1
    public function getLastTag(): ?\GitElephant\Objects\Tag
767
    {
768 1
        $finder = Finder::create()
769 1
            ->files()
770 1
            ->in(sprintf('%s/.git/refs/tags', $this->path))
771 1
            ->sortByChangedTime();
772
773 1
        if ($finder->count() === 0) {
774
            return null;
775
        }
776
777 1
        $files = iterator_to_array($finder->getIterator(), false);
778 1
        $files = array_reverse($files);
779
        /** @var SplFileInfo $firstFile */
780 1
        $firstFile = $files[0];
781 1
        $tagName = $firstFile->getFilename();
782
783 1
        return Tag::pick($this, $tagName);
784
    }
785
786
    /**
787
     * Try to get a branch or a tag by its name.
788
     *
789
     * @param string $name the reference name (a tag name or a branch name)
790
     *
791
     * @throws \RuntimeException
792
     * @throws \InvalidArgumentException
793
     * @throws \Symfony\Component\Process\Exception\RuntimeException
794
     * @return \GitElephant\Objects\Tag|\GitElephant\Objects\Branch|null
795
     */
796 1
    public function getBranchOrTag(string $name)
797
    {
798 1
        if (in_array($name, $this->getBranches(true))) {
799 1
            return new Branch($this, $name);
800
        }
801
802 1
        $tagFinderOutput = $this->caller
803 1
            ->execute(TagCommand::getInstance($this)
804 1
                ->listTags())->getOutputLines(true);
805
806 1
        foreach ($tagFinderOutput as $line) {
807 1
            if ($line === $name) {
808 1
                return new Tag($this, $name);
809
            }
810
        }
811
812 1
        return null;
813
    }
814
815
    /**
816
     * Return a Commit object
817
     *
818
     * @param string $ref The commit reference
819
     *
820
     * @throws \RuntimeException
821
     * @return Objects\Commit
822
     */
823 15
    public function getCommit($ref = 'HEAD'): \GitElephant\Objects\Commit
824
    {
825 15
        return Commit::pick($this, $ref);
826
    }
827
828
    /**
829
     * count the commit to arrive to the given treeish
830
     *
831
     * @param string $start
832
     *
833
     * @throws \RuntimeException
834
     * @throws \Symfony\Component\Process\Exception\RuntimeException
835
     * @return int
836
     */
837 3
    public function countCommits($start = 'HEAD'): int
838
    {
839 3
        $commit = Commit::pick($this, $start);
840
841 3
        return $commit->count();
842
    }
843
844
    /**
845
     * Get a log for a ref
846
     *
847
     * @param string|TreeishInterface|array $ref         the treeish to check, as a string, as an object or as an array
848
     * @param string|NodeObject             $path        the physical path to the tree relative to the repository root
849
     * @param int|null                      $limit       limit to n entries
850
     * @param int|null                      $offset      skip n entries
851
     * @param boolean|false                 $firstParent skip commits brought in to branch by a merge
852
     *
853
     * @return \GitElephant\Objects\Log
854
     */
855 20
    public function getLog(
856
        $ref = 'HEAD',
857
        $path = null,
858
        int $limit = 10,
859
        int $offset = null,
860
        bool $firstParent = false
861
    ): \GitElephant\Objects\Log {
862 20
        return new Log($this, $ref, $path, $limit, $offset, $firstParent);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 857 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...
863
    }
864
865
    /**
866
     * Get a log for a range ref
867
     *
868
     * @param string            $refStart
869
     * @param string            $refEnd
870
     * @param string|NodeObject $path        the physical path to the tree relative to the repository root
871
     * @param int|null          $limit       limit to n entries
872
     * @param int|null          $offset      skip n entries
873
     * @param boolean|false     $firstParent skip commits brought in to branch by a merge
874
     *
875
     * @return \GitElephant\Objects\LogRange|\GitElephant\Objects\Log
876
     */
877
    public function getLogRange(
878
        $refStart,
879
        $refEnd,
880
        $path = null,
881
        int $limit = 10,
882
        int $offset = null,
883
        bool $firstParent = false
884
    ) {
885
        // Handle when clients provide bad start reference on branch creation
886
        if (preg_match('~^[0]+$~', $refStart)) {
887
            return new Log($this, $refEnd, $path, $limit, $offset, $firstParent);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 880 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...
888
        }
889
890
        // Handle when clients provide bad end reference on branch deletion
891
        if (preg_match('~^[0]+$~', $refEnd)) {
892
            $refEnd = $refStart;
893
        }
894
895
        return new LogRange($this, $refStart, $refEnd, $path, $limit, $offset, $firstParent);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 880 can also be of type object<GitElephant\Objects\NodeObject> or string; however, GitElephant\Objects\LogRange::__construct() does only seem to accept 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...
896
    }
897
898
    /**
899
     * Get a log for an object
900
     *
901
     * @param \GitElephant\Objects\NodeObject         $obj    The Object instance
902
     * @param null|string|\GitElephant\Objects\Branch $branch The branch to read from
903
     * @param int                                     $limit  Limit to n entries
904
     * @param int|null                                $offset Skip n entries
905
     *
906
     * @throws \RuntimeException
907
     * @throws \Symfony\Component\Process\Exception\LogicException
908
     * @throws InvalidArgumentException
909
     * @throws \Symfony\Component\Process\Exception\RuntimeException
910
     * @return \GitElephant\Objects\Log
911
     */
912 3
    public function getObjectLog(
913
        NodeObject $obj,
914
        $branch = null,
915
        int $limit = 1,
916
        int $offset = null
917
    ): \GitElephant\Objects\Log {
918 3
        $command = LogCommand::getInstance($this)->showObjectLog($obj, $branch, $limit, $offset);
919
920 3
        return Log::createFromOutputLines($this, $this->caller->execute($command)->getOutputLines());
921
    }
922
923
    /**
924
     * Checkout a branch
925
     * This function change the state of the repository on the filesystem
926
     *
927
     * @param string|TreeishInterface $ref    the reference to checkout
928
     * @param bool                    $create like -b on the command line
929
     *
930
     * @throws \RuntimeException
931
     * @throws \Symfony\Component\Process\Exception\LogicException
932
     * @throws InvalidArgumentException
933
     * @throws \Symfony\Component\Process\Exception\RuntimeException
934
     * @return Repository
935
     */
936 24
    public function checkout($ref, bool $create = false): self
937
    {
938 24
        if ($create && is_null($this->getBranch($ref))) {
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 936 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...
939
            $this->createBranch($ref);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 936 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...
940
        }
941 24
        $this->caller->execute(MainCommand::getInstance($this)->checkout($ref));
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 936 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Command\MainCommand::checkout() does only seem to accept string|object<GitElephant\Objects\Branch>, 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...
942
943 24
        return $this;
944
    }
945
946
    /**
947
     * Retrieve an instance of Tree
948
     * Tree Object is Countable, Iterable and has ArrayAccess for easy manipulation
949
     *
950
     * @param string|TreeishInterface $ref  the treeish to check
951
     * @param string|NodeObject       $path Object or null for root
952
     *
953
     * @throws \RuntimeException
954
     * @throws \Symfony\Component\Process\Exception\LogicException
955
     * @throws InvalidArgumentException
956
     * @throws \Symfony\Component\Process\Exception\RuntimeException
957
     * @return Objects\Tree
958
     */
959 15
    public function getTree($ref = 'HEAD', $path = null): \GitElephant\Objects\Tree
960
    {
961 15
        if (is_string($path) && '' !== $path) {
962
            $outputLines = $this
963 9
                ->getCaller()
964 9
                ->execute(LsTreeCommand::getInstance($this)->tree($ref, $path))
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 959 can also be of type object<GitElephant\Objects\TreeishInterface>; however, GitElephant\Command\LsTreeCommand::tree() 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...
965 9
                ->getOutputLines(true);
966
967 9
            $path = TreeObject::createFromOutputLine($this, $outputLines[0]);
968
        }
969
970 15
        return new Tree($this, $ref, $path);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 959 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...
Bug introduced by
It seems like $path defined by parameter $path on line 959 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...
971
    }
972
973
    /**
974
     * Get a Diff object for a commit with its parent, by default the diff is between the current head and its parent
975
     *
976
     * @param \GitElephant\Objects\Commit|string      $commit1 A TreeishInterface instance
977
     * @param \GitElephant\Objects\Commit|string|null $commit2 A TreeishInterface instance
978
     * @param null|string|NodeObject                  $path    The path to get the diff for or a Object instance
979
     *
980
     * @throws \RuntimeException
981
     * @throws \InvalidArgumentException
982
     * @return Objects\Diff\Diff
983
     */
984 2
    public function getDiff(
985
        string $commit1 = null,
986
        string $commit2 = null,
987
        string $path = null
988
    ): \GitElephant\Objects\Diff\Diff {
989 2
        return Diff::create($this, $commit1, $commit2, $path);
990
    }
991
992
    /**
993
     * Clone a repository
994
     *
995
     * @param string      $url           the repository url (i.e. git://github.com/matteosister/GitElephant.git)
996
     * @param null        $to            where to clone the repo
997
     * @param string|null $repoReference Repo reference to clone. Required if performing a shallow clone.
998
     * @param int|null    $depth         Depth to clone repo. Specify 1 to perform a shallow clone
999
     * @param bool        $recursive     Whether to recursively clone child repos.
1000
     *
1001
     * @throws \RuntimeException
1002
     * @throws \Symfony\Component\Process\Exception\LogicException
1003
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1004
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1005
     * @return Repository
1006
     */
1007 2
    public function cloneFrom(
1008
        string $url,
1009
        string $to = null,
1010
        string $repoReference = null,
1011
        int $depth = null,
1012
        bool $recursive = false
1013
    ): self {
1014 2
        $command = Command\CloneCommand::getInstance($this)
1015 2
            ->cloneUrl($url, $to, $repoReference, $depth, $recursive);
1016 2
        $this->caller->execute($command);
1017
1018 2
        return $this;
1019
    }
1020
1021
    /**
1022
     * @param string $name remote name
1023
     * @param string $url  remote url
1024
     *
1025
     * @throws \RuntimeException
1026
     * @throws \Symfony\Component\Process\Exception\LogicException
1027
     * @throws InvalidArgumentException
1028
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1029
     * @return Repository
1030
     */
1031 7
    public function addRemote(string $name, string $url): self
1032
    {
1033 7
        $this->caller->execute(RemoteCommand::getInstance($this)->add($name, $url));
1034
1035 7
        return $this;
1036
    }
1037
1038
    /**
1039
     * @param string $name         remote name
1040
     * @param bool   $queryRemotes Fetch new information from remotes
1041
     *
1042
     * @return \GitElephant\Objects\Remote
1043
     */
1044 1
    public function getRemote(string $name, bool $queryRemotes = true): \GitElephant\Objects\Remote
1045
    {
1046 1
        return Remote::pick($this, $name, $queryRemotes);
1047
    }
1048
1049
    /**
1050
     * gets a list of remote objects
1051
     *
1052
     * @param bool $queryRemotes Fetch new information from remotes
1053
     *
1054
     * @throws \RuntimeException
1055
     * @throws \Symfony\Component\Process\Exception\LogicException
1056
     * @throws InvalidArgumentException
1057
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1058
     * @return array
1059
     */
1060 1
    public function getRemotes(bool $queryRemotes = true): array
1061
    {
1062 1
        $remoteNames = $this->caller
1063 1
            ->execute(RemoteCommand::getInstance($this)->show(null, $queryRemotes))
1064 1
            ->getOutputLines(true);
1065
1066 1
        $remotes = [];
1067 1
        foreach ($remoteNames as $remoteName) {
1068 1
            $remotes[] = $this->getRemote($remoteName, $queryRemotes);
1069
        }
1070
1071 1
        return $remotes;
1072
    }
1073
1074
    /**
1075
     * Download objects and refs from another repository
1076
     *
1077
     * @param string $from
1078
     * @param string $ref
1079
     * @param bool   $tags
1080
     *
1081
     * @throws \RuntimeException
1082
     * @throws \Symfony\Component\Process\Exception\LogicException
1083
     * @throws InvalidArgumentException
1084
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1085
     */
1086 1
    public function fetch($from = null, $ref = null, bool $tags = false): void
1087
    {
1088 1
        $options = [];
1089 1
        if ($tags) {
1090 1
            $options = ['--tags'];
1091
        }
1092 1
        $this->caller->execute(FetchCommand::getInstance($this)->fetch($from, $ref, $options));
1093 1
    }
1094
1095
    /**
1096
     * Fetch from and merge with another repository or a local branch
1097
     *
1098
     * @param string $from
1099
     * @param string $ref
1100
     * @param bool   $rebase
1101
     *
1102
     * @throws \RuntimeException
1103
     * @throws \Symfony\Component\Process\Exception\LogicException
1104
     * @throws InvalidArgumentException
1105
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1106
     */
1107 2
    public function pull($from = null, $ref = null, bool $rebase = true): void
1108
    {
1109 2
        $this->caller->execute(PullCommand::getInstance($this)->pull($from, $ref, $rebase));
1110 2
    }
1111
1112
    /**
1113
     * Push changes to remote repository
1114
     *
1115
     * @param string $to
1116
     * @param string $ref
1117
     * @param string $args
1118
     *
1119
     * @throws \RuntimeException
1120
     * @throws \Symfony\Component\Process\Exception\LogicException
1121
     * @throws InvalidArgumentException
1122
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1123
     */
1124 1
    public function push($to = null, $ref = null, string $args = null): void
1125
    {
1126 1
        $this->caller->execute(PushCommand::getInstance($this)->push($to, $ref, $args));
1127 1
    }
1128
1129
    /**
1130
     * get the humanish name of the repository
1131
     *
1132
     * @return string
1133
     */
1134 2
    public function getHumanishName(): string
1135
    {
1136 2
        $name = substr($this->getPath(), strrpos($this->getPath(), '/') + 1);
1137 2
        $name = str_replace('.git', '.', $name);
1138
1139 2
        return str_replace('.bundle', '.', $name);
1140
    }
1141
1142
    /**
1143
     * output a node content as an array of lines
1144
     *
1145
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1146
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1147
     *
1148
     * @throws \RuntimeException
1149
     * @throws \Symfony\Component\Process\Exception\LogicException
1150
     * @throws InvalidArgumentException
1151
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1152
     * @return array
1153
     */
1154 1
    public function outputContent(NodeObject $obj, $treeish): array
1155
    {
1156 1
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1157
1158 1
        return $this->caller->execute($command)->getOutputLines();
1159
    }
1160
1161
    /**
1162
     * output a node raw content
1163
     *
1164
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1165
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1166
     *
1167
     * @throws \RuntimeException
1168
     * @throws \Symfony\Component\Process\Exception\LogicException
1169
     * @throws InvalidArgumentException
1170
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1171
     * @return string
1172
     */
1173
    public function outputRawContent(NodeObject $obj, $treeish): string
1174
    {
1175
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1176
1177
        return $this->caller->execute($command)->getRawOutput();
1178
    }
1179
1180
    /**
1181
     * Get the path
1182
     *
1183
     * @return string
1184
     */
1185 64
    public function getPath(): string
1186
    {
1187 64
        return $this->path;
1188
    }
1189
1190
    /**
1191
     * Get the repository name
1192
     *
1193
     * @return string
1194
     */
1195 1
    public function getName(): string
1196
    {
1197 1
        return $this->name;
1198
    }
1199
1200
    /**
1201
     * Set the repository name
1202
     *
1203
     * @param string $name the repository name
1204
     */
1205 1
    public function setName(string $name): void
1206
    {
1207 1
        $this->name = $name;
1208 1
    }
1209
1210
    /**
1211
     * Caller setter
1212
     *
1213
     * @param CallerInterface $caller the caller variable
1214
     */
1215
    public function setCaller(CallerInterface $caller): void
1216
    {
1217
        $this->caller = $caller;
0 ignored issues
show
Documentation Bug introduced by
$caller is of type object<GitElephant\Comma...Caller\CallerInterface>, but the property $caller was declared to be of type object<GitElephant\Command\Caller\Caller>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
1218
    }
1219
1220
    /**
1221
     * Caller getter
1222
     *
1223
     * @return CallerInterface the caller to use to call commands
1224
     */
1225 92
    public function getCaller(): CallerInterface
1226
    {
1227 92
        return $this->caller;
1228
    }
1229
1230
    /**
1231
     * get global config list
1232
     *
1233
     * @return array Global config list
1234
     */
1235 98
    public function getGlobalConfigs(): array
1236
    {
1237 98
        return $this->globalConfigs;
1238
    }
1239
1240
    /**
1241
     * add a key/value pair to the global config list
1242
     *
1243
     * @param string $name  The config name
1244
     * @param mixed  $value The config value
1245
     */
1246 1
    public function addGlobalConfig(string $name, $value): void
1247
    {
1248 1
        $this->globalConfigs[$name] = $value;
1249 1
    }
1250
1251
    /**
1252
     * remove an element form the global config list, identified by key
1253
     *
1254
     * @param  string $name The config name
1255
     */
1256 1
    public function removeGlobalConfig(string $name): void
1257
    {
1258 1
        if (isset($this->globalConfigs[$name])) {
1259 1
            unset($this->globalConfigs[$name]);
1260
        }
1261 1
    }
1262
1263
    /**
1264
     * get global options list
1265
     *
1266
     * @return array Global options list
1267
     */
1268 98
    public function getGlobalOptions(): array
1269
    {
1270 98
        return $this->globalOptions;
1271
    }
1272
1273
    /**
1274
     * add a key/value pair to the global option list
1275
     *
1276
     * @param string $name  The option name
1277
     * @param mixed  $value The option value
1278
     */
1279 1
    public function addGlobalOption(string $name, $value): void
1280
    {
1281 1
        $this->globalOptions[$name] = $value;
1282 1
    }
1283
1284
    /**
1285
     * remove an element form the global option list, identified by key
1286
     *
1287
     * @param  string $name The option name
1288
     */
1289 1
    public function removeGlobalOption(string $name): void
1290
    {
1291 1
        if (isset($this->globalOptions[$name])) {
1292 1
            unset($this->globalOptions[$name]);
1293
        }
1294 1
    }
1295
1296
    /**
1297
     * get global command arguments list
1298
     *
1299
     * @return array Global command arguments list
1300
     */
1301 98
    public function getGlobalCommandArguments(): array
1302
    {
1303 98
        return $this->globalCommandArguments;
1304
    }
1305
1306
    /**
1307
     * add a value to the global command argument list
1308
     *
1309
     * @param string $value The command argument
1310
     */
1311 1
    public function addGlobalCommandArgument($value): void
1312
    {
1313 1
        if (!in_array($value, $this->globalCommandArguments, true)) {
1314 1
            $this->globalCommandArguments[] = $value;
1315
        }
1316 1
    }
1317
1318
    /**
1319
     * remove an element form the global command argument list, identified by
1320
     * value
1321
     *
1322
     * @param  string $value The command argument
1323
     */
1324 1
    public function removeGlobalCommandArgument($value): void
1325
    {
1326 1
        if (in_array($value, $this->globalCommandArguments, true)) {
1327 1
            $index = array_search($value, $this->globalCommandArguments);
1328 1
            unset($this->globalCommandArguments[$index]);
1329
        }
1330 1
    }
1331
1332
    /**
1333
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1334
     *
1335
     * @param string|null $message
1336
     * @param boolean     $includeUntracked
1337
     * @param boolean     $keepIndex
1338
     */
1339 2
    public function stash(string $message = null, bool $includeUntracked = false, bool $keepIndex = false): void
1340
    {
1341 2
        $stashCommand = StashCommand::getInstance($this);
1342 2
        $command = $stashCommand->save($message, $includeUntracked, $keepIndex);
1343 2
        $this->caller->execute($command);
1344 1
    }
1345
1346
    /**
1347
     * Shows stash list
1348
     *
1349
     * @param array|null $options
1350
     *
1351
     * @return array
1352
     */
1353 1
    public function stashList(array $options = null): array
1354
    {
1355 1
        $stashCommand = StashCommand::getInstance($this);
1356 1
        $command = $stashCommand->listStashes($options);
1357 1
        $this->caller->execute($command);
1358
1359 1
        return array_map('trim', $this->caller->getOutputLines(true));
1360
    }
1361
1362
    /**
1363
     * Shows details for a stash
1364
     *
1365
     * @param string $stash
1366
     *
1367
     * @return string
1368
     */
1369 1
    public function stashShow(string $stash): string
1370
    {
1371 1
        $stashCommand = StashCommand::getInstance($this);
1372 1
        $command = $stashCommand->show($stash);
1373 1
        $this->caller->execute($command);
1374
1375 1
        return $this->caller->getOutput();
1376
    }
1377
1378
    /**
1379
     * Drops a stash
1380
     *
1381
     * @param string $stash
1382
     */
1383 1
    public function stashDrop(string $stash): void
1384
    {
1385 1
        $stashCommand = StashCommand::getInstance($this);
1386 1
        $command = $stashCommand->drop($stash);
1387 1
        $this->caller->execute($command);
1388 1
    }
1389
1390
    /**
1391
     * Applies a stash
1392
     *
1393
     * @param string  $stash
1394
     * @param boolean $index
1395
     */
1396 1
    public function stashApply(string $stash, bool $index = false): void
1397
    {
1398 1
        $stashCommand = StashCommand::getInstance($this);
1399 1
        $command = $stashCommand->apply($stash, $index);
1400 1
        $this->caller->execute($command);
1401 1
    }
1402
1403
    /**
1404
     *  Applies a stash, then removes it from the stash
1405
     *
1406
     * @param string  $stash
1407
     * @param boolean $index
1408
     */
1409 1
    public function stashPop(string $stash, bool $index = false): void
1410
    {
1411 1
        $stashCommand = StashCommand::getInstance($this);
1412 1
        $command = $stashCommand->pop($stash, $index);
1413 1
        $this->caller->execute($command);
1414 1
    }
1415
1416
    /**
1417
     *  Creates and checks out a new branch named <branchname> starting from the commit at which the <stash> was originally created
1418
     *
1419
     * @param string $branch
1420
     * @param string $stash
1421
     */
1422 1
    public function stashBranch(string $branch, string $stash): void
1423
    {
1424 1
        $stashCommand = StashCommand::getInstance($this);
1425 1
        $command = $stashCommand->branch($branch, $stash);
1426 1
        $this->caller->execute($command);
1427 1
    }
1428
1429
    /**
1430
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1431
     */
1432
    public function stashClear(): void
1433
    {
1434
        $stashCommand = StashCommand::getInstance($this);
1435
        $command = $stashCommand->clear();
1436
        $this->caller->execute($command);
1437
    }
1438
1439
    /**
1440
     *  Create a stash (which is a regular commit object) and return its object name, without storing it anywhere in the
1441
     *  ref namespace.
1442
     *
1443
     * @return string
1444
     */
1445 1
    public function stashCreate(): string
1446
    {
1447 1
        $stashCommand = StashCommand::getInstance($this);
1448 1
        $command = $stashCommand->clear();
1449 1
        $this->caller->execute($command);
1450
1451 1
        return $this->caller->getOutput();
1452
    }
1453
}
1454