Completed
Push — master ( 85c97a...a6409c )
by Matteo
16:08 queued 07:17
created

src/GitElephant/Repository.php (10 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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