Completed
Pull Request — develop (#165)
by
unknown
01:48
created

Repository::createFromRemote()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3.0052

Importance

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