Completed
Push — develop ( 5247fd...3c6b2e )
by
unknown
9s
created

Repository::checkoutAllRemoteBranches()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

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