Completed
Push — develop ( b29d0d...9c2be0 )
by
unknown
16s queued 11s
created

Repository::isClean()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * GitElephant - An abstraction layer for git written in PHP
4
 * Copyright (C) 2013  Matteo Giachino
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see [http://www.gnu.org/licenses/].
18
 */
19
20
namespace GitElephant;
21
22
use GitElephant\Command\ResetCommand;
23
use GitElephant\Command\StashCommand;
24
use GitElephant\Objects\TreeObject;
25
use Symfony\Component\Process\Exception\InvalidArgumentException;
26
use \GitElephant\Command\BranchCommand;
27
use \GitElephant\Command\Caller\Caller;
28
use \GitElephant\Command\CatFileCommand;
29
use \GitElephant\Command\CloneCommand;
30
use \GitElephant\Command\FetchCommand;
31
use \GitElephant\Command\LogCommand;
32
use \GitElephant\Command\LsTreeCommand;
33
use \GitElephant\Command\MainCommand;
34
use \GitElephant\Command\MergeCommand;
35
use \GitElephant\Command\PullCommand;
36
use \GitElephant\Command\PushCommand;
37
use \GitElephant\Command\RemoteCommand;
38
use \GitElephant\Command\RevParseCommand;
39
use \GitElephant\Command\SubmoduleCommand;
40
use \GitElephant\Command\TagCommand;
41
use \GitElephant\Objects\Author;
42
use \GitElephant\Objects\Branch;
43
use \GitElephant\Objects\Commit;
44
use \GitElephant\Objects\Diff\Diff;
45
use \GitElephant\Objects\Log;
46
use \GitElephant\Objects\LogRange;
47
use \GitElephant\Objects\NodeObject;
48
use \GitElephant\Objects\Remote;
49
use \GitElephant\Objects\Tag;
50
use \GitElephant\Objects\Tree;
51
use \GitElephant\Objects\TreeishInterface;
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
59
/**
60
 * Repository
61
 *
62
 * Base Class for repository operations
63
 *
64
 * @author Matteo Giachino <[email protected]>
65
 * @author Dhaval Patel <[email protected]>
66
 * @author Kirk Madera <[email protected]>
67
 */
68
class Repository
69
{
70
    /**
71
     * the repository path
72
     *
73
     * @var string
74
     */
75
    private $path;
76
77
    /**
78
     * the caller instance
79
     *
80
     * @var \GitElephant\Command\Caller\Caller
81
     */
82
    private $caller;
83
84
    /**
85
     * A general repository name
86
     *
87
     * @var string $name the repository name
88
     */
89
    private $name;
90
91
    /**
92
     * A list of global configs to apply to every command
93
     *
94
     * @var array
95
     */
96
    private $globalConfigs = [];
97
98
    /**
99
     * A list of global options to apply to every command
100
     *
101
     * @var array
102
     */
103
    private $globalOptions = [];
104
105
    /**
106
     * A list of global arguments to apply to every command
107
     *
108
     * @var array
109
     */
110
    private $globalCommandArguments = [];
111
112
    /**
113
     * Class constructor
114
     *
115
     * @param string         $repositoryPath the path of the git repository
116
     * @param string|null $binary         the path to the git binary
117
     * @param string         $name           a repository name
118
     *
119
     * @throws Exception\InvalidRepositoryPathException
120
     */
121 107
    public function __construct($repositoryPath, string $binary = null, $name = null)
122
    {
123 107
        $this->path = $repositoryPath;
124 107
        $this->caller = new Caller($binary, $repositoryPath);
125 107
        $this->name = $name;
126 107
    }
127
128
    /**
129
     * Factory method
130
     *
131
     * @param string         $repositoryPath the path of the git repository
132
     * @param string|null $binary         the path to the git binary
133
     * @param string         $name           a repository name
134
     *
135
     * @return \GitElephant\Repository
136
     */
137 106
    public static function open($repositoryPath, string $binary = null, $name = null): \GitElephant\Repository
138
    {
139 106
        return new self($repositoryPath, $binary, $name);
140
    }
141
142
    /**
143
     * create a repository from a remote git url, or a local filesystem
144
     * and save it in a temp folder
145
     *
146
     * @param string|Repository $git            the git remote url, or the filesystem path
147
     * @param null              $repositoryPath path
148
     * @param string|null $binary         the path to the git binary
149
     * @param null              $name           repository name
150
     *
151
     * @throws \RuntimeException
152
     * @throws \Symfony\Component\Filesystem\Exception\IOException
153
     * @return Repository
154
     */
155 1
    public static function createFromRemote($git, $repositoryPath = null, string $binary = null, $name = null): \GitElephant\Repository
156
    {
157 1
        if (null === $repositoryPath) {
158 1
            $tempDir = realpath(sys_get_temp_dir());
159 1
            $repositoryPath = sprintf('%s%s%s', $tempDir, DIRECTORY_SEPARATOR, sha1(uniqid()));
160 1
            $fs = new Filesystem();
161 1
            $fs->mkdir($repositoryPath);
162
        }
163 1
        $repository = new Repository($repositoryPath, $binary, $name);
164 1
        if ($git instanceof Repository) {
165
            $git = $git->getPath();
166
        }
167 1
        $repository->cloneFrom($git, $repositoryPath);
168 1
        $repository->checkoutAllRemoteBranches();
169
170 1
        return $repository;
171
    }
172
173
    /**
174
     * Init the repository
175
     *
176
     * @param bool $bare created a bare repository
177
     *
178
     * @throws \RuntimeException
179
     * @throws \Symfony\Component\Process\Exception\LogicException
180
     * @throws InvalidArgumentException
181
     * @throws \Symfony\Component\Process\Exception\RuntimeException
182
     * @return Repository
183
     */
184 94
    public function init($bare = false): self
185
    {
186 94
        $this->caller->execute(MainCommand::getInstance($this)->init($bare));
187
188 94
        return $this;
189
    }
190
191
    /**
192
     * Stage the working tree content
193
     *
194
     * @param string|NodeObject $path the path to store
195
     *
196
     * @throws \RuntimeException
197
     * @throws \Symfony\Component\Process\Exception\LogicException
198
     * @throws InvalidArgumentException
199
     * @throws \Symfony\Component\Process\Exception\RuntimeException
200
     * @return Repository
201
     */
202 90
    public function stage($path = '.'): self
203
    {
204 90
        $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 202 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...
205
206 90
        return $this;
207
    }
208
209
    /**
210
     * Unstage a tree content
211
     *
212
     * @param string|NodeObject $path the path to unstage
213
     *
214
     * @throws \RuntimeException
215
     * @throws \Symfony\Component\Process\Exception\LogicException
216
     * @throws InvalidArgumentException
217
     * @throws \Symfony\Component\Process\Exception\RuntimeException
218
     * @return Repository
219
     */
220 2
    public function unstage($path): self
221
    {
222 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 220 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...
223
224 2
        return $this;
225
    }
226
227
    /**
228
     * Move a file/directory
229
     *
230
     * @param string|NodeObject $from source path
231
     * @param string|NodeObject $to   destination path
232
     *
233
     * @throws \RuntimeException
234
     * @throws \Symfony\Component\Process\Exception\LogicException
235
     * @throws \InvalidArgumentException
236
     * @throws InvalidArgumentException
237
     * @throws \Symfony\Component\Process\Exception\RuntimeException
238
     * @return Repository
239
     */
240 1
    public function move($from, $to): self
241
    {
242 1
        $this->caller->execute(MainCommand::getInstance($this)->move($from, $to));
243
244 1
        return $this;
245
    }
246
247
    /**
248
     * Remove a file/directory
249
     *
250
     * @param string|NodeObject $path      the path to remove
251
     * @param bool              $recursive recurse
252
     * @param bool              $force     force
253
     *
254
     * @throws \RuntimeException
255
     * @throws \Symfony\Component\Process\Exception\LogicException
256
     * @throws \InvalidArgumentException
257
     * @throws InvalidArgumentException
258
     * @throws \Symfony\Component\Process\Exception\RuntimeException
259
     * @return Repository
260
     */
261 1
    public function remove($path, $recursive = false, $force = false): self
262
    {
263 1
        $this->caller->execute(MainCommand::getInstance($this)->remove($path, $recursive, $force));
264
265 1
        return $this;
266
    }
267
268
    /**
269
     * Commit content to the repository, eventually staging all unstaged content
270
     *
271
     * @param string        $message    the commit message
272
     * @param bool          $stageAll   whether to stage on not everything before commit
273
     * @param string|null   $ref        the reference to commit to (checkout -> commit -> checkout previous)
274
     * @param string|Author $author     override the author for this commit
275
     * @param bool          $allowEmpty override the author for this commit
276
     *
277
     * @throws \RuntimeException
278
     * @throws \InvalidArgumentException
279
     * @throws \Symfony\Component\Process\Exception\RuntimeException
280
     * @return Repository
281
     */
282 85
    public function commit(string $message, $stageAll = false, $ref = null, $author = null, $allowEmpty = false): self
283
    {
284 85
        $currentBranch = null;
285 85
        if (!is_null($ref)) {
286 1
            $currentBranch = $this->getMainBranch();
287 1
            $this->checkout($ref);
288
        }
289 85
        if ($stageAll) {
290 83
            $this->stage();
291
        }
292 85
        $this->caller->execute(MainCommand::getInstance($this)->commit($message, $stageAll, $author, $allowEmpty));
293 85
        if (!is_null($ref)) {
294 1
            $this->checkout($currentBranch);
0 ignored issues
show
Bug introduced by
It seems like $currentBranch defined by null on line 284 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...
295
        }
296
297 85
        return $this;
298
    }
299
300
    /**
301
     * rev-parse command - often used to return a commit tag.
302
     *
303
     * @param array                    $options the options to apply to rev-parse
304
     * @param string|NodeObject|Commit $arg     the argument (may be a branch head, etc)
305
     *
306
     * @throws \RuntimeException
307
     * @throws \InvalidArgumentException
308
     * @throws \Symfony\Component\Process\Exception\RuntimeException
309
     * @return array
310
     */
311 1
    public function revParse(string $arg = null, array $options = []): array
312
    {
313 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse($arg, $options));
314
315 1
        return array_map('trim', $this->caller->getOutputLines(true));
316
    }
317
318
    /**
319
     * Check if this is a bare repository
320
     *
321
     * @return boolean
322
     */
323 1
    public function isBare(): bool
324
    {
325 1
        $options = [RevParseCommand::OPTION_IS_BARE_REPOSIORY];
326 1
        $this->caller->execute(RevParseCommand::getInstance()->revParse(null, $options));
327
328 1
        return trim($this->caller->getOutput()) === 'true';
329
    }
330
331
    /**
332
     * @param TreeishInterface|Commit|string $arg
333
     * @param array                          $options
334
     */
335 2
    public function reset($arg, $options): void
336
    {
337 2
        $this->caller->execute(ResetCommand::getInstance($this)->reset($arg, $options));
338 2
    }
339
340
    /**
341
     * Get the repository status
342
     *
343
     * @return Status
344
     */
345 5
    public function getStatus(): \GitElephant\Status\Status
346
    {
347 5
        return Status::get($this);
348
    }
349
350
    /**
351
     * @return Status
352
     */
353 1
    public function getWorkingTreeStatus(): \GitElephant\Status\Status
354
    {
355 1
        return StatusWorkingTree::get($this);
356
    }
357
358
    /**
359
     * @return Status
360
     */
361 4
    public function getIndexStatus(): \GitElephant\Status\Status
362
    {
363 4
        return StatusIndex::get($this);
364
    }
365
366
    /**
367
     * isClean Return true if the repository is not dirty.
368
     *
369
     * @return boolean
370
     */
371
    public function isClean(): bool
372
    {
373
        return $this->getStatus()->all()->isEmpty();
374
    }
375
376
    /**
377
     * isDirty Return true if the repository has some modified files.
378
     *
379
     * @return boolean
380
     */
381
    public function isDirty(): bool
382
    {
383
        return !$this->isClean();
384
    }
385
386
    /**
387
     * Get the repository status as a string
388
     *
389
     * @throws \RuntimeException
390
     * @throws \Symfony\Component\Process\Exception\LogicException
391
     * @throws InvalidArgumentException
392
     * @throws \Symfony\Component\Process\Exception\RuntimeException
393
     * @return array
394
     */
395 4
    public function getStatusOutput(): array
396
    {
397 4
        $this->caller->execute(MainCommand::getInstance($this)->status());
398
399 4
        return array_map('trim', $this->caller->getOutputLines());
400
    }
401
402
    /**
403
     * Create a new branch
404
     *
405
     * @param string $name       the new branch name
406
     * @param null   $startPoint the reference to create the branch from
407
     *
408
     * @throws \RuntimeException
409
     * @throws \Symfony\Component\Process\Exception\RuntimeException
410
     * @return Repository
411
     */
412 27
    public function createBranch(string $name, $startPoint = null): self
413
    {
414 27
        Branch::create($this, $name, $startPoint);
415
416 27
        return $this;
417
    }
418
419
    /**
420
     * Delete a branch by its name
421
     * This function change the state of the repository on the filesystem
422
     *
423
     * @param string $name  The branch to delete
424
     * @param bool   $force Force the delete
425
     *
426
     * @throws \RuntimeException
427
     * @throws \Symfony\Component\Process\Exception\LogicException
428
     * @throws InvalidArgumentException
429
     * @throws \Symfony\Component\Process\Exception\RuntimeException
430
     * @return Repository
431
     */
432 1
    public function deleteBranch(string $name, bool $force = false): self
433
    {
434 1
        $this->caller->execute(BranchCommand::getInstance($this)->delete($name, $force));
435
436 1
        return $this;
437
    }
438
439
    /**
440
     * An array of Branch objects
441
     *
442
     * @param bool $namesOnly return an array of branch names as a string
443
     * @param bool $all       lists also remote branches
444
     *
445
     * @throws \RuntimeException
446
     * @throws InvalidArgumentException
447
     * @throws \Symfony\Component\Process\Exception\LogicException
448
     * @throws \InvalidArgumentException
449
     * @throws \Symfony\Component\Process\Exception\RuntimeException
450
     * @return array
451
     */
452 17
    public function getBranches(bool $namesOnly = false, bool $all = false): array
453
    {
454 17
        $branches = [];
455 17
        if ($namesOnly) {
456 6
            $outputLines = $this->caller
457 6
                ->execute(BranchCommand::getInstance($this)->listBranches($all, true))
458 6
                ->getOutputLines(true);
459
460 6
            $branches = array_map(
461
                function ($v) {
462 6
                    return ltrim($v, '* ');
463 6
                },
464
                $outputLines
465
            );
466
        } else {
467 14
            $outputLines = $this->caller
468 14
                ->execute(BranchCommand::getInstance($this)->listBranches($all))
469 14
                ->getOutputLines(true);
470
471 14
            foreach ($outputLines as $branchLine) {
472 14
                $branches[] = Branch::createFromOutputLine($this, $branchLine);
473
            }
474
        }
475
476 17
        return $branches;
477
    }
478
479
    /**
480
     * Return the actually checked out branch
481
     *
482
     * @throws \RuntimeException
483
     * @throws \InvalidArgumentException
484
     * @throws \Symfony\Component\Process\Exception\RuntimeException
485
     * @return Objects\Branch
486
     */
487 5
    public function getMainBranch(): \GitElephant\Objects\Branch
488
    {
489 5
        $filtered = array_filter(
490 5
            $this->getBranches(),
491
            function (Branch $branch) {
492 5
                return $branch->getCurrent();
493 5
            }
494
        );
495 5
        sort($filtered);
496
497 5
        return $filtered[0];
498
    }
499
500
    /**
501
     * Retrieve a Branch object by a branch name
502
     *
503
     * @param string $name The branch name
504
     *
505
     * @throws \RuntimeException
506
     * @throws \InvalidArgumentException
507
     * @throws \Symfony\Component\Process\Exception\RuntimeException
508
     * @return null|Branch
509
     */
510 9
    public function getBranch(string $name): ?\GitElephant\Objects\Branch
511
    {
512
        /** @var Branch $branch */
513 9
        foreach ($this->getBranches() as $branch) {
514 9
            if ($branch->getName() === $name) {
515 9
                return $branch;
516
            }
517
        }
518
519 1
        return null;
520
    }
521
522
    /**
523
     * Checkout all branches from the remote and make them local
524
     *
525
     * @param string $remote remote to fetch from
526
     *
527
     * @throws \RuntimeException
528
     * @throws \InvalidArgumentException
529
     * @throws \Symfony\Component\Process\Exception\RuntimeException
530
     * @return Repository
531
     */
532 1
    public function checkoutAllRemoteBranches($remote = 'origin'): self
533
    {
534 1
        $actualBranch = $this->getMainBranch();
535 1
        $actualBranches = $this->getBranches(true, false);
536 1
        $allBranches = $this->getBranches(true, true);
537 1
        $realBranches = array_filter(
538 1
            $allBranches,
539
            function (string $branch) use ($actualBranches) {
540 1
                return !in_array($branch, $actualBranches)
541 1
                && preg_match('/^remotes(.+)$/', $branch)
542 1
                && !preg_match('/^(.+)(HEAD)(.*?)$/', $branch);
543 1
            }
544
        );
545
546 1
        foreach ($realBranches as $realBranch) {
547 1
            $this->checkout(str_replace(sprintf('remotes/%s/', $remote), '', $realBranch));
548
        }
549
550 1
        $this->checkout($actualBranch);
551
552 1
        return $this;
553
    }
554
555
    /**
556
     * Merge a Branch in the current checked out branch
557
     *
558
     * @param Objects\Branch $branch  The branch to merge in the current checked out branch
559
     * @param string         $message The message for the merge commit, if merge is 3-way
560
     * @param string         $mode    The merge mode: ff-only, no-ff or auto
561
     *
562
     * @throws \RuntimeException
563
     * @throws \Symfony\Component\Process\Exception\LogicException
564
     * @throws InvalidArgumentException
565
     * @throws \Symfony\Component\Process\Exception\RuntimeException
566
     * @return Repository
567
     */
568 2
    public function merge(Branch $branch, string $message = '', string $mode = 'auto'): self
569
    {
570
        $valid_modes = [
571 2
            'auto', // deafult git behavior
572
            'ff-only', // force fast forward merge
573
            'no-ff', // force 3-way merge
574
        ];
575 2
        if (!in_array($mode, $valid_modes)) {
576
            throw new InvalidArgumentException("Invalid merge mode: $mode.");
577
        }
578
579 2
        $options = [];
580 2
        switch ($mode) {
581 2
            case 'ff-only':
582 1
                $options[] = MergeCommand::MERGE_OPTION_FF_ONLY;
583 1
                break;
584 2
            case 'no-ff':
585 1
                $options[] = MergeCommand::MERGE_OPTION_NO_FF;
586 1
                break;
587
        }
588
589 2
        $this->caller->execute(MergeCommand::getInstance($this)->merge($branch, $message, $options));
590
591 2
        return $this;
592
    }
593
594
    /**
595
     * Create a new tag
596
     * This function change the state of the repository on the filesystem
597
     *
598
     * @param string $name       The new tag name
599
     * @param null   $startPoint The reference to create the tag from
600
     * @param null   $message    the tag message
601
     *
602
     * @throws \RuntimeException
603
     * @throws \Symfony\Component\Process\Exception\RuntimeException
604
     * @return Repository
605
     */
606 25
    public function createTag(string $name, $startPoint = null, string $message = null): self
607
    {
608 25
        Tag::create($this, $name, $startPoint, $message);
609
610 25
        return $this;
611
    }
612
613
    /**
614
     * Delete a tag by it's name or by passing a Tag object
615
     * This function change the state of the repository on the filesystem
616
     *
617
     * @param string|Tag $tag The tag name or the Tag object
618
     *
619
     * @throws \RuntimeException
620
     * @throws \Symfony\Component\Process\Exception\RuntimeException
621
     * @return Repository
622
     */
623 2
    public function deleteTag($tag): self
624
    {
625 2
        if ($tag instanceof Tag) {
626 1
            $tag->delete();
627
        } else {
628 1
            Tag::pick($this, $tag)->delete();
629
        }
630
631 2
        return $this;
632
    }
633
634
    /**
635
     * add a git submodule to the repository
636
     *
637
     * @param string $gitUrl git url of the submodule
638
     * @param string $path   path to register the submodule to
639
     *
640
     * @throws \RuntimeException
641
     * @throws \Symfony\Component\Process\Exception\LogicException
642
     * @throws InvalidArgumentException
643
     * @throws \Symfony\Component\Process\Exception\RuntimeException
644
     * @return Repository
645
     */
646 1
    public function addSubmodule(string $gitUrl, $path = null): self
647
    {
648 1
        $this->caller->execute(SubmoduleCommand::getInstance($this)->add($gitUrl, $path));
649
650 1
        return $this;
651
    }
652
653
    /**
654
     * initialize submodules
655
     *
656
     * @param  string $path init only submodules at the specified path
657
     *
658
     * @return Repository
659
     */
660
    public function initSubmodule($path = null): self
661
    {
662
        $this->caller->execute(SubmoduleCommand::getInstance($this)->init($path));
663
664
        return $this;
665
    }
666
667
    /**
668
     * update submodules
669
     *
670
     * @param  bool   $recursive update recursively
671
     * @param  bool   $init      init before update
672
     * @param  bool   $force     force the checkout as part of update
673
     * @param  string $path      update only a specific submodule path
674
     *
675
     * @return Repository
676
     */
677
    public function updateSubmodule(
678
        bool $recursive = false,
679
        bool $init = false,
680
        bool $force = false,
681
        $path = null
682
    ): self {
683
        $this->caller->execute(SubmoduleCommand::getInstance($this)->update($recursive, $init, $force, $path));
684
685
        return $this;
686
    }
687
688
    /**
689
     * Gets an array of Tag objects
690
     *
691
     * @throws \RuntimeException
692
     * @throws \Symfony\Component\Process\Exception\LogicException
693
     * @throws InvalidArgumentException
694
     * @throws \Symfony\Component\Process\Exception\RuntimeException
695
     * @return array
696
     */
697 4
    public function getTags(): array
698
    {
699 4
        $tags = [];
700 4
        $this->caller->execute(TagCommand::getInstance($this)->listTags());
701 4
        foreach ($this->caller->getOutputLines() as $tagString) {
702 4
            if ($tagString != '') {
703 3
                $tags[] = new Tag($this, trim($tagString));
704
            }
705
        }
706
707 4
        return $tags;
708
    }
709
710
    /**
711
     * Return a tag object
712
     *
713
     * @param string $name The tag name
714
     *
715
     * @throws \RuntimeException
716
     * @throws \Symfony\Component\Process\Exception\RuntimeException
717
     * @return Tag|null
718
     */
719 28
    public function getTag(string $name): ?\GitElephant\Objects\Tag
720
    {
721 28
        $tagFinderOutput = $this->caller
722 28
            ->execute(TagCommand::getInstance()->listTags())
723 28
            ->getOutputLines(true);
724
725 28
        foreach ($tagFinderOutput as $line) {
726 28
            if ($line === $name) {
727 28
                return new Tag($this, $name);
728
            }
729
        }
730
731 1
        return null;
732
    }
733
734
    /**
735
     * Return the last created tag
736
     *
737
     * @throws \LogicException
738
     * @throws \RuntimeException
739
     * @throws \InvalidArgumentException
740
     * @return Tag|null
741
     */
742 1
    public function getLastTag(): ?\GitElephant\Objects\Tag
743
    {
744 1
        $finder = Finder::create()
745 1
            ->files()
746 1
            ->in(sprintf('%s/.git/refs/tags', $this->path))
747 1
            ->sortByChangedTime();
748
749 1
        if ($finder->count() === 0) {
750
            return null;
751
        }
752
753 1
        $files = iterator_to_array($finder->getIterator(), false);
754 1
        $files = array_reverse($files);
755
        /** @var $firstFile SplFileInfo */
756 1
        $firstFile = $files[0];
757 1
        $tagName = $firstFile->getFilename();
758
759 1
        return Tag::pick($this, $tagName);
760
    }
761
762
    /**
763
     * Try to get a branch or a tag by its name.
764
     *
765
     * @param string $name the reference name (a tag name or a branch name)
766
     *
767
     * @throws \RuntimeException
768
     * @throws \InvalidArgumentException
769
     * @throws \Symfony\Component\Process\Exception\RuntimeException
770
     * @return \GitElephant\Objects\Tag|\GitElephant\Objects\Branch|null
771
     */
772 1
    public function getBranchOrTag(string $name)
773
    {
774 1
        if (in_array($name, $this->getBranches(true))) {
775 1
            return new Branch($this, $name);
776
        }
777 1
        $tagFinderOutput = $this->caller->execute(TagCommand::getInstance($this)->listTags())->getOutputLines(true);
778 1
        foreach ($tagFinderOutput as $line) {
779 1
            if ($line === $name) {
780 1
                return new Tag($this, $name);
781
            }
782
        }
783
784 1
        return null;
785
    }
786
787
    /**
788
     * Return a Commit object
789
     *
790
     * @param string $ref The commit reference
791
     *
792
     * @throws \RuntimeException
793
     * @return Objects\Commit
794
     */
795 15
    public function getCommit($ref = 'HEAD'): \GitElephant\Objects\Commit
796
    {
797 15
        return Commit::pick($this, $ref);
798
    }
799
800
    /**
801
     * count the commit to arrive to the given treeish
802
     *
803
     * @param string $start
804
     *
805
     * @throws \RuntimeException
806
     * @throws \Symfony\Component\Process\Exception\RuntimeException
807
     * @return int
808
     */
809 3
    public function countCommits($start = 'HEAD'): int
810
    {
811 3
        $commit = Commit::pick($this, $start);
812
813 3
        return $commit->count();
814
    }
815
816
    /**
817
     * Get a log for a ref
818
     *
819
     * @param string|TreeishInterface|array $ref         the treeish to check, as a string, as an object or as an array
820
     * @param string|NodeObject             $path        the physical path to the tree relative to the repository root
821
     * @param int|null                      $limit       limit to n entries
822
     * @param int|null                      $offset      skip n entries
823
     * @param boolean|false                 $firstParent skip commits brought in to branch by a merge
824
     *
825
     * @return \GitElephant\Objects\Log
826
     */
827 20
    public function getLog(
828
        $ref = 'HEAD',
829
        $path = null,
830
        int $limit = 10,
831
        int $offset = null,
832
        bool $firstParent = false
833
    ): \GitElephant\Objects\Log {
834 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 829 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...
835
    }
836
837
    /**
838
     * Get a log for a range ref
839
     *
840
     * @param string            $refStart
841
     * @param string            $refEnd
842
     * @param string|NodeObject $path        the physical path to the tree relative to the repository root
843
     * @param int|null          $limit       limit to n entries
844
     * @param int|null          $offset      skip n entries
845
     * @param boolean|false     $firstParent skip commits brought in to branch by a merge
846
     *
847
     * @return \GitElephant\Objects\LogRange|\GitElephant\Objects\Log
848
     */
849
    public function getLogRange(
850
        $refStart,
851
        $refEnd,
852
        $path = null,
853
        int $limit = 10,
854
        int $offset = null,
855
        bool $firstParent = false
856
    ) {
857
        // Handle when clients provide bad start reference on branch creation
858
        if (preg_match('~^[0]+$~', $refStart)) {
859
            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 852 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
        // Handle when clients provide bad end reference on branch deletion
863
        if (preg_match('~^[0]+$~', $refEnd)) {
864
            $refEnd = $refStart;
865
        }
866
867
        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 852 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...
868
    }
869
870
    /**
871
     * Get a log for an object
872
     *
873
     * @param \GitElephant\Objects\NodeObject         $obj    The Object instance
874
     * @param null|string|\GitElephant\Objects\Branch $branch The branch to read from
875
     * @param int                                     $limit  Limit to n entries
876
     * @param int|null                                $offset Skip n entries
877
     *
878
     * @throws \RuntimeException
879
     * @throws \Symfony\Component\Process\Exception\LogicException
880
     * @throws InvalidArgumentException
881
     * @throws \Symfony\Component\Process\Exception\RuntimeException
882
     * @return \GitElephant\Objects\Log
883
     */
884 3
    public function getObjectLog(NodeObject $obj, $branch = null, int $limit = 1, int $offset = null): \GitElephant\Objects\Log
885
    {
886 3
        $command = LogCommand::getInstance($this)->showObjectLog($obj, $branch, $limit, $offset);
887
888 3
        return Log::createFromOutputLines($this, $this->caller->execute($command)->getOutputLines());
889
    }
890
891
    /**
892
     * Checkout a branch
893
     * This function change the state of the repository on the filesystem
894
     *
895
     * @param string|TreeishInterface $ref    the reference to checkout
896
     * @param bool                    $create like -b on the command line
897
     *
898
     * @throws \RuntimeException
899
     * @throws \Symfony\Component\Process\Exception\LogicException
900
     * @throws InvalidArgumentException
901
     * @throws \Symfony\Component\Process\Exception\RuntimeException
902
     * @return Repository
903
     */
904 24
    public function checkout($ref, bool $create = false): self
905
    {
906 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 904 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...
907
            $this->createBranch($ref);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 904 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...
908
        }
909 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 904 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...
910
911 24
        return $this;
912
    }
913
914
    /**
915
     * Retrieve an instance of Tree
916
     * Tree Object is Countable, Iterable and has ArrayAccess for easy manipulation
917
     *
918
     * @param string|TreeishInterface $ref  the treeish to check
919
     * @param string|NodeObject       $path Object or null for root
920
     *
921
     * @throws \RuntimeException
922
     * @throws \Symfony\Component\Process\Exception\LogicException
923
     * @throws InvalidArgumentException
924
     * @throws \Symfony\Component\Process\Exception\RuntimeException
925
     * @return Objects\Tree
926
     */
927 15
    public function getTree($ref = 'HEAD', $path = null): \GitElephant\Objects\Tree
928
    {
929 15
        if (is_string($path) && '' !== $path) {
930
            $outputLines = $this
931 9
                ->getCaller()
932 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 927 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...
933 9
                ->getOutputLines(true);
934
935 9
            $path = TreeObject::createFromOutputLine($this, $outputLines[0]);
936
        }
937
938 15
        return new Tree($this, $ref, $path);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 927 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 927 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...
939
    }
940
941
    /**
942
     * Get a Diff object for a commit with its parent, by default the diff is between the current head and its parent
943
     *
944
     * @param \GitElephant\Objects\Commit|string      $commit1 A TreeishInterface instance
945
     * @param \GitElephant\Objects\Commit|string|null $commit2 A TreeishInterface instance
946
     * @param null|string|NodeObject                  $path    The path to get the diff for or a Object instance
947
     *
948
     * @throws \RuntimeException
949
     * @throws \InvalidArgumentException
950
     * @return Objects\Diff\Diff
951
     */
952 2
    public function getDiff(string $commit1 = null, string $commit2 = null, string $path = null): \GitElephant\Objects\Diff\Diff
953
    {
954 2
        return Diff::create($this, $commit1, $commit2, $path);
955
    }
956
957
    /**
958
     * Clone a repository
959
     *
960
     * @param string      $url           the repository url (i.e. git://github.com/matteosister/GitElephant.git)
961
     * @param null        $to            where to clone the repo
962
     * @param string|null $repoReference Repo reference to clone. Required if performing a shallow clone.
963
     * @param int|null    $depth         Depth to clone repo. Specify 1 to perform a shallow clone
964
     * @param bool        $recursive     Whether to recursively clone child repos.
965
     *
966
     * @throws \RuntimeException
967
     * @throws \Symfony\Component\Process\Exception\LogicException
968
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
969
     * @throws \Symfony\Component\Process\Exception\RuntimeException
970
     * @return Repository
971
     */
972 2
    public function cloneFrom(string $url, string $to = null, string $repoReference = null, int $depth = null, bool $recursive = false): self
973
    {
974 2
        $command = (Command\CloneCommand::getInstance($this))->cloneUrl($url, $to, $repoReference, $depth, $recursive);
975 2
        $this->caller->execute($command);
976 2
        return $this;
977
    }
978
979
    /**
980
     * @param string $name remote name
981
     * @param string $url  remote url
982
     *
983
     * @throws \RuntimeException
984
     * @throws \Symfony\Component\Process\Exception\LogicException
985
     * @throws InvalidArgumentException
986
     * @throws \Symfony\Component\Process\Exception\RuntimeException
987
     * @return Repository
988
     */
989 7
    public function addRemote(string $name, string $url): self
990
    {
991 7
        $this->caller->execute(RemoteCommand::getInstance($this)->add($name, $url));
992
993 7
        return $this;
994
    }
995
996
    /**
997
     * @param string $name         remote name
998
     * @param bool   $queryRemotes Fetch new information from remotes
999
     *
1000
     * @return \GitElephant\Objects\Remote
1001
     */
1002 1
    public function getRemote(string $name, bool $queryRemotes = true): \GitElephant\Objects\Remote
1003
    {
1004 1
        return Remote::pick($this, $name, $queryRemotes);
1005
    }
1006
1007
    /**
1008
     * gets a list of remote objects
1009
     *
1010
     * @param bool $queryRemotes Fetch new information from remotes
1011
     *
1012
     * @throws \RuntimeException
1013
     * @throws \Symfony\Component\Process\Exception\LogicException
1014
     * @throws InvalidArgumentException
1015
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1016
     * @return array
1017
     */
1018 1
    public function getRemotes(bool $queryRemotes = true): array
1019
    {
1020 1
        $remoteNames = $this->caller
1021 1
            ->execute(RemoteCommand::getInstance($this)->show(null, $queryRemotes))
1022 1
            ->getOutputLines(true);
1023
1024 1
        $remotes = [];
1025 1
        foreach ($remoteNames as $remoteName) {
1026 1
            $remotes[] = $this->getRemote($remoteName, $queryRemotes);
1027
        }
1028
1029 1
        return $remotes;
1030
    }
1031
1032
    /**
1033
     * Download objects and refs from another repository
1034
     *
1035
     * @param string $from
1036
     * @param string $ref
1037
     * @param bool   $tags
1038
     *
1039
     * @throws \RuntimeException
1040
     * @throws \Symfony\Component\Process\Exception\LogicException
1041
     * @throws InvalidArgumentException
1042
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1043
     */
1044 1
    public function fetch($from = null, $ref = null, bool $tags = false): void
1045
    {
1046 1
        $options = [];
1047 1
        if ($tags) {
1048 1
            $options = ['--tags'];
1049
        }
1050 1
        $this->caller->execute(FetchCommand::getInstance($this)->fetch($from, $ref, $options));
1051 1
    }
1052
1053
    /**
1054
     * Fetch from and merge with another repository or a local branch
1055
     *
1056
     * @param string $from
1057
     * @param string $ref
1058
     * @param bool   $rebase
1059
     *
1060
     * @throws \RuntimeException
1061
     * @throws \Symfony\Component\Process\Exception\LogicException
1062
     * @throws InvalidArgumentException
1063
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1064
     */
1065 2
    public function pull($from = null, $ref = null, bool $rebase = true): void
1066
    {
1067 2
        $this->caller->execute(PullCommand::getInstance($this)->pull($from, $ref, $rebase));
1068 2
    }
1069
1070
    /**
1071
     * Push changes to remote repository
1072
     *
1073
     * @param string $to
1074
     * @param string $ref
1075
     * @param string $args
1076
     *
1077
     * @throws \RuntimeException
1078
     * @throws \Symfony\Component\Process\Exception\LogicException
1079
     * @throws InvalidArgumentException
1080
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1081
     */
1082 1
    public function push($to = null, $ref = null, string $args = null): void
1083
    {
1084 1
        $this->caller->execute(PushCommand::getInstance($this)->push($to, $ref, $args));
1085 1
    }
1086
1087
    /**
1088
     * get the humanish name of the repository
1089
     *
1090
     * @return string
1091
     */
1092 2
    public function getHumanishName(): string
1093
    {
1094 2
        $name = substr($this->getPath(), strrpos($this->getPath(), '/') + 1);
1095 2
        $name = str_replace('.git', '.', $name);
1096
1097 2
        return str_replace('.bundle', '.', $name);
1098
    }
1099
1100
    /**
1101
     * output a node content as an array of lines
1102
     *
1103
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1104
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1105
     *
1106
     * @throws \RuntimeException
1107
     * @throws \Symfony\Component\Process\Exception\LogicException
1108
     * @throws InvalidArgumentException
1109
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1110
     * @return array
1111
     */
1112 1
    public function outputContent(NodeObject $obj, $treeish): array
1113
    {
1114 1
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1115
1116 1
        return $this->caller->execute($command)->getOutputLines();
1117
    }
1118
1119
    /**
1120
     * output a node raw content
1121
     *
1122
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1123
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1124
     *
1125
     * @throws \RuntimeException
1126
     * @throws \Symfony\Component\Process\Exception\LogicException
1127
     * @throws InvalidArgumentException
1128
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1129
     * @return string
1130
     */
1131
    public function outputRawContent(NodeObject $obj, $treeish): string
1132
    {
1133
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1134
1135
        return $this->caller->execute($command)->getRawOutput();
1136
    }
1137
1138
    /**
1139
     * Get the path
1140
     *
1141
     * @return string
1142
     */
1143 64
    public function getPath(): string
1144
    {
1145 64
        return $this->path;
1146
    }
1147
1148
    /**
1149
     * Get the repository name
1150
     *
1151
     * @return string
1152
     */
1153 1
    public function getName(): string
1154
    {
1155 1
        return $this->name;
1156
    }
1157
1158
    /**
1159
     * Set the repository name
1160
     *
1161
     * @param string $name the repository name
1162
     */
1163 1
    public function setName(string $name): void
1164
    {
1165 1
        $this->name = $name;
1166 1
    }
1167
1168
    /**
1169
     * Caller setter
1170
     *
1171
     * @param \GitElephant\Command\Caller\Caller $caller the caller variable
1172
     */
1173
    public function setCaller(Caller $caller): void
1174
    {
1175
        $this->caller = $caller;
1176
    }
1177
1178
    /**
1179
     * Caller getter
1180
     *
1181
     * @return \GitElephant\Command\Caller\Caller
1182
     */
1183 91
    public function getCaller(): \GitElephant\Command\Caller\Caller
1184
    {
1185 91
        return $this->caller;
1186
    }
1187
1188
    /**
1189
     * get global config list
1190
     *
1191
     * @return array Global config list
1192
     */
1193 97
    public function getGlobalConfigs(): array
1194
    {
1195 97
        return $this->globalConfigs;
1196
    }
1197
1198
    /**
1199
     * add a key/value pair to the global config list
1200
     *
1201
     * @param string $name  The config name
1202
     * @param mixed  $value The config value
1203
     */
1204 1
    public function addGlobalConfig(string $name, $value): void
1205
    {
1206 1
        $this->globalConfigs[$name] = $value;
1207 1
    }
1208
1209
    /**
1210
     * remove an element form the global config list, identified by key
1211
     *
1212
     * @param  string $name The config name
1213
     */
1214 1
    public function removeGlobalConfig(string $name): void
1215
    {
1216 1
        if (isset($this->globalConfigs[$name])) {
1217 1
            unset($this->globalConfigs[$name]);
1218
        }
1219 1
    }
1220
1221
    /**
1222
     * get global options list
1223
     *
1224
     * @return array Global options list
1225
     */
1226 97
    public function getGlobalOptions(): array
1227
    {
1228 97
        return $this->globalOptions;
1229
    }
1230
1231
    /**
1232
     * add a key/value pair to the global option list
1233
     *
1234
     * @param string $name  The option name
1235
     * @param mixed  $value The option value
1236
     */
1237 1
    public function addGlobalOption(string $name, $value): void
1238
    {
1239 1
        $this->globalOptions[$name] = $value;
1240 1
    }
1241
1242
    /**
1243
     * remove an element form the global option list, identified by key
1244
     *
1245
     * @param  string $name The option name
1246
     */
1247 1
    public function removeGlobalOption(string $name): void
1248
    {
1249 1
        if (isset($this->globalOptions[$name])) {
1250 1
            unset($this->globalOptions[$name]);
1251
        }
1252 1
    }
1253
1254
    /**
1255
     * get global command arguments list
1256
     *
1257
     * @return array Global command arguments list
1258
     */
1259 97
    public function getGlobalCommandArguments(): array
1260
    {
1261 97
        return $this->globalCommandArguments;
1262
    }
1263
1264
    /**
1265
     * add a value to the global command argument list
1266
     *
1267
     * @param string $value The command argument
1268
     */
1269 1
    public function addGlobalCommandArgument($value): void
1270
    {
1271 1
        if (!in_array($value, $this->globalCommandArguments, true)) {
1272 1
            $this->globalCommandArguments[] = $value;
1273
        }
1274 1
    }
1275
1276
    /**
1277
     * remove an element form the global command argument list, identified by
1278
     * value
1279
     *
1280
     * @param  string $value The command argument
1281
     */
1282 1
    public function removeGlobalCommandArgument($value): void
1283
    {
1284 1
        if (in_array($value, $this->globalCommandArguments, true)) {
1285 1
            $index = array_search($value, $this->globalCommandArguments);
1286 1
            unset($this->globalCommandArguments[$index]);
1287
        }
1288 1
    }
1289
1290
    /**
1291
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1292
     *
1293
     * @param string|null $message
1294
     * @param boolean     $includeUntracked
1295
     * @param boolean     $keepIndex
1296
     */
1297 2
    public function stash(string $message = null, bool $includeUntracked = false, bool $keepIndex = false): void
1298
    {
1299 2
        $stashCommand = StashCommand::getInstance($this);
1300 2
        $command = $stashCommand->save($message, $includeUntracked, $keepIndex);
1301 2
        $this->caller->execute($command);
1302 1
    }
1303
1304
    /**
1305
     * Shows stash list
1306
     *
1307
     * @param array|null $options
1308
     *
1309
     * @return array
1310
     */
1311 1
    public function stashList(array $options = null): array
1312
    {
1313 1
        $stashCommand = StashCommand::getInstance($this);
1314 1
        $command = $stashCommand->listStashes($options);
1315 1
        $this->caller->execute($command);
1316
1317 1
        return array_map('trim', $this->caller->getOutputLines(true));
1318
    }
1319
1320
    /**
1321
     * Shows details for a stash
1322
     *
1323
     * @param string $stash
1324
     *
1325
     * @return string
1326
     */
1327 1
    public function stashShow(string $stash): string
1328
    {
1329 1
        $stashCommand = StashCommand::getInstance($this);
1330 1
        $command = $stashCommand->show($stash);
1331 1
        $this->caller->execute($command);
1332
1333 1
        return $this->caller->getOutput();
1334
    }
1335
1336
    /**
1337
     * Drops a stash
1338
     *
1339
     * @param string $stash
1340
     */
1341 1
    public function stashDrop(string $stash): void
1342
    {
1343 1
        $stashCommand = StashCommand::getInstance($this);
1344 1
        $command = $stashCommand->drop($stash);
1345 1
        $this->caller->execute($command);
1346 1
    }
1347
1348
    /**
1349
     * Applies a stash
1350
     *
1351
     * @param string  $stash
1352
     * @param boolean $index
1353
     */
1354 1
    public function stashApply(string $stash, bool $index = false): void
1355
    {
1356 1
        $stashCommand = StashCommand::getInstance($this);
1357 1
        $command = $stashCommand->apply($stash, $index);
1358 1
        $this->caller->execute($command);
1359 1
    }
1360
1361
    /**
1362
     *  Applies a stash, then removes it from the stash
1363
     *
1364
     * @param string  $stash
1365
     * @param boolean $index
1366
     */
1367 1
    public function stashPop(string $stash, bool $index = false): void
1368
    {
1369 1
        $stashCommand = StashCommand::getInstance($this);
1370 1
        $command = $stashCommand->pop($stash, $index);
1371 1
        $this->caller->execute($command);
1372 1
    }
1373
1374
    /**
1375
     *  Creates and checks out a new branch named <branchname> starting from the commit at which the <stash> was originally created
1376
     *
1377
     * @param string $branch
1378
     * @param string $stash
1379
     */
1380 1
    public function stashBranch(string $branch, string $stash): void
1381
    {
1382 1
        $stashCommand = StashCommand::getInstance($this);
1383 1
        $command = $stashCommand->branch($branch, $stash);
1384 1
        $this->caller->execute($command);
1385 1
    }
1386
1387
    /**
1388
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1389
     */
1390
    public function stashClear(): void
1391
    {
1392
        $stashCommand = StashCommand::getInstance($this);
1393
        $command = $stashCommand->clear();
1394
        $this->caller->execute($command);
1395
    }
1396
1397
    /**
1398
     *  Create a stash (which is a regular commit object) and return its object name, without storing it anywhere in the
1399
     *  ref namespace.
1400
     *
1401
     * @return string
1402
     */
1403 1
    public function stashCreate(): string
1404
    {
1405 1
        $stashCommand = StashCommand::getInstance($this);
1406 1
        $command = $stashCommand->clear();
1407 1
        $this->caller->execute($command);
1408
1409 1
        return $this->caller->getOutput();
1410
    }
1411
}
1412