Completed
Pull Request — develop (#169)
by Pol
02:20
created

Repository::addSubmodule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
/**
4
 * GitElephant - An abstraction layer for git written in PHP
5
 * Copyright (C) 2013  Matteo Giachino
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see [http://www.gnu.org/licenses/].
19
 */
20
21
namespace GitElephant;
22
23
use GitElephant\Command\BranchCommand;
24
use GitElephant\Command\Caller\Caller;
25
use GitElephant\Command\Caller\CallerInterface;
26
use GitElephant\Command\CatFileCommand;
27
use GitElephant\Command\FetchCommand;
28
use GitElephant\Command\LogCommand;
29
use GitElephant\Command\LsTreeCommand;
30
use GitElephant\Command\MainCommand;
31
use GitElephant\Command\MergeCommand;
32
use GitElephant\Command\PullCommand;
33
use GitElephant\Command\PushCommand;
34
use GitElephant\Command\RemoteCommand;
35
use GitElephant\Command\ResetCommand;
36
use GitElephant\Command\RevParseCommand;
37
use GitElephant\Command\StashCommand;
38
use GitElephant\Command\SubmoduleCommand;
39
use GitElephant\Command\TagCommand;
40
use GitElephant\Objects\Author;
41
use GitElephant\Objects\Branch;
42
use GitElephant\Objects\Commit;
43
use GitElephant\Objects\Diff\Diff;
44
use GitElephant\Objects\Log;
45
use GitElephant\Objects\LogRange;
46
use GitElephant\Objects\NodeObject;
47
use GitElephant\Objects\Remote;
48
use GitElephant\Objects\Tag;
49
use GitElephant\Objects\Tree;
50
use GitElephant\Objects\TreeishInterface;
51
use GitElephant\Objects\TreeObject;
52
use GitElephant\Status\Status;
53
use GitElephant\Status\StatusIndex;
54
use GitElephant\Status\StatusWorkingTree;
55
use Symfony\Component\Filesystem\Filesystem;
56
use Symfony\Component\Finder\Finder;
57
use Symfony\Component\Finder\SplFileInfo;
58
use Symfony\Component\Process\Exception\InvalidArgumentException;
59
60
/**
61
 * Repository
62
 *
63
 * Base Class for repository operations
64
 *
65
 * @author Matteo Giachino <[email protected]>
66
 * @author Dhaval Patel <[email protected]>
67
 * @author Kirk Madera <[email protected]>
68
 */
69
class Repository
70
{
71
    /**
72
     * the repository path
73
     *
74
     * @var string
75
     */
76
    private $path;
77
78
    /**
79
     * the caller instance
80
     *
81
     * @var \GitElephant\Command\Caller\CallerInterface
82
     */
83
    private $caller;
84
85
    /**
86
     * A general repository name
87
     *
88
     * @var string the repository name
89
     */
90
    private $name;
91
92
    /**
93
     * A list of global configs to apply to every command
94
     *
95
     * @var array
96
     */
97
    private $globalConfigs = [];
98
99
    /**
100
     * A list of global options to apply to every command
101
     *
102
     * @var array
103
     */
104
    private $globalOptions = [];
105
106
    /**
107
     * A list of global arguments to apply to every command
108
     *
109
     * @var array
110
     */
111
    private $globalCommandArguments = [];
112
113
    /**
114
     * Class constructor
115
     *
116
     * @param string         $repositoryPath the path of the git repository
117
     * @param string|null $binary         the path to the git binary
118
     * @param string         $name           a repository name
119
     *
120
     * @throws Exception\InvalidRepositoryPathException
121
     */
122 108
    public function __construct($repositoryPath, string $binary = null, $name = null)
123
    {
124 108
        $this->path = $repositoryPath;
125 108
        $this->caller = new Caller($binary, $repositoryPath);
126 108
        $this->name = $name;
127 108
    }
128
129
    /**
130
     * Factory method
131
     *
132
     * @param string         $repositoryPath the path of the git repository
133
     * @param string|null $binary         the path to the git binary
134
     * @param string         $name           a repository name
135
     *
136
     * @return \GitElephant\Repository
137
     */
138 107
    public static function open($repositoryPath, string $binary = null, $name = null): \GitElephant\Repository
139
    {
140 107
        return new self($repositoryPath, $binary, $name);
141
    }
142
143
    /**
144
     * create a repository from a remote git url, or a local filesystem
145
     * and save it in a temp folder
146
     *
147
     * @param string|Repository $git            the git remote url, or the filesystem path
148
     * @param string|null             $repositoryPath path
149
     * @param string|null $binary         the path to the git binary
150
     * @param string|null             $name           repository name
151
     *
152
     * @throws \RuntimeException
153
     * @throws \Symfony\Component\Filesystem\Exception\IOException
154
     * @return Repository
155
     */
156 1
    public static function createFromRemote(
157
        $git,
158
        $repositoryPath = null,
159
        string $binary = null,
160
        $name = null
161
    ): \GitElephant\Repository {
162 1
        if (null === $repositoryPath) {
163 1
            $tempDir = realpath(sys_get_temp_dir());
164 1
            $repositoryPath = sprintf('%s%s%s', $tempDir, DIRECTORY_SEPARATOR, sha1(uniqid()));
165 1
            $fs = new Filesystem();
166 1
            $fs->mkdir($repositoryPath);
167
        }
168 1
        $repository = new Repository($repositoryPath, $binary, $name);
169 1
        if ($git instanceof Repository) {
170
            $git = $git->getPath();
171
        }
172 1
        $repository->cloneFrom($git, $repositoryPath);
173 1
        $repository->checkoutAllRemoteBranches();
174
175 1
        return $repository;
176
    }
177
178
    /**
179
     * Init the repository
180
     *
181
     * @param bool $bare created a bare repository
182
     *
183
     * @throws \RuntimeException
184
     * @throws \Symfony\Component\Process\Exception\LogicException
185
     * @throws InvalidArgumentException
186
     * @throws \Symfony\Component\Process\Exception\RuntimeException
187
     * @return Repository
188
     */
189 95
    public function init($bare = false): self
190
    {
191 95
        $this->caller->execute(MainCommand::getInstance($this)->init($bare));
192
193 95
        return $this;
194
    }
195
196
    /**
197
     * Stage the working tree content
198
     *
199
     * @param string|NodeObject $path the path to store
200
     *
201
     * @throws \RuntimeException
202
     * @throws \Symfony\Component\Process\Exception\LogicException
203
     * @throws InvalidArgumentException
204
     * @throws \Symfony\Component\Process\Exception\RuntimeException
205
     * @return Repository
206
     */
207 91
    public function stage($path = '.'): self
208
    {
209 91
        $this->caller->execute(MainCommand::getInstance($this)->add($path));
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 207 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::add() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
210
211 91
        return $this;
212
    }
213
214
    /**
215
     * Unstage a tree content
216
     *
217
     * @param string|NodeObject $path the path to unstage
218
     *
219
     * @throws \RuntimeException
220
     * @throws \Symfony\Component\Process\Exception\LogicException
221
     * @throws InvalidArgumentException
222
     * @throws \Symfony\Component\Process\Exception\RuntimeException
223
     * @return Repository
224
     */
225 2
    public function unstage($path): self
226
    {
227 2
        if ($this->caller instanceof Caller) {
228 2
            $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null, [0, 1]);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 225 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::unstage() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
229
        } else {
230
            $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 225 can also be of type object<GitElephant\Objects\NodeObject>; however, GitElephant\Command\MainCommand::unstage() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

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