Completed
Pull Request — master (#166)
by
unknown
112:06 queued 110:39
created

Repository::stashApply()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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 107
    public function __construct($repositoryPath, string $binary = null, $name = null)
123
    {
124 107
        $this->path = $repositoryPath;
125 107
        $this->caller = new Caller($binary, $repositoryPath);
126 107
        $this->name = $name;
127 107
    }
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 106
    public static function open($repositoryPath, string $binary = null, $name = null): \GitElephant\Repository
139
    {
140 106
        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 94
    public function init($bare = false): self
190
    {
191 94
        $this->caller->execute(MainCommand::getInstance($this)->init($bare));
192
193 94
        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 90
    public function stage($path = '.'): self
208
    {
209 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 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 90
        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 85
    public function commit(string $message, $stageAll = false, $ref = null, $author = null, $allowEmpty = false): self
288
    {
289 85
        $currentBranch = null;
290 85
        if (!is_null($ref)) {
291 1
            $currentBranch = $this->getMainBranch();
292 1
            $this->checkout($ref);
293
        }
294 85
        if ($stageAll) {
295 83
            $this->stage();
296
        }
297 85
        $this->caller->execute(MainCommand::getInstance($this)->commit($message, $stageAll, $author, $allowEmpty));
298 85
        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 85
        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 5
    public function getStatus(): \GitElephant\Status\Status
351
    {
352 5
        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
                $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
    /**
675
     * update submodules
676
     *
677
     * @param  bool   $recursive update recursively
678
     * @param  bool   $init      init before update
679
     * @param  bool   $force     force the checkout as part of update
680
     * @param  string $path      update only a specific submodule path
681
     *
682
     * @return Repository
683
     */
684
    public function updateSubmodule(
685
        bool $recursive = false,
686
        bool $init = false,
687
        bool $force = false,
688
        $path = null
689
    ): self {
690
        $this->caller->execute(SubmoduleCommand::getInstance($this)->update($recursive, $init, $force, $path));
691
692
        return $this;
693
    }
694
695
    /**
696
     * Gets an array of Tag objects
697
     *
698
     * @throws \RuntimeException
699
     * @throws \Symfony\Component\Process\Exception\LogicException
700
     * @throws InvalidArgumentException
701
     * @throws \Symfony\Component\Process\Exception\RuntimeException
702
     * @return array
703
     */
704 4
    public function getTags(): array
705
    {
706 4
        $tags = [];
707 4
        $this->caller->execute(TagCommand::getInstance($this)->listTags());
708
709 4
        foreach ($this->caller->getOutputLines() as $tagString) {
710 4
            if ($tagString != '') {
711 3
                $tags[] = new Tag($this, trim($tagString));
712
            }
713
        }
714
715 4
        return $tags;
716
    }
717
718
    /**
719
     * Return a tag object
720
     *
721
     * @param string $name The tag name
722
     *
723
     * @throws \RuntimeException
724
     * @throws \Symfony\Component\Process\Exception\RuntimeException
725
     * @return Tag|null
726
     */
727 28
    public function getTag(string $name): ?\GitElephant\Objects\Tag
728
    {
729 28
        $tagFinderOutput = $this->caller
730 28
            ->execute(TagCommand::getInstance()->listTags())
731 28
            ->getOutputLines(true);
732
733 28
        foreach ($tagFinderOutput as $line) {
734 28
            if ($line === $name) {
735 28
                return new Tag($this, $name);
736
            }
737
        }
738
739 1
        return null;
740
    }
741
742
    /**
743
     * Return the last created tag
744
     *
745
     * @throws \LogicException
746
     * @throws \RuntimeException
747
     * @throws \InvalidArgumentException
748
     * @return Tag|null
749
     */
750 1
    public function getLastTag(): ?\GitElephant\Objects\Tag
751
    {
752 1
        $finder = Finder::create()
753 1
            ->files()
754 1
            ->in(sprintf('%s/.git/refs/tags', $this->path))
755 1
            ->sortByChangedTime();
756
757 1
        if ($finder->count() === 0) {
758
            return null;
759
        }
760
761 1
        $files = iterator_to_array($finder->getIterator(), false);
762 1
        $files = array_reverse($files);
763
        /** @var SplFileInfo $firstFile */
764 1
        $firstFile = $files[0];
765 1
        $tagName = $firstFile->getFilename();
766
767 1
        return Tag::pick($this, $tagName);
768
    }
769
770
    /**
771
     * Try to get a branch or a tag by its name.
772
     *
773
     * @param string $name the reference name (a tag name or a branch name)
774
     *
775
     * @throws \RuntimeException
776
     * @throws \InvalidArgumentException
777
     * @throws \Symfony\Component\Process\Exception\RuntimeException
778
     * @return \GitElephant\Objects\Tag|\GitElephant\Objects\Branch|null
779
     */
780 1
    public function getBranchOrTag(string $name)
781
    {
782 1
        if (in_array($name, $this->getBranches(true))) {
783 1
            return new Branch($this, $name);
784
        }
785
786 1
        $tagFinderOutput = $this->caller
787 1
            ->execute(TagCommand::getInstance($this)
788 1
            ->listTags())->getOutputLines(true);
789
        
790 1
        foreach ($tagFinderOutput as $line) {
791 1
            if ($line === $name) {
792 1
                return new Tag($this, $name);
793
            }
794
        }
795
796 1
        return null;
797
    }
798
799
    /**
800
     * Return a Commit object
801
     *
802
     * @param string $ref The commit reference
803
     *
804
     * @throws \RuntimeException
805
     * @return Objects\Commit
806
     */
807 15
    public function getCommit($ref = 'HEAD'): \GitElephant\Objects\Commit
808
    {
809 15
        return Commit::pick($this, $ref);
810
    }
811
812
    /**
813
     * count the commit to arrive to the given treeish
814
     *
815
     * @param string $start
816
     *
817
     * @throws \RuntimeException
818
     * @throws \Symfony\Component\Process\Exception\RuntimeException
819
     * @return int
820
     */
821 3
    public function countCommits($start = 'HEAD'): int
822
    {
823 3
        $commit = Commit::pick($this, $start);
824
825 3
        return $commit->count();
826
    }
827
828
    /**
829
     * Get a log for a ref
830
     *
831
     * @param string|TreeishInterface|array $ref         the treeish to check, as a string, as an object or as an array
832
     * @param string|NodeObject             $path        the physical path to the tree relative to the repository root
833
     * @param int|null                      $limit       limit to n entries
834
     * @param int|null                      $offset      skip n entries
835
     * @param boolean|false                 $firstParent skip commits brought in to branch by a merge
836
     *
837
     * @return \GitElephant\Objects\Log
838
     */
839 20
    public function getLog(
840
        $ref = 'HEAD',
841
        $path = null,
842
        int $limit = 10,
843
        int $offset = null,
844
        bool $firstParent = false
845
    ): \GitElephant\Objects\Log {
846 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 841 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...
847
    }
848
849
    /**
850
     * Get a log for a range ref
851
     *
852
     * @param string            $refStart
853
     * @param string            $refEnd
854
     * @param string|NodeObject $path        the physical path to the tree relative to the repository root
855
     * @param int|null          $limit       limit to n entries
856
     * @param int|null          $offset      skip n entries
857
     * @param boolean|false     $firstParent skip commits brought in to branch by a merge
858
     *
859
     * @return \GitElephant\Objects\LogRange|\GitElephant\Objects\Log
860
     */
861
    public function getLogRange(
862
        $refStart,
863
        $refEnd,
864
        $path = null,
865
        int $limit = 10,
866
        int $offset = null,
867
        bool $firstParent = false
868
    ) {
869
        // Handle when clients provide bad start reference on branch creation
870
        if (preg_match('~^[0]+$~', $refStart)) {
871
            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 864 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...
872
        }
873
874
        // Handle when clients provide bad end reference on branch deletion
875
        if (preg_match('~^[0]+$~', $refEnd)) {
876
            $refEnd = $refStart;
877
        }
878
879
        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 864 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...
880
    }
881
882
    /**
883
     * Get a log for an object
884
     *
885
     * @param \GitElephant\Objects\NodeObject         $obj    The Object instance
886
     * @param null|string|\GitElephant\Objects\Branch $branch The branch to read from
887
     * @param int                                     $limit  Limit to n entries
888
     * @param int|null                                $offset Skip n entries
889
     *
890
     * @throws \RuntimeException
891
     * @throws \Symfony\Component\Process\Exception\LogicException
892
     * @throws InvalidArgumentException
893
     * @throws \Symfony\Component\Process\Exception\RuntimeException
894
     * @return \GitElephant\Objects\Log
895
     */
896 3
    public function getObjectLog(
897
        NodeObject $obj,
898
        $branch = null,
899
        int $limit = 1,
900
        int $offset = null
901
    ): \GitElephant\Objects\Log {
902 3
        $command = LogCommand::getInstance($this)->showObjectLog($obj, $branch, $limit, $offset);
903
904 3
        return Log::createFromOutputLines($this, $this->caller->execute($command)->getOutputLines());
905
    }
906
907
    /**
908
     * Checkout a branch
909
     * This function change the state of the repository on the filesystem
910
     *
911
     * @param string|TreeishInterface $ref    the reference to checkout
912
     * @param bool                    $create like -b on the command line
913
     *
914
     * @throws \RuntimeException
915
     * @throws \Symfony\Component\Process\Exception\LogicException
916
     * @throws InvalidArgumentException
917
     * @throws \Symfony\Component\Process\Exception\RuntimeException
918
     * @return Repository
919
     */
920 24
    public function checkout($ref, bool $create = false): self
921
    {
922 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 920 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...
923
            $this->createBranch($ref);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 920 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...
924
        }
925 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 920 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...
926
927 24
        return $this;
928
    }
929
930
    /**
931
     * Retrieve an instance of Tree
932
     * Tree Object is Countable, Iterable and has ArrayAccess for easy manipulation
933
     *
934
     * @param string|TreeishInterface $ref  the treeish to check
935
     * @param string|NodeObject       $path Object or null for root
936
     *
937
     * @throws \RuntimeException
938
     * @throws \Symfony\Component\Process\Exception\LogicException
939
     * @throws InvalidArgumentException
940
     * @throws \Symfony\Component\Process\Exception\RuntimeException
941
     * @return Objects\Tree
942
     */
943 15
    public function getTree($ref = 'HEAD', $path = null): \GitElephant\Objects\Tree
944
    {
945 15
        if (is_string($path) && '' !== $path) {
946
            $outputLines = $this
947 9
                ->getCaller()
948 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 943 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...
949 9
                ->getOutputLines(true);
950
951 9
            $path = TreeObject::createFromOutputLine($this, $outputLines[0]);
952
        }
953
954 15
        return new Tree($this, $ref, $path);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 943 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 943 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...
955
    }
956
957
    /**
958
     * Get a Diff object for a commit with its parent, by default the diff is between the current head and its parent
959
     *
960
     * @param \GitElephant\Objects\Commit|string      $commit1 A TreeishInterface instance
961
     * @param \GitElephant\Objects\Commit|string|null $commit2 A TreeishInterface instance
962
     * @param null|string|NodeObject                  $path    The path to get the diff for or a Object instance
963
     *
964
     * @throws \RuntimeException
965
     * @throws \InvalidArgumentException
966
     * @return Objects\Diff\Diff
967
     */
968 2
    public function getDiff(
969
        string $commit1 = null,
970
        string $commit2 = null,
971
        string $path = null
972
    ): \GitElephant\Objects\Diff\Diff {
973 2
        return Diff::create($this, $commit1, $commit2, $path);
974
    }
975
976
    /**
977
     * Clone a repository
978
     *
979
     * @param string      $url           the repository url (i.e. git://github.com/matteosister/GitElephant.git)
980
     * @param null        $to            where to clone the repo
981
     * @param string|null $repoReference Repo reference to clone. Required if performing a shallow clone.
982
     * @param int|null    $depth         Depth to clone repo. Specify 1 to perform a shallow clone
983
     * @param bool        $recursive     Whether to recursively clone child repos.
984
     *
985
     * @throws \RuntimeException
986
     * @throws \Symfony\Component\Process\Exception\LogicException
987
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
988
     * @throws \Symfony\Component\Process\Exception\RuntimeException
989
     * @return Repository
990
     */
991 2
    public function cloneFrom(
992
        string $url,
993
        string $to = null,
994
        string $repoReference = null,
995
        int $depth = null,
996
        bool $recursive = false
997
    ): self {
998 2
        $command = Command\CloneCommand::getInstance($this)
999 2
            ->cloneUrl($url, $to, $repoReference, $depth, $recursive);
1000 2
        $this->caller->execute($command);
1001
1002 2
        return $this;
1003
    }
1004
1005
    /**
1006
     * @param string $name remote name
1007
     * @param string $url  remote url
1008
     *
1009
     * @throws \RuntimeException
1010
     * @throws \Symfony\Component\Process\Exception\LogicException
1011
     * @throws InvalidArgumentException
1012
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1013
     * @return Repository
1014
     */
1015 7
    public function addRemote(string $name, string $url): self
1016
    {
1017 7
        $this->caller->execute(RemoteCommand::getInstance($this)->add($name, $url));
1018
1019 7
        return $this;
1020
    }
1021
1022
    /**
1023
     * @param string $name         remote name
1024
     * @param bool   $queryRemotes Fetch new information from remotes
1025
     *
1026
     * @return \GitElephant\Objects\Remote
1027
     */
1028 1
    public function getRemote(string $name, bool $queryRemotes = true): \GitElephant\Objects\Remote
1029
    {
1030 1
        return Remote::pick($this, $name, $queryRemotes);
1031
    }
1032
1033
    /**
1034
     * gets a list of remote objects
1035
     *
1036
     * @param bool $queryRemotes Fetch new information from remotes
1037
     *
1038
     * @throws \RuntimeException
1039
     * @throws \Symfony\Component\Process\Exception\LogicException
1040
     * @throws InvalidArgumentException
1041
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1042
     * @return array
1043
     */
1044 1
    public function getRemotes(bool $queryRemotes = true): array
1045
    {
1046 1
        $remoteNames = $this->caller
1047 1
            ->execute(RemoteCommand::getInstance($this)->show(null, $queryRemotes))
1048 1
            ->getOutputLines(true);
1049
1050 1
        $remotes = [];
1051 1
        foreach ($remoteNames as $remoteName) {
1052 1
            $remotes[] = $this->getRemote($remoteName, $queryRemotes);
1053
        }
1054
1055 1
        return $remotes;
1056
    }
1057
1058
    /**
1059
     * Download objects and refs from another repository
1060
     *
1061
     * @param string $from
1062
     * @param string $ref
1063
     * @param bool   $tags
1064
     *
1065
     * @throws \RuntimeException
1066
     * @throws \Symfony\Component\Process\Exception\LogicException
1067
     * @throws InvalidArgumentException
1068
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1069
     */
1070 1
    public function fetch($from = null, $ref = null, bool $tags = false): void
1071
    {
1072 1
        $options = [];
1073 1
        if ($tags) {
1074 1
            $options = ['--tags'];
1075
        }
1076 1
        $this->caller->execute(FetchCommand::getInstance($this)->fetch($from, $ref, $options));
1077 1
    }
1078
1079
    /**
1080
     * Fetch from and merge with another repository or a local branch
1081
     *
1082
     * @param string $from
1083
     * @param string $ref
1084
     * @param bool   $rebase
1085
     *
1086
     * @throws \RuntimeException
1087
     * @throws \Symfony\Component\Process\Exception\LogicException
1088
     * @throws InvalidArgumentException
1089
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1090
     */
1091 2
    public function pull($from = null, $ref = null, bool $rebase = true): void
1092
    {
1093 2
        $this->caller->execute(PullCommand::getInstance($this)->pull($from, $ref, $rebase));
1094 2
    }
1095
1096
    /**
1097
     * Push changes to remote repository
1098
     *
1099
     * @param string $to
1100
     * @param string $ref
1101
     * @param string $args
1102
     *
1103
     * @throws \RuntimeException
1104
     * @throws \Symfony\Component\Process\Exception\LogicException
1105
     * @throws InvalidArgumentException
1106
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1107
     */
1108 1
    public function push($to = null, $ref = null, string $args = null): void
1109
    {
1110 1
        $this->caller->execute(PushCommand::getInstance($this)->push($to, $ref, $args));
1111 1
    }
1112
1113
    /**
1114
     * get the humanish name of the repository
1115
     *
1116
     * @return string
1117
     */
1118 2
    public function getHumanishName(): string
1119
    {
1120 2
        $name = substr($this->getPath(), strrpos($this->getPath(), '/') + 1);
1121 2
        $name = str_replace('.git', '.', $name);
1122
1123 2
        return str_replace('.bundle', '.', $name);
1124
    }
1125
1126
    /**
1127
     * output a node content as an array of lines
1128
     *
1129
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1130
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1131
     *
1132
     * @throws \RuntimeException
1133
     * @throws \Symfony\Component\Process\Exception\LogicException
1134
     * @throws InvalidArgumentException
1135
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1136
     * @return array
1137
     */
1138 1
    public function outputContent(NodeObject $obj, $treeish): array
1139
    {
1140 1
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1141
1142 1
        return $this->caller->execute($command)->getOutputLines();
1143
    }
1144
1145
    /**
1146
     * output a node raw content
1147
     *
1148
     * @param \GitElephant\Objects\NodeObject              $obj     The Object of type BLOB
1149
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1150
     *
1151
     * @throws \RuntimeException
1152
     * @throws \Symfony\Component\Process\Exception\LogicException
1153
     * @throws InvalidArgumentException
1154
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1155
     * @return string
1156
     */
1157
    public function outputRawContent(NodeObject $obj, $treeish): string
1158
    {
1159
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1160
1161
        return $this->caller->execute($command)->getRawOutput();
1162
    }
1163
1164
    /**
1165
     * Get the path
1166
     *
1167
     * @return string
1168
     */
1169 64
    public function getPath(): string
1170
    {
1171 64
        return $this->path;
1172
    }
1173
1174
    /**
1175
     * Get the repository name
1176
     *
1177
     * @return string
1178
     */
1179 1
    public function getName(): string
1180
    {
1181 1
        return $this->name;
1182
    }
1183
1184
    /**
1185
     * Set the repository name
1186
     *
1187
     * @param string $name the repository name
1188
     */
1189 1
    public function setName(string $name): void
1190
    {
1191 1
        $this->name = $name;
1192 1
    }
1193
1194
    /**
1195
     * Caller setter
1196
     *
1197
     * @param CallerInterface $caller the caller variable
1198
     */
1199
    public function setCaller(CallerInterface $caller): void
1200
    {
1201
        $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...
1202
    }
1203
1204
    /**
1205
     * Caller getter
1206
     *
1207
     * @return CallerInterface the caller to use to call commands
1208
     */
1209 91
    public function getCaller(): CallerInterface
1210
    {
1211 91
        return $this->caller;
1212
    }
1213
1214
    /**
1215
     * get global config list
1216
     *
1217
     * @return array Global config list
1218
     */
1219 97
    public function getGlobalConfigs(): array
1220
    {
1221 97
        return $this->globalConfigs;
1222
    }
1223
1224
    /**
1225
     * add a key/value pair to the global config list
1226
     *
1227
     * @param string $name  The config name
1228
     * @param mixed  $value The config value
1229
     */
1230 1
    public function addGlobalConfig(string $name, $value): void
1231
    {
1232 1
        $this->globalConfigs[$name] = $value;
1233 1
    }
1234
1235
    /**
1236
     * remove an element form the global config list, identified by key
1237
     *
1238
     * @param  string $name The config name
1239
     */
1240 1
    public function removeGlobalConfig(string $name): void
1241
    {
1242 1
        if (isset($this->globalConfigs[$name])) {
1243 1
            unset($this->globalConfigs[$name]);
1244
        }
1245 1
    }
1246
1247
    /**
1248
     * get global options list
1249
     *
1250
     * @return array Global options list
1251
     */
1252 97
    public function getGlobalOptions(): array
1253
    {
1254 97
        return $this->globalOptions;
1255
    }
1256
1257
    /**
1258
     * add a key/value pair to the global option list
1259
     *
1260
     * @param string $name  The option name
1261
     * @param mixed  $value The option value
1262
     */
1263 1
    public function addGlobalOption(string $name, $value): void
1264
    {
1265 1
        $this->globalOptions[$name] = $value;
1266 1
    }
1267
1268
    /**
1269
     * remove an element form the global option list, identified by key
1270
     *
1271
     * @param  string $name The option name
1272
     */
1273 1
    public function removeGlobalOption(string $name): void
1274
    {
1275 1
        if (isset($this->globalOptions[$name])) {
1276 1
            unset($this->globalOptions[$name]);
1277
        }
1278 1
    }
1279
1280
    /**
1281
     * get global command arguments list
1282
     *
1283
     * @return array Global command arguments list
1284
     */
1285 97
    public function getGlobalCommandArguments(): array
1286
    {
1287 97
        return $this->globalCommandArguments;
1288
    }
1289
1290
    /**
1291
     * add a value to the global command argument list
1292
     *
1293
     * @param string $value The command argument
1294
     */
1295 1
    public function addGlobalCommandArgument($value): void
1296
    {
1297 1
        if (!in_array($value, $this->globalCommandArguments, true)) {
1298 1
            $this->globalCommandArguments[] = $value;
1299
        }
1300 1
    }
1301
1302
    /**
1303
     * remove an element form the global command argument list, identified by
1304
     * value
1305
     *
1306
     * @param  string $value The command argument
1307
     */
1308 1
    public function removeGlobalCommandArgument($value): void
1309
    {
1310 1
        if (in_array($value, $this->globalCommandArguments, true)) {
1311 1
            $index = array_search($value, $this->globalCommandArguments);
1312 1
            unset($this->globalCommandArguments[$index]);
1313
        }
1314 1
    }
1315
1316
    /**
1317
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1318
     *
1319
     * @param string|null $message
1320
     * @param boolean     $includeUntracked
1321
     * @param boolean     $keepIndex
1322
     */
1323 2
    public function stash(string $message = null, bool $includeUntracked = false, bool $keepIndex = false): void
1324
    {
1325 2
        $stashCommand = StashCommand::getInstance($this);
1326 2
        $command = $stashCommand->save($message, $includeUntracked, $keepIndex);
1327 2
        $this->caller->execute($command);
1328 1
    }
1329
1330
    /**
1331
     * Shows stash list
1332
     *
1333
     * @param array|null $options
1334
     *
1335
     * @return array
1336
     */
1337 1
    public function stashList(array $options = null): array
1338
    {
1339 1
        $stashCommand = StashCommand::getInstance($this);
1340 1
        $command = $stashCommand->listStashes($options);
1341 1
        $this->caller->execute($command);
1342
1343 1
        return array_map('trim', $this->caller->getOutputLines(true));
1344
    }
1345
1346
    /**
1347
     * Shows details for a stash
1348
     *
1349
     * @param string $stash
1350
     *
1351
     * @return string
1352
     */
1353 1
    public function stashShow(string $stash): string
1354
    {
1355 1
        $stashCommand = StashCommand::getInstance($this);
1356 1
        $command = $stashCommand->show($stash);
1357 1
        $this->caller->execute($command);
1358
1359 1
        return $this->caller->getOutput();
1360
    }
1361
1362
    /**
1363
     * Drops a stash
1364
     *
1365
     * @param string $stash
1366
     */
1367 1
    public function stashDrop(string $stash): void
1368
    {
1369 1
        $stashCommand = StashCommand::getInstance($this);
1370 1
        $command = $stashCommand->drop($stash);
1371 1
        $this->caller->execute($command);
1372 1
    }
1373
1374
    /**
1375
     * Applies a stash
1376
     *
1377
     * @param string  $stash
1378
     * @param boolean $index
1379
     */
1380 1
    public function stashApply(string $stash, bool $index = false): void
1381
    {
1382 1
        $stashCommand = StashCommand::getInstance($this);
1383 1
        $command = $stashCommand->apply($stash, $index);
1384 1
        $this->caller->execute($command);
1385 1
    }
1386
1387
    /**
1388
     *  Applies a stash, then removes it from the stash
1389
     *
1390
     * @param string  $stash
1391
     * @param boolean $index
1392
     */
1393 1
    public function stashPop(string $stash, bool $index = false): void
1394
    {
1395 1
        $stashCommand = StashCommand::getInstance($this);
1396 1
        $command = $stashCommand->pop($stash, $index);
1397 1
        $this->caller->execute($command);
1398 1
    }
1399
1400
    /**
1401
     *  Creates and checks out a new branch named <branchname> starting from the commit at which the <stash> was originally created
1402
     *
1403
     * @param string $branch
1404
     * @param string $stash
1405
     */
1406 1
    public function stashBranch(string $branch, string $stash): void
1407
    {
1408 1
        $stashCommand = StashCommand::getInstance($this);
1409 1
        $command = $stashCommand->branch($branch, $stash);
1410 1
        $this->caller->execute($command);
1411 1
    }
1412
1413
    /**
1414
     *  Save your local modifications to a new stash, and run git reset --hard to revert them.
1415
     */
1416
    public function stashClear(): void
1417
    {
1418
        $stashCommand = StashCommand::getInstance($this);
1419
        $command = $stashCommand->clear();
1420
        $this->caller->execute($command);
1421
    }
1422
1423
    /**
1424
     *  Create a stash (which is a regular commit object) and return its object name, without storing it anywhere in the
1425
     *  ref namespace.
1426
     *
1427
     * @return string
1428
     */
1429 1
    public function stashCreate(): string
1430
    {
1431 1
        $stashCommand = StashCommand::getInstance($this);
1432 1
        $command = $stashCommand->clear();
1433 1
        $this->caller->execute($command);
1434
1435 1
        return $this->caller->getOutput();
1436
    }
1437
}
1438