Completed
Pull Request — develop (#109)
by
unknown
15:23
created

Repository   F

Complexity

Total Complexity 110

Size/Duplication

Total Lines 1250
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 34

Test Coverage

Coverage 90.94%

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 110
lcom 4
cbo 34
dl 0
loc 1250
ccs 291
cts 320
cp 0.9094
rs 0.5217
c 9
b 0
f 0

69 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
A open() 0 4 1
A createFromRemote() 0 17 3
A init() 0 6 1
A stage() 0 6 1
A unstage() 0 6 1
A move() 0 6 1
A remove() 0 6 1
A commit() 0 17 4
A revParse() 0 6 1
A isBare() 0 7 1
A reset() 0 4 1
A getStatus() 0 4 1
A getWorkingTreeStatus() 0 4 1
A getIndexStatus() 0 4 1
A isClean() 0 4 1
A isDirty() 0 4 1
A getStatusOutput() 0 15 2
A createBranch() 0 6 1
A deleteBranch() 0 6 1
B getBranches() 0 32 4
A getMainBranch() 0 20 2
A getBranch() 0 11 3
A checkoutAllRemoteBranches() 0 20 4
B merge() 0 25 4
A createTag() 0 6 1
A deleteTag() 0 10 2
A addSubmodule() 0 6 1
A initSubmodule() 0 5 1
A updateSubmodule() 0 5 1
A getTags() 0 12 3
A getTag() 0 11 3
A getLastTag() 0 17 2
A getBranchOrTag() 0 14 4
A getCommit() 0 6 1
A countCommits() 0 6 1
A getLog() 0 4 1
A getLogRange() 0 14 3
A getObjectLog() 0 6 1
A checkout() 0 10 3
A getTree() 0 11 3
A getDiff() 0 4 1
A cloneFrom() 0 6 1
A addRemote() 0 6 1
A getRemote() 0 4 1
A getRemotes() 0 11 2
A fetch() 0 16 3
A pull() 0 4 1
A push() 0 4 1
A getHumanishName() 0 8 1
A outputContent() 0 6 1
A outputRawContent() 0 6 1
A getPath() 0 4 1
A getName() 0 4 1
A setName() 0 4 1
A setCaller() 0 4 1
A getCaller() 0 4 1
A getGlobalConfigs() 0 4 1
A addGlobalConfig() 0 4 1
A removeGlobalConfig() 0 6 2
A getGlobalOptions() 0 4 1
A addGlobalOption() 0 4 1
A removeGlobalOption() 0 6 2
A getGlobalCommandArguments() 0 4 1
A addGlobalCommandArgument() 0 6 2
A removeGlobalCommandArgument() 0 7 2
A getCacheTag() 0 4 1
A getCacheKey() 0 4 1
A flushCache() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Repository often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Repository, and based on these observations, apply Extract Interface, too.

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\Objects\Author;
29
use \GitElephant\Objects\Remote;
30
use \GitElephant\Objects\Tree;
31
use \GitElephant\Objects\Branch;
32
use \GitElephant\Objects\Tag;
33
use \GitElephant\Objects\Object;
34
use \GitElephant\Objects\Diff\Diff;
35
use \GitElephant\Objects\Commit;
36
use \GitElephant\Objects\Log;
37
use \GitElephant\Objects\LogRange;
38
use \GitElephant\Objects\TreeishInterface;
39
use \GitElephant\Command\MainCommand;
40
use \GitElephant\Command\BranchCommand;
41
use \GitElephant\Command\MergeCommand;
42
use \GitElephant\Command\RevParseCommand;
43
use \GitElephant\Command\TagCommand;
44
use \GitElephant\Command\LogCommand;
45
use \GitElephant\Command\CloneCommand;
46
use \GitElephant\Command\CatFileCommand;
47
use \GitElephant\Command\LsTreeCommand;
48
use \GitElephant\Command\SubmoduleCommand;
49
use GitElephant\Objects\TreeObject;
50
use \GitElephant\Status\Status;
51
use \GitElephant\Status\StatusIndex;
52
use \GitElephant\Status\StatusWorkingTree;
53
use \Symfony\Component\Filesystem\Filesystem;
54
use \Symfony\Component\Finder\Finder;
55
use \Symfony\Component\Finder\SplFileInfo;
56
use Illuminate\Support\Facades\Cache;
57
58
/**
59
 * Repository
60
 *
61
 * Base Class for repository operations
62
 *
63
 * @author Matteo Giachino <[email protected]>
64
 * @author Dhaval Patel <[email protected]>
65
 */
66
class Repository
67
{
68
    /**
69
     * the repository path
70
     *
71
     * @var string
72
     */
73
    private $path;
74
75
    /**
76
     * the caller instance
77
     *
78
     * @var \GitElephant\Command\Caller\Caller
79
     */
80
    private $caller;
81
82
    /**
83
     * A general repository name
84
     *
85
     * @var string $name the repository name
86
     */
87
    private $name;
88
89
    /**
90
     * A list of global configs to apply to every command
91
     * 
92
     * @var array
93
     */
94
    private $globalConfigs = array();
95
96
    /**
97
     * A list of global options to apply to every command
98
     * 
99
     * @var array
100
     */
101
    private $globalOptions = array();
102
103
    /**
104
     * A list of global arguments to apply to every command
105
     * 
106
     * @var array
107
     */
108
    private $globalCommandArguments = array();
109
110
    /**
111
     * Class constructor
112
     *
113
     * @param string         $repositoryPath the path of the git repository
114
     * @param GitBinary|null $binary         the GitBinary instance that calls the commands
115
     * @param string         $name           a repository name
116
     *
117
     * @throws Exception\InvalidRepositoryPathException
118 103
     */
119
    public function __construct($repositoryPath, GitBinary $binary = null, $name = null)
120 103
    {
121 103
        if (is_null($binary)) {
122 103
            $binary = new GitBinary();
123
        }
124 103
125 103
        $this->path = $repositoryPath;
126 103
        $this->caller = new Caller($binary, $repositoryPath);
127 103
        $this->name = $name;
128
    }
129
130
    /**
131
     * Factory method
132
     *
133
     * @param string         $repositoryPath the path of the git repository
134
     * @param GitBinary|null $binary         the GitBinary instance that calls the commands
135
     * @param string         $name           a repository name
136
     *
137
     * @return \GitElephant\Repository
138 102
     */
139 1
    public static function open($repositoryPath, GitBinary $binary = null, $name = null)
140 102
    {
141
        return new self($repositoryPath, $binary, $name);
142
    }
143
144
    /**
145
     * create a repository from a remote git url, or a local filesystem
146
     * and save it in a temp folder
147
     *
148
     * @param string|Repository $git            the git remote url, or the filesystem path
149
     * @param null              $repositoryPath path
150
     * @param GitBinary         $binary         binary
151
     * @param null              $name           repository name
152
     *
153
     * @throws \RuntimeException
154
     * @throws \Symfony\Component\Filesystem\Exception\IOException
155
     * @return Repository
156 1
     */
157
    public static function createFromRemote($git, $repositoryPath = null, GitBinary $binary = null, $name = null)
158 1
    {
159 1
        if (null === $repositoryPath) {
160 1
            $tempDir = realpath(sys_get_temp_dir());
161 1
            $repositoryPath = sprintf('%s%s%s', $tempDir, DIRECTORY_SEPARATOR, sha1(uniqid()));
162 1
            $fs = new Filesystem();
163 1
            $fs->mkdir($repositoryPath);
164 1
        }
165 1
        $repository = new Repository($repositoryPath, $binary, $name);
166
        if ($git instanceof Repository) {
167
            $git = $git->getPath();
168 1
        }
169 1
        $repository->cloneFrom($git, $repositoryPath);
0 ignored issues
show
Bug introduced by
It seems like $repositoryPath defined by sprintf('%s%s%s', $tempD...ARATOR, sha1(uniqid())) on line 161 can also be of type string; however, GitElephant\Repository::cloneFrom() does only seem to accept null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
170
        $repository->checkoutAllRemoteBranches();
171 1
172
        return $repository;
173
    }
174
175
    /**
176
     * Init the repository
177
     *
178
     * @param bool $bare created a bare repository
179
     *
180
     * @throws \RuntimeException
181
     * @throws \Symfony\Component\Process\Exception\LogicException
182
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
183
     * @throws \Symfony\Component\Process\Exception\RuntimeException
184
     * @return Repository
185 92
     */
186
    public function init($bare = false)
187 92
    {
188
        $this->caller->execute(MainCommand::getInstance($this)->init($bare));
189 92
190
        return $this;
191
    }
192
193
    /**
194
     * Stage the working tree content
195
     *
196
     * @param string|Object $path the path to store
197
     *
198
     * @throws \RuntimeException
199
     * @throws \Symfony\Component\Process\Exception\LogicException
200
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
201
     * @throws \Symfony\Component\Process\Exception\RuntimeException
202
     * @return Repository
203 88
     */
204
    public function stage($path = '.')
205 88
    {
206
        $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 204 can also be of type object<GitElephant\Objects\Object>; 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...
207 88
208
        return $this;
209
    }
210
211
    /**
212
     * Unstage a tree content
213
     *
214
     * @param string|Object $path the path to unstage
215
     *
216
     * @throws \RuntimeException
217
     * @throws \Symfony\Component\Process\Exception\LogicException
218
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
219
     * @throws \Symfony\Component\Process\Exception\RuntimeException
220
     * @return Repository
221 2
     */
222
    public function unstage($path)
223 2
    {
224
        $this->caller->execute(MainCommand::getInstance($this)->unstage($path), true, null, array(0, 1));
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 222 can also be of type object<GitElephant\Objects\Object>; 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...
225 2
226
        return $this;
227
    }
228
229
    /**
230
     * Move a file/directory
231
     *
232
     * @param string|Object $from source path
233
     * @param string|Object $to   destination path
234
     *
235
     * @throws \RuntimeException
236
     * @throws \Symfony\Component\Process\Exception\LogicException
237
     * @throws \InvalidArgumentException
238
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
239
     * @throws \Symfony\Component\Process\Exception\RuntimeException
240
     * @return Repository
241 1
     */
242
    public function move($from, $to)
243 1
    {
244
        $this->caller->execute(MainCommand::getInstance($this)->move($from, $to));
245 1
246
        return $this;
247
    }
248
249
    /**
250
     * Remove a file/directory
251
     *
252
     * @param string|Object $path      the path to remove
253
     * @param bool          $recursive recurse
254
     * @param bool          $force     force
255
     *
256
     * @throws \RuntimeException
257
     * @throws \Symfony\Component\Process\Exception\LogicException
258
     * @throws \InvalidArgumentException
259
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
260
     * @throws \Symfony\Component\Process\Exception\RuntimeException
261
     * @return Repository
262 1
     */
263
    public function remove($path, $recursive = false, $force = false)
264 1
    {
265
        $this->caller->execute(MainCommand::getInstance($this)->remove($path, $recursive, $force));
266 1
267
        return $this;
268
    }
269
270
    /**
271
     * Commit content to the repository, eventually staging all unstaged content
272
     *
273
     * @param string        $message  the commit message
274
     * @param bool          $stageAll whether to stage on not everything before commit
275
     * @param string|null   $ref      the reference to commit to (checkout -> commit -> checkout previous)
276
     * @param string|Author $author   override the author for this commit
277
     *
278
     * @throws \RuntimeException
279
     * @throws \InvalidArgumentException
280
     * @throws \Symfony\Component\Process\Exception\RuntimeException
281
     * @return Repository
282 85
     */
283
    public function commit($message, $stageAll = false, $ref = null, $author = null, $allowEmpty = false)
284 85
    {
285 85
        $currentBranch = null;
286 1
        if (! is_null($ref)) {
287 1
            $currentBranch = $this->getMainBranch();
288 1
            $this->checkout($ref);
289 85
        }
290 83
        if ($stageAll) {
291 83
            $this->stage();
292 85
        }
293 85
        $this->caller->execute(MainCommand::getInstance($this)->commit($message, $stageAll, $author, $allowEmpty));
294 1
        if (! is_null($ref)) {
295 1
            $this->checkout($currentBranch);
0 ignored issues
show
Bug introduced by
It seems like $currentBranch defined by null on line 285 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...
296
        }
297 85
298
        return $this;
299
    }
300
301
    /**
302
     * rev-parse command - often used to return a commit tag.
303
     *
304
     * @param array                  $options the options to apply to rev-parse
305
     * @param string|Object|Commit   $arg the argument (may be a branch head, etc)
306
     *
307
     * @throws \RuntimeException
308
     * @throws \InvalidArgumentException
309
     * @throws \Symfony\Component\Process\Exception\RuntimeException
310
     * @return array
311 1
     */
312
    public function revParse($arg = null, Array $options = array())
0 ignored issues
show
Coding Style introduced by
As per coding-style, PHP keywords should be in lowercase; expected array, but found Array.
Loading history...
313 1
    {
314
        $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 312 can also be of type object<GitElephant\Objects\Commit> or object<GitElephant\Objects\Object>; 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...
315 1
316
        return array_map('trim', $this->caller->getOutputLines(true));
317
    }
318
319
    /**
320
     * Check if this is a bare repository
321
     * @return boolean
322 1
     */
323
    public function isBare()
324 1
    {
325 1
        $options = array(RevParseCommand::OPTION_IS_BARE_REPOSIORY);
326
        $this->caller->execute(RevParseCommand::getInstance()->revParse(null, $options));
327 1
328
        return trim($this->caller->getOutput()) === 'true';
329
    }
330
331
    /**
332
     * @param TreeishInterface|Commit|string $arg
333
     * @param array $options
334 2
     */
335
    public function reset($arg,$options)
0 ignored issues
show
Coding Style introduced by
Expected 1 space between comma and argument "$options"; 0 found
Loading history...
336 2
    {
337 2
        $this->caller->execute(ResetCommand::getInstance($this)->reset($arg,$options));
338
    }
339
340
    /**
341
     * Get the repository status
342
     *
343
     * @return Status
344 5
     */
345
    public function getStatus()
346 5
    {
347
        return Status::get($this);
348
    }
349
350
    /**
351
     * @return StatusWorkingTree
352 1
     */
353
    public function getWorkingTreeStatus()
354 1
    {
355
        return StatusWorkingTree::get($this);
356
    }
357
358
    /**
359
     * @return StatusIndex
360 4
     */
361
    public function getIndexStatus()
362 4
    {
363
        return StatusIndex::get($this);
364
    }
365
    
366
    /**
367
     * isClean Return true if the repository is not dirty.
368
     * 
369
     * @return boolean
370
     */
371
    public function isClean()
372
    {
373
        return $this->getStatus()->all()->isEmpty();
374
    }
375
    
376
    /**
377
     * isDirty Return true if the repository has some modified files.
378
     * 
379
     * @return boolean
380
     */
381
    public function isDirty()
382
    {
383
        return !$this->isClean();
384
    }
385
386
    /**
387
     * Get the repository status as a string
388
     *
389
     * @throws \RuntimeException
390
     * @throws \Symfony\Component\Process\Exception\LogicException
391
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
392
     * @throws \Symfony\Component\Process\Exception\RuntimeException
393
     * @return array
394 4
     */
395
    public function getStatusOutput() {
396 4
397
    	$cacheKey = $this->getCacheKey();
398 4
	    $cacheTag = $this->getCacheTag();
399
		if(Cache::tags($cacheTag)->has($cacheKey)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
Coding Style introduced by
Blank line found at start of control structure
Loading history...
400
401
			return Cache::tags($cacheTag)->get($cacheKey);
402
		}
403
404
        $this->caller->execute(MainCommand::getInstance($this)->status());
405
		$outputLines = array_map('trim', $this->caller->getOutputLines());
406
		Cache::tags($cacheTag)->put($cacheKey, $outputLines, 15);
407
408
        return $outputLines;
409
    }
410
411 27
    /**
412
     * Create a new branch
413 27
     *
414
     * @param string $name       the new branch name
415 27
     * @param null   $startPoint the reference to create the branch from
416
     *
417
     * @throws \RuntimeException
418
     * @throws \Symfony\Component\Process\Exception\RuntimeException
419
     * @return Repository
420
     */
421
    public function createBranch($name, $startPoint = null)
422
    {
423
        Branch::create($this, $name, $startPoint);
424
425
        return $this;
426
    }
427
428
    /**
429
     * Delete a branch by its name
430
     * This function change the state of the repository on the filesystem
431 1
     *
432
     * @param string $name  The branch to delete
433 1
     * @param bool   $force Force the delete
434
     *
435 1
     * @throws \RuntimeException
436
     * @throws \Symfony\Component\Process\Exception\LogicException
437
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
438
     * @throws \Symfony\Component\Process\Exception\RuntimeException
439
     * @return Repository
440
     */
441
    public function deleteBranch($name, $force = false)
442
    {
443
        $this->caller->execute(BranchCommand::getInstance($this)->delete($name, $force));
444
445
        return $this;
446
    }
447
448
    /**
449
     * An array of Branch objects
450
     *
451 17
     * @param bool $namesOnly return an array of branch names as a string
452
     * @param bool $all       lists also remote branches
453 17
     *
454 17
     * @throws \RuntimeException
455 6
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
456 6
     * @throws \Symfony\Component\Process\Exception\LogicException
457 6
     * @throws \InvalidArgumentException
458 6
     * @throws \Symfony\Component\Process\Exception\RuntimeException
459
     * @return array
460 6
     */
461 6
    public function getBranches($namesOnly = false, $all = false)
462
    {
463 6
    	$cacheKey = $this->getCacheKey();
464 6
    	$cacheTag = $this->getCacheTag();
465 14
    	if(Cache::tags($cacheTag)->has($cacheKey)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
Coding Style introduced by
Blank line found at start of control structure
Loading history...
466 14
467 14
    		return Cache::tags($cacheTag)->get($cacheKey);
468 14
	    }
469 14
470 14
        $branches = array();
471
        if ($namesOnly) {
472
            $outputLines = $this->caller->execute(
473 17
                BranchCommand::getInstance($this)->listBranches($all, true)
474
            )->getOutputLines(true);
475
            $branches = array_map(
476
                function ($v) {
477
                    return ltrim($v, '* ');
478
                },
479
                $outputLines
480
            );
481
        } else {
482
            $outputLines = $this->caller->execute(
483
                BranchCommand::getInstance($this)->listBranches($all)
484 5
            )->getOutputLines(true);
485
            foreach ($outputLines as $branchLine) {
486 5
                $branches[] = Branch::createFromOutputLine($this, $branchLine);
487 5
            }
488
        }
489 5
490
	    Cache::tags($cacheTag)->put($cacheKey, $branches, 15);
491 5
        return $branches;
492 5
    }
493
494 5
    /**
495
     * Return the actually checked out branch
496
     *
497
     * @throws \RuntimeException
498
     * @throws \InvalidArgumentException
499
     * @throws \Symfony\Component\Process\Exception\RuntimeException
500
     * @return Objects\Branch
501
     */
502
    public function getMainBranch()
503
    {
504
    	$cacheKey = $this->getCacheKey();
505
	    $cacheTag = $this->getCacheTag();
506
	    if(Cache::tags($cacheTag)->has($cacheKey)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
Coding Style introduced by
Blank line found at start of control structure
Loading history...
507 9
508
	    	return Cache::tags($cacheTag)->get($cacheKey);
509
	    }
510 9
511 9
        $filtered = array_filter(
512 9
            $this->getBranches(),
513
            function (Branch $branch) {
514 4
                return $branch->getCurrent();
515
            }
516 1
        );
517
        sort($filtered);
518
519
        Cache::tags($cacheTag)->put($cacheKey, $filtered[0], 15);
520
        return $filtered[0];
521
    }
522
523
    /**
524
     * Retrieve a Branch object by a branch name
525
     *
526
     * @param string $name The branch name
527
     *
528
     * @throws \RuntimeException
529 1
     * @throws \InvalidArgumentException
530
     * @throws \Symfony\Component\Process\Exception\RuntimeException
531 1
     * @return null|Branch
532 1
     */
533 1
    public function getBranch($name)
534 1
    {
535 1
        /** @var Branch $branch */
536 1
        foreach ($this->getBranches() as $branch) {
537 1
            if ($branch->getName() == $name) {
538 1
                return $branch;
539 1
            }
540
        }
541 1
542 1
        return null;
543 1
    }
544 1
545 1
    /**
546
     * Checkout all branches from the remote and make them local
547 1
     *
548
     * @param string $remote remote to fetch from
549
     *
550
     * @throws \RuntimeException
551
     * @throws \InvalidArgumentException
552
     * @throws \Symfony\Component\Process\Exception\RuntimeException
553
     * @return Repository
554
     */
555
    public function checkoutAllRemoteBranches($remote = 'origin')
556
    {
557
        $actualBranch = $this->getMainBranch();
558
        $actualBranches = $this->getBranches(true, false);
559
        $allBranches = $this->getBranches(true, true);
560
        $realBranches = array_filter(
561
            $allBranches,
562
            function ($branch) use ($actualBranches) {
563 2
                return !in_array($branch, $actualBranches)
564
                && preg_match('/^remotes(.+)$/', $branch)
565
                && !preg_match('/^(.+)(HEAD)(.*?)$/', $branch);
566 2
            }
567 2
        );
568 2
        foreach ($realBranches as $realBranch) {
569 2
            $this->checkout(str_replace(sprintf('remotes/%s/', $remote), '', $realBranch));
570 2
        }
571
        $this->checkout($actualBranch);
572
573
        return $this;
574 2
    }
575
576 2
    /**
577 1
     * Merge a Branch in the current checked out branch
578 1
     *
579 2
     * @param Objects\Branch $branch  The branch to merge in the current checked out branch
580 2
     * @param string         $message The message for the merge commit, if merge is 3-way
581 1
     * @param string         $mode    The merge mode: ff-only, no-ff or auto
582
     *
583
     * @throws \RuntimeException
584 2
     * @throws \Symfony\Component\Process\Exception\LogicException
585
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
586 2
     * @throws \Symfony\Component\Process\Exception\RuntimeException
587
     * @return Repository
588
     */
589
    public function merge(Branch $branch, $message = '', $mode = 'auto')
590
    {
591
        $valid_modes = array(
592
            'auto',    // deafult git behavior
593
            'ff-only', // force fast forward merge
594
            'no-ff',   // force 3-way merge
595
        );
596
        if (!in_array($mode, $valid_modes)) {
597
            throw new \Symfony\Component\Process\Exception\InvalidArgumentException("Invalid merge mode: $mode.");
598
        }
599
600
        $options = array();
601 25
        switch ($mode) {
602
            case 'ff-only':
603 25
                $options[] = MergeCommand::MERGE_OPTION_FF_ONLY;
604
                break;
605 25
            case 'no-ff':
606
                $options[] = MergeCommand::MERGE_OPTION_NO_FF;
607
                break;
608
        }
609
610
        $this->caller->execute(MergeCommand::getInstance($this)->merge($branch, $message, $options));
611
612
        return $this;
613
    }
614
615
    /**
616
     * Create a new tag
617
     * This function change the state of the repository on the filesystem
618 2
     *
619
     * @param string $name       The new tag name
620 2
     * @param null   $startPoint The reference to create the tag from
621 1
     * @param null   $message    the tag message
622 1
     *
623 1
     * @throws \RuntimeException
624
     * @throws \Symfony\Component\Process\Exception\RuntimeException
625
     * @return Repository
626 2
     */
627
    public function createTag($name, $startPoint = null, $message = null)
628
    {
629
        Tag::create($this, $name, $startPoint, $message);
630
631
        return $this;
632
    }
633
634
    /**
635
     * Delete a tag by it's name or by passing a Tag object
636
     * This function change the state of the repository on the filesystem
637
     *
638
     * @param string|Tag $tag The tag name or the Tag object
639
     *
640
     * @throws \RuntimeException
641 1
     * @throws \Symfony\Component\Process\Exception\RuntimeException
642
     * @return Repository
643 1
     */
644
    public function deleteTag($tag)
645 1
    {
646
        if ($tag instanceof Tag) {
647
            $tag->delete();
648
        } else {
649
            Tag::pick($this, $tag)->delete();
650
        }
651
652
        return $this;
653
    }
654
655
    /**
656
     * add a git submodule to the repository
657
     *
658
     * @param string $gitUrl git url of the submodule
659
     * @param string $path   path to register the submodule to
660
     *
661
     * @throws \RuntimeException
662
     * @throws \Symfony\Component\Process\Exception\LogicException
663
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
664
     * @throws \Symfony\Component\Process\Exception\RuntimeException
665
     * @return Repository
666
     */
667
    public function addSubmodule($gitUrl, $path = null)
668
    {
669
        $this->caller->execute(SubmoduleCommand::getInstance($this)->add($gitUrl, $path));
670
671
        return $this;
672
    }
673
674
    /**
675
     * initialize submodules
676
     *
677
     * @param  string $path init only submodules at the specified path
678
     *
679
     * @return Repository
680
     */
681
    public function initSubmodule($path = null)
682
    {
683
        $this->caller->execute(SubmoduleCommand::getInstance($this)->init($path));
684
        return $this;
685
    }
686 2
687
    /**
688 2
     * update submodules
689 2
     *
690 2
     * @param  bool   $recursive update recursively
691 2
     * @param  bool   $init      init before update
692 2
     * @param  bool   $force     force the checkout as part of update
693 2
     * @param  string $path      update only a specific submodule path
694 2
     *
695
     * @return Repository
696 2
     */
697
    public function updateSubmodule($recursive = false, $init = false, $force = false, $path = null)
698
    {
699
        $this->caller->execute(SubmoduleCommand::getInstance($this)->update($recursive, $init, $force, $path));
700
        return $this;
701
    }
702
703
    /**
704
     * Gets an array of Tag objects
705
     *
706
     * @throws \RuntimeException
707
     * @throws \Symfony\Component\Process\Exception\LogicException
708 27
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
709
     * @throws \Symfony\Component\Process\Exception\RuntimeException
710 27
     * @return array
711 27
     */
712 27
    public function getTags()
713 27
    {
714
        $tags = array();
715 5
        $this->caller->execute(TagCommand::getInstance($this)->listTags());
716
        foreach ($this->caller->getOutputLines() as $tagString) {
717 1
            if ($tagString != '') {
718
                $tags[] = new Tag($this, trim($tagString));
719
            }
720
        }
721
722
        return $tags;
723
    }
724
725
    /**
726
     * Return a tag object
727
     *
728 1
     * @param string $name The tag name
729
     *
730 1
     * @throws \RuntimeException
731 1
     * @throws \Symfony\Component\Process\Exception\RuntimeException
732 1
     * @return Tag|null
733 1
     */
734 1
    public function getTag($name)
735
    {
736
        $tagFinderOutput = $this->caller->execute(TagCommand::getInstance()->listTags())->getOutputLines(true);
737 1
        foreach ($tagFinderOutput as $line) {
738 1
            if ($line === $name) {
739
                return new Tag($this, $name);
740 1
            }
741 1
        }
742
743 1
        return null;
744
    }
745
746
    /**
747
     * Return the last created tag
748
     *
749
     * @throws \LogicException
750
     * @throws \RuntimeException
751
     * @throws \InvalidArgumentException
752
     * @return Tag|null
753
     */
754
    public function getLastTag()
755
    {
756 1
        $finder = Finder::create()
757
                  ->files()
758 1
                  ->in(sprintf('%s/.git/refs/tags', $this->path))
759 1
                  ->sortByChangedTime();
760
        if ($finder->count() == 0) {
761 1
            return null;
762 1
        }
763 1
        $files = iterator_to_array($finder->getIterator(), false);
764 1
        $files = array_reverse($files);
765
        /** @var $firstFile SplFileInfo */
766 1
        $firstFile = $files[0];
767
        $tagName = $firstFile->getFilename();
768 1
769
        return Tag::pick($this, $tagName);
770
    }
771
772
    /**
773
     * Try to get a branch or a tag by its name.
774
     *
775
     * @param string $name the reference name (a tag name or a branch name)
776
     *
777
     * @throws \RuntimeException
778
     * @throws \InvalidArgumentException
779 13
     * @throws \Symfony\Component\Process\Exception\RuntimeException
780
     * @return \GitElephant\Objects\Tag|\GitElephant\Objects\Branch|null
781 13
     */
782
    public function getBranchOrTag($name)
783 13
    {
784
        if (in_array($name, $this->getBranches(true))) {
785
            return new Branch($this, $name);
786
        }
787
        $tagFinderOutput = $this->caller->execute(TagCommand::getInstance($this)->listTags())->getOutputLines(true);
788
        foreach ($tagFinderOutput as $line) {
789
            if ($line === $name) {
790
                return new Tag($this, $name);
791
            }
792
        }
793
794
        return null;
795 3
    }
796
797 3
    /**
798
     * Return a Commit object
799 3
     *
800
     * @param string $ref The commit reference
801
     *
802
     * @throws \RuntimeException
803
     * @return Objects\Commit
804
     */
805
    public function getCommit($ref = 'HEAD')
806
    {
807
        $commit = Commit::pick($this, $ref);
808
809
        return $commit;
810
    }
811
812
    /**
813 20
     * count the commit to arrive to the given treeish
814
     *
815 20
     * @param string $start
816
     *
817
     * @throws \RuntimeException
818
     * @throws \Symfony\Component\Process\Exception\RuntimeException
819
     * @return int|void
820
     */
821
    public function countCommits($start = 'HEAD')
822
    {
823
        $commit = Commit::pick($this, $start);
824
825
        return $commit->count();
826
    }
827
828
    /**
829
     * Get a log for a ref
830
     *
831
     * @param string|TreeishInterface|array $ref         the treeish to check, as a string, as an object or as an array
832
     * @param string|Object                 $path        the physical path to the tree relative to the repository root
833
     * @param int|null                      $limit       limit to n entries
834
     * @param int|null                      $offset      skip n entries
835
     * @param boolean|false                 $firstParent skip commits brought in to branch by a merge
836
     *
837
     * @return \GitElephant\Objects\Log
838
     */
839
    public function getLog($ref = 'HEAD', $path = null, $limit = 10, $offset = null, $firstParent = false)
840
    {
841
        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 839 can also be of type object<GitElephant\Objects\Object> or string; however, GitElephant\Objects\Log::__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...
Bug introduced by
It seems like $offset defined by parameter $offset on line 839 can also be of type integer; however, GitElephant\Objects\Log::__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...
842
    }
843
844
    /**
845
     * Get a log for a range ref
846
     *
847
     * @param string        $refStart
848
     * @param string        $refEnd
849
     * @param string|Object $path        the physical path to the tree relative to the repository root
850
     * @param int|null      $limit       limit to n entries
851
     * @param int|null      $offset      skip n entries
852
     * @param boolean|false $firstParent skip commits brought in to branch by a merge
853
     *
854
     * @return \GitElephant\Objects\LogRange
855
     */
856
    public function getLogRange($refStart, $refEnd, $path = null, $limit = 10, $offset = null, $firstParent = false)
857
    {
858
        // Handle when clients provide bad start reference on branch creation
859 3
        if (preg_match('~^[0]+$~', $refStart)) {
860
            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 856 can also be of type object<GitElephant\Objects\Object> or string; however, GitElephant\Objects\Log::__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...
Bug introduced by
It seems like $offset defined by parameter $offset on line 856 can also be of type integer; however, GitElephant\Objects\Log::__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...
Bug Best Practice introduced by
The return type of return new \GitElephant\...$offset, $firstParent); (GitElephant\Objects\Log) is incompatible with the return type documented by GitElephant\Repository::getLogRange of type GitElephant\Objects\LogRange.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
861 3
        }
862
863 3
        // Handle when clients provide bad end reference on branch deletion
864
        if (preg_match('~^[0]+$~', $refEnd)) {
865
            $refEnd = $refStart;
866
        }
867
868
        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 856 can also be of type object<GitElephant\Objects\Object> 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...
Bug introduced by
It seems like $offset defined by parameter $offset on line 856 can also be of type integer; 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...
869
    }
870
871
    /**
872
     * Get a log for an object
873
     *
874
     * @param \GitElephant\Objects\Object             $obj    The Object instance
875
     * @param null|string|\GitElephant\Objects\Branch $branch The branch to read from
876
     * @param int                                     $limit  Limit to n entries
877
     * @param int|null                                $offset Skip n entries
878
     *
879 24
     * @throws \RuntimeException
880
     * @throws \Symfony\Component\Process\Exception\LogicException
881 24
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
882
     * @throws \Symfony\Component\Process\Exception\RuntimeException
883
     * @return \GitElephant\Objects\Log
884 24
     */
885
    public function getObjectLog(Object $obj, $branch = null, $limit = 1, $offset = null)
886 24
    {
887
        $command = LogCommand::getInstance($this)->showObjectLog($obj, $branch, $limit, $offset);
888
889
        return Log::createFromOutputLines($this, $this->caller->execute($command)->getOutputLines());
890
    }
891
892
    /**
893
     * Checkout a branch
894
     * This function change the state of the repository on the filesystem
895
     *
896
     * @param string|TreeishInterface $ref    the reference to checkout
897
     * @param bool                    $create like -b on the command line
898
     *
899
     * @throws \RuntimeException
900
     * @throws \Symfony\Component\Process\Exception\LogicException
901
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
902 15
     * @throws \Symfony\Component\Process\Exception\RuntimeException
903
     * @return Repository
904 15
     */
905 9
    public function checkout($ref, $create = false)
906 9
    {
907 9
        if ($create && is_null($this->getBranch($ref))) {
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 905 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...
908 9
            $this->createBranch($ref);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 905 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...
909 9
        }
910
        $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 905 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...
911 15
912
	    $this->flushCache();
913
        return $this;
914
    }
915
916
    /**
917
     * Retrieve an instance of Tree
918
     * Tree Object is Countable, Iterable and has ArrayAccess for easy manipulation
919
     *
920
     * @param string|TreeishInterface $ref  the treeish to check
921
     * @param string|Object           $path Object or null for root
922
     *
923
     * @throws \RuntimeException
924
     * @throws \Symfony\Component\Process\Exception\LogicException
925 2
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
926
     * @throws \Symfony\Component\Process\Exception\RuntimeException
927 2
     * @return Objects\Tree
928
     */
929
    public function getTree($ref = 'HEAD', $path = null)
930
    {
931
        if (is_string($path) && '' !== $path) {
932
            $outputLines = $this->getCaller()->execute(
933
                LsTreeCommand::getInstance($this)->tree($ref, $path)
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 929 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...
934
            )->getOutputLines(true);
935
            $path = TreeObject::createFromOutputLine($this, $outputLines[0]);
936
        }
937
938
        return new Tree($this, $ref, $path);
0 ignored issues
show
Bug introduced by
It seems like $ref defined by parameter $ref on line 929 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 929 can also be of type string; however, GitElephant\Objects\Tree::__construct() does only seem to accept object|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...
939
    }
940
941
    /**
942 2
     * Get a Diff object for a commit with its parent, by default the diff is between the current head and its parent
943
     *
944 2
     * @param \GitElephant\Objects\Commit|string      $commit1 A TreeishInterface instance
945
     * @param \GitElephant\Objects\Commit|string|null $commit2 A TreeishInterface instance
946 2
     * @param null|string|Object                      $path    The path to get the diff for or a Object instance
947
     *
948
     * @throws \RuntimeException
949
     * @throws \InvalidArgumentException
950
     * @return Objects\Diff\Diff
951
     */
952
    public function getDiff($commit1 = null, $commit2 = null, $path = null)
953
    {
954
        return Diff::create($this, $commit1, $commit2, $path);
0 ignored issues
show
Bug introduced by
It seems like $path defined by parameter $path on line 952 can also be of type object<GitElephant\Objects\Object>; 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...
955
    }
956
957
    /**
958
     * Clone a repository
959 7
     *
960
     * @param string $url the repository url (i.e. git://github.com/matteosister/GitElephant.git)
961 7
     * @param null   $to  where to clone the repo
962
     *
963 7
     * @throws \RuntimeException
964
     * @throws \Symfony\Component\Process\Exception\LogicException
965
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
966
     * @throws \Symfony\Component\Process\Exception\RuntimeException
967
     * @return Repository
968
     */
969
    public function cloneFrom($url, $to = null)
970
    {
971
        $this->caller->execute(CloneCommand::getInstance($this)->cloneUrl($url, $to));
972 1
973
        return $this;
974 1
    }
975
976
    /**
977
     * @param string $name remote name
978
     * @param string $url  remote url
979
     *
980
     * @throws \RuntimeException
981
     * @throws \Symfony\Component\Process\Exception\LogicException
982
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
983
     * @throws \Symfony\Component\Process\Exception\RuntimeException
984
     * @return Repository
985
     */
986
    public function addRemote($name, $url)
987
    {
988 1
        $this->caller->execute(RemoteCommand::getInstance($this)->add($name, $url));
989
990 1
        return $this;
991 1
    }
992 1
993 1
    /**
994 1
     * @param string $name         remote name
995 1
     * @param bool   $queryRemotes Fetch new information from remotes
996
     *
997 1
     * @return \GitElephant\Objects\Remote
998
     */
999
    public function getRemote($name, $queryRemotes = true)
1000
    {
1001
        return Remote::pick($this, $name, $queryRemotes);
1002
    }
1003
1004
    /**
1005
     * gets a list of remote objects
1006
     *
1007
     * @param bool $queryRemotes Fetch new information from remotes
1008
     *
1009
     * @throws \RuntimeException
1010
     * @throws \Symfony\Component\Process\Exception\LogicException
1011
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1012 1
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1013
     * @return array
1014 1
     */
1015 1
    public function getRemotes($queryRemotes = true)
1016 1
    {
1017 1
        $remoteNames = $this->caller->execute(RemoteCommand::getInstance($this)->show(null, $queryRemotes))
1018 1
          ->getOutputLines(true);
1019 1
        $remotes = array();
1020
        foreach ($remoteNames as $remoteName) {
1021
            $remotes[] = $this->getRemote($remoteName, $queryRemotes);
1022
        }
1023
1024
        return $remotes;
1025
    }
1026
1027
    /**
1028
     * Download objects and refs from another repository
1029
     *
1030
     * @param string $from
1031
     * @param string $ref
1032 2
     * @param array $args
1033
     * @param bool   $tags
1034 2
     *
1035 2
     * @throws \RuntimeException
1036
     * @throws \Symfony\Component\Process\Exception\LogicException
1037
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1038
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1039
     * @return string
1040
     */
1041
    public function fetch($from = null, $ref = null, $args = [], $tags = false)
1042
    {
1043
        $options = array();
1044
        if ($tags === true) {
1045
            $options = array('--tags');
1046
        }
1047 1
1048
        if(is_array($args)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after IF keyword; 0 found
Loading history...
1049 1
        	$options = array_merge($options, $args);
1050 1
        }
1051
1052
        $this->caller->execute(FetchCommand::getInstance($this)->fetch($from, $ref, $options));
1053
        $this->flushCache();
1054
1055
        return $this->caller->getOutput();
1056
    }
1057 2
1058
    /**
1059 2
     * Fetch from and merge with another repository or a local branch
1060 2
     *
1061 2
     * @param string $from
1062
     * @param string $ref
1063 2
     * @param bool   $rebase
1064
     * @throws \RuntimeException
1065
     * @throws \Symfony\Component\Process\Exception\LogicException
1066
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1067
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1068
     */
1069
    public function pull($from = null, $ref = null, $rebase = true)
1070
    {
1071
        $this->caller->execute(PullCommand::getInstance($this)->pull($from, $ref, $rebase));
1072
    }
1073
1074
    /**
1075
     * Push changes to remote repository
1076
     *
1077
     * @param string $to
1078 1
     * @param string $ref
1079
     * @throws \RuntimeException
1080 1
     * @throws \Symfony\Component\Process\Exception\LogicException
1081
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1082 1
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1083
     */
1084
    public function push($to = null, $ref = null)
1085
    {
1086
        $this->caller->execute(PushCommand::getInstance($this)->push($to, $ref));
1087
    }
1088
1089
    /**
1090
     * get the humanish name of the repository
1091
     *
1092
     * @return string
1093
     */
1094
    public function getHumanishName()
1095
    {
1096
        $name = substr($this->getPath(), strrpos($this->getPath(), '/') + 1);
1097
        $name = str_replace('.git', '.', $name);
1098
        $name = str_replace('.bundle', '.', $name);
1099
1100
        return $name;
1101
    }
1102
1103
    /**
1104
     * output a node content as an array of lines
1105
     *
1106
     * @param \GitElephant\Objects\Object                  $obj     The Object of type BLOB
1107
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1108
     *
1109 61
     * @throws \RuntimeException
1110
     * @throws \Symfony\Component\Process\Exception\LogicException
1111 61
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1112
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1113
     * @return array
1114
     */
1115
    public function outputContent(Object $obj, $treeish)
1116
    {
1117
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1118
1119 1
        return $this->caller->execute($command)->getOutputLines();
1120
    }
1121 1
1122
    /**
1123
     * output a node raw content
1124
     *
1125
     * @param \GitElephant\Objects\Object                  $obj     The Object of type BLOB
1126
     * @param \GitElephant\Objects\TreeishInterface|string $treeish A treeish object
1127
     *
1128
     * @throws \RuntimeException
1129 1
     * @throws \Symfony\Component\Process\Exception\LogicException
1130
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
1131 1
     * @throws \Symfony\Component\Process\Exception\RuntimeException
1132 1
     * @return string
1133
     */
1134
    public function outputRawContent(Object $obj, $treeish)
1135
    {
1136
        $command = CatFileCommand::getInstance($this)->content($obj, $treeish);
1137
1138
        return $this->caller->execute($command)->getRawOutput();
1139
    }
1140
1141
    /**
1142
     * Get the path
1143
     *
1144
     * @return string
1145
     */
1146
    public function getPath()
1147
    {
1148
        return $this->path;
1149 87
    }
1150
1151 87
    /**
1152
     * Get the repository name
1153
     *
1154
     * @return string
1155
     */
1156
    public function getName()
1157
    {
1158
        return $this->name;
1159 93
    }
1160
1161 93
    /**
1162
     * Set the repository name
1163
     *
1164
     * @param string $name the repository name
1165
     */
1166
    public function setName($name)
1167
    {
1168
        $this->name = $name;
1169
    }
1170 1
1171
    /**
1172 1
     * Caller setter
1173 1
     *
1174
     * @param \GitElephant\Command\Caller\Caller $caller the caller variable
1175
     */
1176
    public function setCaller($caller)
1177
    {
1178
        $this->caller = $caller;
1179
    }
1180 1
1181
    /**
1182 1
     * Caller getter
1183 1
     *
1184 1
     * @return \GitElephant\Command\Caller\Caller
1185 1
     */
1186
    public function getCaller()
1187
    {
1188
        return $this->caller;
1189
    }
1190
1191
    /**
1192 93
     * get global config list
1193
     *
1194 93
     * @return array Global config list
1195
     */
1196
    public function getGlobalConfigs()
1197
    {
1198
        return $this->globalConfigs;
1199
    }
1200
1201
    /**
1202
     * add a key/value pair to the global config list
1203 1
     *
1204
     * @param string $name  The config name
1205 1
     * @param string $value The config value
1206 1
     */
1207
    public function addGlobalConfig($name, $value)
1208
    {
1209
        $this->globalConfigs[$name] = $value;
1210
    }
1211
1212
    /**
1213 1
     * remove an element form the global config list, identified by key
1214
     *
1215 1
     * @param  string $name The config name
1216 1
     */
1217 1
    public function removeGlobalConfig($name)
1218 1
    {
1219
        if (isset($this->globalConfigs[$name])) {
1220
            unset($this->globalConfigs[$name]);
1221
        }
1222
    }
1223
1224
    /**
1225 93
     * get global options list
1226
     *
1227 93
     * @return array Global options list
1228
     */
1229
    public function getGlobalOptions()
1230
    {
1231
        return $this->globalOptions;
1232
    }
1233
1234
    /**
1235 1
     * add a key/value pair to the global option list
1236
     *
1237 1
     * @param string $name  The option name
1238 1
     * @param string $value The option value
1239 1
     */
1240 1
    public function addGlobalOption($name, $value)
1241
    {
1242
        $this->globalOptions[$name] = $value;
1243
    }
1244
1245
    /**
1246
     * remove an element form the global option list, identified by key
1247
     *
1248 1
     * @param  string $name The option name
1249
     */
1250 1
    public function removeGlobalOption($name)
1251 1
    {
1252 1
        if (isset($this->globalOptions[$name])) {
1253 1
            unset($this->globalOptions[$name]);
1254 1
        }
1255
    }
1256
1257
    /**
1258
     * get global command arguments list
1259
     *
1260
     * @return array Global command arguments list
1261
     */
1262
    public function getGlobalCommandArguments()
1263
    {
1264
        return $this->globalCommandArguments;
1265
    }
1266
1267
    /**
1268
     * add a value to the global command argument list
1269
     *
1270
     * @param string $value The command argument
1271
     */
1272
    public function addGlobalCommandArgument($value)
1273
    {
1274
        if (!in_array($value, $this->globalCommandArguments, true)) {
1275
            $this->globalCommandArguments[] = $value;
1276
        }
1277
    }
1278
1279
    /**
1280
     * remove an element form the global command argument list, identified by 
1281
     * value
1282
     *
1283
     * @param  string $value The command argument
1284
     */
1285
    public function removeGlobalCommandArgument($value)
1286
    {
1287
        if (in_array($value, $this->globalCommandArguments, true)) {
1288
            $index = array_search($value, $this->globalCommandArguments);
1289
            unset($this->globalCommandArguments[$index]);
1290
        }
1291
    }
1292
1293
	/**
1294
	 * Return the cache tag for this repository
1295
	 *
1296
	 * @return string
1297
	 */
1298
	private function getCacheTag() {
1299
1300
    	return 'repo_'.strtolower($this->getName());
1301
	}
1302
1303
	private function getCacheKey() {
1304
		$args = array_map('strtolower', debug_backtrace()[1]['args']);
1305
		return strtolower(debug_backtrace()[1]['function']).'_'.implode("", $args);
1306
	}
1307
1308
	/**
1309
	 * Flush the cache for this repository
1310
	 */
1311
    public function flushCache() {
1312
    	$cacheTag = $this->getCacheTag();
1313
    	Cache::tags($cacheTag)->flush();
1314
    }
1315
}
1316