GitRepository::extractRepositoryNameFromUrl()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.7
c 0
b 0
f 0
cc 3
nc 4
nop 1
1
<?php
2
    /**
3
     * Default implementation of IGit interface
4
     *
5
     * @author  Jan Pecha, <[email protected]>
6
     * @license New BSD License (BSD-3), see file license.md
7
     */
8
9
    namespace Cz\Git;
10
11
class GitRepository implements IGit
12
{
13
    /**
14
     * @var string 
15
     */
16
    protected $repository;
17
18
    /**
19
     * @var string|NULL  @internal 
20
     */
21
    protected $cwd;
22
23
24
    /**
25
     * @param  string
26
     * @throws GitException
27
     */
28
    public function __construct($repository)
29
    {
30
        if(basename($repository) === '.git') {
31
            $repository = dirname($repository);
32
        }
33
34
        $this->repository = realpath($repository);
35
36
        if($this->repository === false) {
37
            throw new GitException("Repository '$repository' not found.");
38
        }
39
    }
40
41
42
    /**
43
     * @return string
44
     */
45
    public function getRepositoryPath()
46
    {
47
        return $this->repository;
48
    }
49
50
51
    /**
52
     * Creates a tag.
53
     * `git tag <name>`
54
     *
55
     * @param  string
56
     * @param  array|NULL
57
     * @throws GitException
58
     * @return self
59
     */
60
    public function createTag($name, $options = null)
61
    {
62
        return $this->begin()
63
            ->run('git tag', $options, $name)
64
            ->end();
65
    }
66
67
68
    /**
69
     * Removes tag.
70
     * `git tag -d <name>`
71
     *
72
     * @param  string
73
     * @throws GitException
74
     * @return self
75
     */
76
    public function removeTag($name)
77
    {
78
        return $this->begin()
79
            ->run(
80
                'git tag', array(
81
                '-d' => $name,
82
                )
83
            )
84
            ->end();
85
    }
86
87
88
    /**
89
     * Renames tag.
90
     * `git tag <new> <old>`
91
     * `git tag -d <old>`
92
     *
93
     * @param  string
94
     * @param  string
95
     * @throws GitException
96
     * @return self
97
     */
98
    public function renameTag($oldName, $newName)
99
    {
100
        return $this->begin()
101
        // http://stackoverflow.com/a/1873932
102
        // create new as alias to old (`git tag NEW OLD`)
103
            ->run('git tag', $newName, $oldName)
104
        // delete old (`git tag -d OLD`)
105
            ->removeTag($oldName) // WARN! removeTag() calls end() method!!!
106
            ->end();
107
    }
108
109
110
    /**
111
     * Returns list of tags in repo.
112
     *
113
     * @return string[]|NULL  NULL => no tags
114
     * @throws GitException
115
     */
116
    public function getTags()
117
    {
118
        return $this->extractFromCommand('git tag', 'trim');
119
    }
120
121
122
    /**
123
     * Merges branches.
124
     * `git merge <options> <name>`
125
     *
126
     * @param  string
127
     * @param  array|NULL
128
     * @throws GitException
129
     * @return self
130
     */
131
    public function merge($branch, $options = null)
132
    {
133
        return $this->begin()
134
            ->run('git merge', $options, $branch)
135
            ->end();
136
    }
137
138
139
    /**
140
     * Creates new branch.
141
     * `git branch <name>`
142
     * (optionaly) `git checkout <name>`
143
     *
144
     * @param  string
145
     * @param  bool
146
     * @throws GitException
147
     * @return self
148
     */
149
    public function createBranch($name, $checkout = false)
150
    {
151
        $this->begin();
152
153
        // git branch $name
154
        $this->run('git branch', $name);
155
156
        if($checkout) {
157
            $this->checkout($name);
158
        }
159
160
        return $this->end();
161
    }
162
163
164
    /**
165
     * Removes branch.
166
     * `git branch -d <name>`
167
     *
168
     * @param  string
169
     * @throws GitException
170
     * @return self
171
     */
172
    public function removeBranch($name)
173
    {
174
        return $this->begin()
175
            ->run(
176
                'git branch', array(
177
                '-d' => $name,
178
                )
179
            )
180
            ->end();
181
    }
182
183
184
    /**
185
     * Gets name of current branch
186
     * `git branch` + magic
187
     *
188
     * @return string
189
     * @throws GitException
190
     */
191
    public function getCurrentBranchName()
192
    {
193
        try
194
        {
195
            $branch = $this->extractFromCommand(
196
                'git branch -a', function ($value) {
197
                    if(isset($value[0]) && $value[0] === '*') {
198
                        return trim(substr($value, 1));
199
                    }
200
201
                    return false;
202
                }
203
            );
204
205
            if(is_array($branch)) {
206
                  return $branch[0];
207
            }
208
        }
209
        catch(GitException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
210
        }
211
        throw new GitException('Getting current branch name failed.');
212
    }
213
214
215
    /**
216
     * Returns list of all (local & remote) branches in repo.
217
     *
218
     * @return string[]|NULL  NULL => no branches
219
     * @throws GitException
220
     */
221
    public function getBranches()
222
    {
223
        return $this->extractFromCommand(
224
            'git branch -a', function ($value) {
225
                return trim(substr($value, 1));
226
            }
227
        );
228
    }
229
230
231
    /**
232
     * Returns list of remote branches in repo.
233
     *
234
     * @return string[]|NULL  NULL => no branches
235
     * @throws GitException
236
     */
237
    public function getRemoteBranches()
238
    {
239
        return $this->extractFromCommand(
240
            'git branch -r', function ($value) {
241
                return trim(substr($value, 1));
242
            }
243
        );
244
    }
245
246
247
    /**
248
     * Returns list of local branches in repo.
249
     *
250
     * @return string[]|NULL  NULL => no branches
251
     * @throws GitException
252
     */
253
    public function getLocalBranches()
254
    {
255
        return $this->extractFromCommand(
256
            'git branch', function ($value) {
257
                return trim(substr($value, 1));
258
            }
259
        );
260
    }
261
262
263
    /**
264
     * Checkout branch.
265
     * `git checkout <branch>`
266
     *
267
     * @param  string
268
     * @throws GitException
269
     * @return self
270
     */
271
    public function checkout($name)
272
    {
273
        return $this->begin()
274
            ->run('git checkout', $name)
275
            ->end();
276
    }
277
278
279
    /**
280
     * Removes file(s).
281
     * `git rm <file>`
282
     *
283
     * @param  string|string[]
284
     * @throws GitException
285
     * @return self
286
     */
287
    public function removeFile($file)
288
    {
289
        if(!is_array($file)) {
290
            $file = func_get_args();
291
        }
292
293
        $this->begin();
294
295
        foreach($file as $item)
296
        {
297
            $this->run('git rm', $item, '-r');
298
        }
299
300
        return $this->end();
301
    }
302
303
304
    /**
305
     * Adds file(s).
306
     * `git add <file>`
307
     *
308
     * @param  string|string[]
309
     * @throws GitException
310
     * @return self
311
     */
312
    public function addFile($file)
313
    {
314
        if(!is_array($file)) {
315
            $file = func_get_args();
316
        }
317
318
        $this->begin();
319
320
        foreach($file as $item)
321
        {
322
            // make sure the given item exists
323
            // this can be a file or an directory, git supports both
324
            $path = self::isAbsolute($item) ? $item : ($this->getRepositoryPath() . DIRECTORY_SEPARATOR . $item);
325
326
            if (!file_exists($path)) {
327
                  throw new GitException("The path at '$item' does not represent a valid file.");
328
            }
329
330
            $this->run('git add', $item);
331
        }
332
333
        return $this->end();
334
    }
335
336
337
    /**
338
     * Adds all created, modified & removed files.
339
     * `git add --all`
340
     *
341
     * @throws GitException
342
     * @return self
343
     */
344
    public function addAllChanges()
345
    {
346
        return $this->begin()
347
            ->run('git add --all')
348
            ->end();
349
    }
350
351
352
    /**
353
     * Renames file(s).
354
     * `git mv <file>`
355
     *
356
     * @param  string|string[]  from: array('from' => 'to', ...) || (from, to)
357
     * @param  string|NULL
358
     * @throws GitException
359
     * @return self
360
     */
361
    public function renameFile($file, $to = null)
362
    {
363
        if(!is_array($file)) // rename(file, to);
364
        {
365
            $file = array(
366
            $file => $to,
367
            );
368
        }
369
370
        $this->begin();
371
372
        foreach($file as $from => $to)
373
        {
374
            $this->run('git mv', $from, $to);
375
        }
376
377
        return $this->end();
378
    }
379
380
381
    /**
382
     * Commits changes
383
     * `git commit <params> -m <message>`
384
     *
385
     * @param  string
386
     * @param  string[]  param => value
387
     * @throws GitException
388
     * @return self
389
     */
390
    public function commit($message, $params = null)
391
    {
392
        if(!is_array($params)) {
393
            $params = array();
394
        }
395
396
        return $this->begin()
397
            ->run(
398
                "git commit", $params, array(
399
                '-m' => $message,
400
                )
401
            )
402
            ->end();
403
    }
404
405
406
    /**
407
     * Returns last commit ID on current branch
408
     * `git log --pretty=format:"%H" -n 1`
409
     *
410
     * @return string|NULL
411
     * @throws GitException
412
     */
413
    public function getLastCommitId()
414
    {
415
        $this->begin();
416
        $lastLine = exec('git log --pretty=format:"%H" -n 1 2>&1');
417
        $this->end();
418
        if (preg_match('/^[0-9a-f]{40}$/i', $lastLine)) {
419
            return $lastLine;
420
        }
421
        return null;
422
    }
423
424
425
    /**
426
     * Exists changes?
427
     * `git status` + magic
428
     *
429
     * @return bool
430
     * @throws GitException
431
     */
432
    public function hasChanges()
433
    {
434
        // Make sure the `git status` gets a refreshed look at the working tree.
435
        $this->begin()
436
            ->run('git update-index -q --refresh')
437
            ->end();
438
439
        $output = $this->extractFromCommand('git status --porcelain');
440
        return !empty($output);
441
    }
442
443
444
    /**
445
     * @deprecated
446
     * @throws     GitException
447
     */
448
    public function isChanges()
449
    {
450
        return $this->hasChanges();
451
    }
452
453
454
    /**
455
     * Pull changes from a remote
456
     *
457
     * @param  string|NULL
458
     * @param  array|NULL
459
     * @return self
460
     * @throws GitException
461
     */
462 View Code Duplication
    public function pull($remote = null, array $params = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
463
    {
464
        if(!is_array($params)) {
465
            $params = array();
466
        }
467
468
        return $this->begin()
469
            ->run("git pull $remote", $params)
470
            ->end();
471
    }
472
473
474
    /**
475
     * Push changes to a remote
476
     *
477
     * @param  string|NULL
478
     * @param  array|NULL
479
     * @return self
480
     * @throws GitException
481
     */
482 View Code Duplication
    public function push($remote = null, array $params = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
483
    {
484
        if(!is_array($params)) {
485
            $params = array();
486
        }
487
488
        return $this->begin()
489
            ->run("git push $remote", $params)
490
            ->end();
491
    }
492
493
494
    /**
495
     * Run fetch command to get latest branches
496
     *
497
     * @param  string|NULL
498
     * @param  array|NULL
499
     * @return self
500
     * @throws GitException
501
     */
502 View Code Duplication
    public function fetch($remote = null, array $params = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
503
    {
504
        if(!is_array($params)) {
505
            $params = array();
506
        }
507
508
        return $this->begin()
509
            ->run("git fetch $remote", $params)
510
            ->end();
511
    }
512
513
514
    /**
515
     * Adds new remote repository
516
     *
517
     * @param  string
518
     * @param  string
519
     * @param  array|NULL
520
     * @return self
521
     * @throws GitException
522
     */
523
    public function addRemote($name, $url, array $params = null)
524
    {
525
        return $this->begin()
526
            ->run('git remote add', $params, $name, $url)
527
            ->end();
528
    }
529
530
531
    /**
532
     * Renames remote repository
533
     *
534
     * @param  string
535
     * @param  string
536
     * @return self
537
     * @throws GitException
538
     */
539
    public function renameRemote($oldName, $newName)
540
    {
541
        return $this->begin()
542
            ->run('git remote rename', $oldName, $newName)
543
            ->end();
544
    }
545
546
547
    /**
548
     * Removes remote repository
549
     *
550
     * @param  string
551
     * @return self
552
     * @throws GitException
553
     */
554
    public function removeRemote($name)
555
    {
556
        return $this->begin()
557
            ->run('git remote remove', $name)
558
            ->end();
559
    }
560
561
562
    /**
563
     * Changes remote repository URL
564
     *
565
     * @param  string
566
     * @param  string
567
     * @param  array|NULL
568
     * @return self
569
     * @throws GitException
570
     */
571
    public function setRemoteUrl($name, $url, array $params = null)
572
    {
573
        return $this->begin()
574
            ->run('git remote set-url', $params, $name, $url)
575
            ->end();
576
    }
577
578
579
    /**
580
     * @param  string|string[]
581
     * @return string[]  returns output
582
     * @throws GitException
583
     */
584
    public function execute($cmd)
585
    {
586
        if (!is_array($cmd)) {
587
            $cmd = array($cmd);
588
        }
589
590
        array_unshift($cmd, 'git');
591
        $cmd = self::processCommand($cmd);
592
593
        $this->begin();
594
        exec($cmd . ' 2>&1', $output, $ret);
595
        $this->end();
596
597
        if($ret !== 0) {
598
            throw new GitException("Command '$cmd' failed (exit-code $ret).", $ret);
599
        }
600
601
        return $output;
602
    }
603
604
605
    /**
606
     * @return self
607
     */
608
    protected function begin()
609
    {
610
        if($this->cwd === null) // TODO: good idea??
611
        {
612
            $this->cwd = getcwd();
613
            chdir($this->repository);
614
        }
615
616
        return $this;
617
    }
618
619
620
    /**
621
     * @return self
622
     */
623
    protected function end()
624
    {
625
        if(is_string($this->cwd)) {
626
            chdir($this->cwd);
627
        }
628
629
        $this->cwd = null;
630
        return $this;
631
    }
632
633
634
    /**
635
     * @param  string
636
     * @param  callback|NULL
637
     * @return string[]|NULL
638
     * @throws GitException
639
     */
640
    protected function extractFromCommand($cmd, $filter = null)
641
    {
642
        $output = array();
643
        $exitCode = null;
644
645
        $this->begin();
646
        exec("$cmd", $output, $exitCode);
647
        $this->end();
648
649
        if($exitCode !== 0 || !is_array($output)) {
650
            throw new GitException("Command $cmd failed.");
651
        }
652
653
        if($filter !== null) {
654
            $newArray = array();
655
656
            foreach($output as $line)
657
            {
658
                  $value = $filter($line);
659
660
                if($value === false) {
661
                    continue;
662
                }
663
664
                $newArray[] = $value;
665
            }
666
667
            $output = $newArray;
668
        }
669
670
        if(!isset($output[0])) // empty array
671
        {
672
            return null;
673
        }
674
675
            return $output;
676
    }
677
678
679
    /**
680
     * Runs command.
681
     *
682
     * @param  string|array
683
     * @return self
684
     * @throws GitException
685
     */
686
    protected function run($cmd/*, $options = NULL*/)
0 ignored issues
show
Unused Code introduced by
The parameter $cmd is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
687
    {
688
        $args = func_get_args();
689
        $cmd = self::processCommand($args);
690
        exec($cmd . ' 2>&1', $output, $ret);
691
692
        if($ret !== 0) {
693
            throw new GitException("Command '$cmd' failed (exit-code $ret).", $ret);
694
        }
695
696
        return $this;
697
    }
698
699
700
    protected static function processCommand(array $args)
701
    {
702
        $cmd = array();
703
704
        $programName = array_shift($args);
705
706
        foreach($args as $arg)
707
        {
708
            if(is_array($arg)) {
709
                foreach($arg as $key => $value)
710
                {
711
                    $_c = '';
712
713
                    if(is_string($key)) {
714
                        $_c = "$key ";
715
                    }
716
717
                    $cmd[] = $_c . escapeshellarg($value);
718
                }
719
            }
720
            elseif(is_scalar($arg) && !is_bool($arg)) {
721
                $cmd[] = escapeshellarg($arg);
722
            }
723
        }
724
725
        return "$programName " . implode(' ', $cmd);
726
    }
727
728
729
    /**
730
     * Init repo in directory
731
     *
732
     * @param  string
733
     * @param  array|NULL
734
     * @return self
735
     * @throws GitException
736
     */
737
    public static function init($directory, array $params = null)
738
    {
739
        if(is_dir("$directory/.git")) {
740
            throw new GitException("Repo already exists in $directory.");
741
        }
742
743
        if(!is_dir($directory) && !@mkdir($directory, 0777, true)) // intentionally @; not atomic; from Nette FW
744
        {
745
            throw new GitException("Unable to create directory '$directory'.");
746
        }
747
748
        $cwd = getcwd();
749
        chdir($directory);
750
        exec(
751
            self::processCommand(
752
                array(
753
                'git init',
754
                $params,
755
                $directory,
756
                )
757
            ), $output, $returnCode
758
        );
759
760
        if($returnCode !== 0) {
761
            throw new GitException("Git init failed (directory $directory).");
762
        }
763
764
        $repo = getcwd();
765
        chdir($cwd);
766
767
        return new static($repo);
768
    }
769
770
771
    /**
772
     * Clones GIT repository from $url into $directory
773
     *
774
     * @param  string
775
     * @param  string|NULL
776
     * @param  array|NULL
777
     * @return self
778
     * @throws GitException
779
     */
780
    public static function cloneRepository($url, $directory = null, array $params = null)
781
    {
782
        if($directory !== null && is_dir("$directory/.git")) {
783
            throw new GitException("Repo already exists in $directory.");
784
        }
785
786
        $cwd = getcwd();
787
788
        if($directory === null) {
789
            $directory = self::extractRepositoryNameFromUrl($url);
790
            $directory = "$cwd/$directory";
791
        }
792
        elseif(!self::isAbsolute($directory)) {
793
            $directory = "$cwd/$directory";
794
        }
795
796
        if ($params === null) {
797
            $params = '-q';
798
        }
799
800
            $descriptorspec = array(
801
                0 => array('pipe', 'r'), // stdout
802
                1 => array('pipe', 'w'), // stdin
803
                2 => array('pipe', 'w'), // stderr
804
            );
805
806
            $pipes = [];
807
            $command = self::processCommand(
808
                array(
809
                'git clone',
810
                $params,
811
                $url,
812
                $directory
813
                )
814
            );
815
        $process = proc_open($command, $descriptorspec, $pipes);
816
817
        if (!$process) {
818
            throw new GitException("Git clone failed (directory $directory).");
819
        }
820
821
        // Reset output and error
822
        $stdout = '';
823
        $stderr = '';
824
825
        while (true)
826
        {
827
            // Read standard output
828
            $output = fgets($pipes[0], 1024);
829
830
            if ($output) {
831
                  $stdout .= $output;
832
            }
833
834
            // Read error output
835
            $output_err = fgets($pipes[2], 1024);
836
837
            if ($output_err) {
838
                $stderr .= $output_err;
839
            }
840
841
            // We are done
842
            if ((feof($pipes[0]) OR $output === false) AND (feof($pipes[2]) OR $output_err === false)) {
843
                break;
844
            }
845
        }
846
847
        $returnCode = proc_close($process);
848
849
        if($returnCode !== 0) {
850
            throw new GitException("Git clone failed (directory $directory)." . ($stderr !== '' ? ("\n$stderr") : ''));
851
        }
852
853
            return new static($directory);
854
    }
855
856
857
    /**
858
     * @param  string
859
     * @param  array|NULL
860
     * @return bool
861
     */
862
    public static function isRemoteUrlReadable($url, array $refs = null)
863
    {
864
        $env = '';
0 ignored issues
show
Unused Code introduced by
$env is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
865
866
        if (DIRECTORY_SEPARATOR === '\\') { // Windows
867
            $env = 'set GIT_TERMINAL_PROMPT=0 &&';
868
869
        } else {
870
            $env = 'GIT_TERMINAL_PROMPT=0';
871
        }
872
873
        exec(
874
            self::processCommand(
875
                array(
876
                $env . ' git ls-remote',
877
                '--heads',
878
                '--quiet',
879
                '--exit-code',
880
                $url,
881
                $refs,
882
                )
883
            ) . ' 2>&1', $output, $returnCode
884
        );
885
886
        return $returnCode === 0;
887
    }
888
889
890
    /**
891
     * @param  string  /path/to/repo.git | host.xz:foo/.git | ...
892
     * @return string  repo | foo | ...
893
     */
894
    public static function extractRepositoryNameFromUrl($url)
895
    {
896
        // /path/to/repo.git => repo
897
        // host.xz:foo/.git => foo
898
        $directory = rtrim($url, '/');
899
        if(substr($directory, -5) === '/.git') {
900
            $directory = substr($directory, 0, -5);
901
        }
902
903
        $directory = basename($directory, '.git');
904
905
        if(($pos = strrpos($directory, ':')) !== false) {
906
            $directory = substr($directory, $pos + 1);
907
        }
908
909
        return $directory;
910
    }
911
912
913
    /**
914
     * Is path absolute?
915
     * Method from Nette\Utils\FileSystem
916
     *
917
     * @link   https://github.com/nette/nette/blob/master/Nette/Utils/FileSystem.php
918
     * @return bool
919
     */
920
    public static function isAbsolute($path)
921
    {
922
        return (bool) preg_match('#[/\\\\]|[a-zA-Z]:[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
923
    }
924
925
926
    /**
927
     * Returns commit message from specific commit
928
     * `git log -1 --format={%s|%B} )--pretty=format:'%H' -n 1`
929
     *
930
     * @param  string  commit ID
931
     * @param  bool    use %s instead of %B if TRUE
932
     * @return string
933
     * @throws GitException
934
     */
935
    public function getCommitMessage($commit, $oneline = false)
936
    {
937
        $this->begin();
938
        exec('git log -1 --format=' . ($oneline ? '%s' : '%B') . ' ' . $commit . ' 2>&1', $message);
939
        $this->end();
940
        return implode(PHP_EOL, $message);
941
    }
942
943
944
    /**
945
     * Returns array of commit metadata from specific commit
946
     * `git show --raw <sha1>`
947
     *
948
     * @param  string  commit ID
949
     * @return array
950
     * @throws GitException
951
     */
952
    public function getCommitData($commit)
953
    {
954
        $message = $this->getCommitMessage($commit);
955
        $subject = $this->getCommitMessage($commit, true);
956
957
        $this->begin();
958
        exec('git show --raw ' . $commit . ' 2>&1', $output);
959
        $this->end();
960
        $data = array(
961
        'commit' => $commit,
962
        'subject' => $subject,
963
        'message' => $message,
964
        'author' => null,
965
        'committer' => null,
966
        'date' => null,
967
        );
968
969
        // git show is a porcelain command and output format may changes
970
        // in future git release or custom config.
971
        foreach ($output as $index => $info) {
0 ignored issues
show
Bug introduced by
The expression $output of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
972
            if (preg_match('`Author: *(.*)`', $info, $author)) {
973
                  $data['author'] = trim($author[1]);
974
                  unset($output[$index]);
975
            }
976
977 View Code Duplication
            if (preg_match('`Commit: *(.*)`', $info, $committer)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
978
                $data['committer'] = trim($committer[1]);
979
                unset($output[$index]);
980
            }
981
982 View Code Duplication
            if (preg_match('`Date: *(.*)`', $info, $date)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
983
                $data['date'] = trim($date[1]);
984
                unset($output[$index]);
985
            }
986
        }
987
988
        return $data;
989
    }
990
991
}
992