Completed
Push — develop ( 5f7df1...9cb564 )
by Matteo
10s
created

Remote::parseOutputLines()   D

Complexity

Conditions 16
Paths 18

Size

Total Lines 48
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 16.004

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 48
ccs 39
cts 40
cp 0.975
rs 4.9765
cc 16
eloc 37
nc 18
nop 1
crap 16.004

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Objects;
21
22
use \GitElephant\Command\RemoteCommand;
23
use \GitElephant\Repository;
24
25
/**
26
 * Class Remote
27
 *
28
 * An object representing a git remote
29
 *
30
 * @package GitElephant\Objects
31
 * @author  David Neimeyer <[email protected]>
32
 */
33
class Remote
34
{
35
    /**
36
     * @var \GitElephant\Repository
37
     */
38
    private $repository;
39
40
    /**
41
     * remote name
42
     *
43
     * @var string
44
     */
45
    private $name;
46
47
    /**
48
     * fetch url of named remote
49
     * @var string
50
     */
51
    private $fetchURL = '';
52
53
    /**
54
     * push url of named remote
55
     * @var string
56
     */
57
    private $pushURL = '';
58
59
    /**
60
     * HEAD branch of named remote
61
     * @var string
62
     */
63
    private $remoteHEAD = null;
64
65
    /**
66
     * @var array
67
     */
68
    private $branches;
69
70
    /**
71
     * Class constructor
72
     *
73
     * @param \GitElephant\Repository $repository   repository instance
74
     * @param string                  $name         remote name
75
     * @param bool                    $queryRemotes Do not fetch new information from remotes
76
     *
77
     * @throws \RuntimeException
78
     * @throws \InvalidArgumentException
79
     * @throws \UnexpectedValueException
80
     * @return \GitElephant\Objects\Remote
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
81
     */
82 14
    public function __construct(Repository $repository, $name = null, $queryRemotes = true)
83
    {
84 14
        $this->repository = $repository;
85 14
        if ($name) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $name of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
86 3
            $this->name = trim($name);
87 3
            $this->createFromCommand($queryRemotes);
88 3
        }
89
90 14
        return $this;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
91
    }
92
93
    /**
94
     * Static constructor
95
     *
96
     * @param \GitElephant\Repository $repository   repository instance
97
     * @param string                  $name         remote name
98
     * @param bool                    $queryRemotes Fetch new information from remotes
99
     *
100
     * @return \GitElephant\Objects\Remote
101
     */
102 1
    public static function pick(Repository $repository, $name = null, $queryRemotes = true)
103
    {
104 1
        return new self($repository, $name, $queryRemotes);
105
    }
106
107
    /**
108
     * get output lines from git-remote --verbose
109
     *
110
     * @param RemoteCommand $remoteCmd Optionally provide RemoteCommand object
111
     *
112
     * @throws \RuntimeException
113
     * @throws \Symfony\Component\Process\Exception\LogicException
114
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
115
     * @throws \Symfony\Component\Process\Exception\RuntimeException
116
     * @return array
117
     */
118 2
    public function getVerboseOutput(RemoteCommand $remoteCmd = null)
119
    {
120 2
        if (!$remoteCmd) {
121 2
            $remoteCmd = RemoteCommand::getInstance($this->repository);
122 2
        }
123 2
        $command = $remoteCmd->verbose();
124
125 2
        return $this->repository->getCaller()->execute($command)->getOutputLines(true);
126
    }
127
128
    /**
129
     * get output lines from git-remote show [name]
130
     *
131
     * NOTE: for technical reasons $name is optional, however under normal
132
     * implementation it SHOULD be passed!
133
     *
134
     * @param string        $name         Name of remote to show details
135
     * @param RemoteCommand $remoteCmd    Optionally provide RemoteCommand object
136
     * @param bool          $queryRemotes Do not fetch new information from remotes
137
     *
138
     * @throws \RuntimeException
139
     * @throws \Symfony\Component\Process\Exception\LogicException
140
     * @throws \Symfony\Component\Process\Exception\InvalidArgumentException
141
     * @throws \Symfony\Component\Process\Exception\RuntimeException
142
     * @return array
143
     */
144 2
    public function getShowOutput($name = null, RemoteCommand $remoteCmd = null, $queryRemotes = true)
145
    {
146 2
        if (!$remoteCmd) {
147 2
            $remoteCmd = RemoteCommand::getInstance($this->repository);
148 2
        }
149 2
        $command = $remoteCmd->show($name, $queryRemotes);
150
151 2
        return $this->repository->getCaller()->execute($command)->getOutputLines(true);
152
    }
153
154
    /**
155
     * get/store the properties from git-remote command
156
     *
157
     * NOTE: the name property should be set if this is to do anything,
158
     * otherwise it's likely to throw
159
     *
160
     * @param bool $queryRemotes Do not fetch new information from remotes
161
     *
162
     * @throws \RuntimeException
163
     * @throws \UnexpectedValueException
164
     * @throws \InvalidArgumentException
165
     * @throws \Symfony\Component\Process\Exception\RuntimeException
166
     * @return \GitElephant\Objects\Remote
167
     */
168 3
    private function createFromCommand($queryRemotes = true)
169
    {
170 3
        $outputLines = $this->getVerboseOutput();
171 3
        $list = array();
172 3
        foreach ($outputLines as $line) {
173 3
            $matches = static::getMatches($line);
174 3
            if (isset($matches[1])) {
175 3
                $list[] = $matches[1];
176 3
            }
177 3
        }
178 3
        array_filter($list);
179 3
        if (in_array($this->name, $list)) {
180 3
            $remoteDetails = $this->getShowOutput($this->name, null, $queryRemotes);
181 3
            $this->parseOutputLines($remoteDetails);
182 3
        } else {
183
            throw new \InvalidArgumentException(sprintf('The %s remote doesn\'t exists', $this->name));
184
        }
185
186 3
        return $this;
187
    }
188
189
    /**
190
     * parse details from remote show
191
     *
192
     * @param array|string $remoteDetails Output lines for a remote show
193
     *
194
     * @throws \UnexpectedValueException
195
     */
196 8
    public function parseOutputLines(Array $remoteDetails)
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...
197
    {
198 8
        array_filter($remoteDetails);
199 8
        $name = array_shift($remoteDetails);
200 8
        $name = (is_string($name)) ? trim($name) : '';
201 8
        $name = $this->parseName($name);
202 8
        if (!$name) {
203
            throw new \UnexpectedValueException(sprintf('Invalid data provided for remote detail parsing'));
204
        }
205 8
        $this->name = $name;
206 8
        $fetchURLPattern = '/^Fetch\s+URL:\s*(.*)$/';
207 8
        $fetchURL = null;
208
209 8
        $pushURLPattern = '/^Push\s+URL:\s*(.*)$/';
210 8
        $pushURL = null;
211
212 8
        $remoteHEADPattern = '/^HEAD\s+branch:\s*(.*)$/';
213 8
        $remoteHEAD = null;
214
215 8
        $remoteBranchHeaderPattern = '/^Remote\s+branch(?:es)?:$/';
216 8
        $localBranchPullHeaderPattern = '/^Local\sbranch(?:es)?\sconfigured\sfor\s\'git\spull\'\:$/';
217 8
        $localRefPushHeaderPattern = '/^Local\sref(?:s)?\sconfigured\sfor\s\'git\spush\':$/';
218
        $groups = array(
219 8
            'remoteBranches'=>null,
220 8
            'localBranches'=>null,
221 8
            'localRefs'=>null,
222 8
        );
223
224 8
        foreach ($remoteDetails as $lineno => $line) {
225 8
            $line = trim($line);
226 8
            $matches = array();
227 8
            if (is_null($fetchURL) && preg_match($fetchURLPattern, $line, $matches)) {
228 8
                $this->fetchURL = $fetchURL = $matches[1];
229 8
            } elseif (is_null($pushURL) && preg_match($pushURLPattern, $line, $matches)) {
230 8
                $this->pushURL = $pushURL = $matches[1];
231 8
            } elseif (is_null($remoteHEAD) && preg_match($remoteHEADPattern, $line, $matches)) {
232 8
                $this->remoteHEAD = $remoteHEAD = $matches[1];
233 8
            } elseif (is_null($groups['remoteBranches']) && preg_match($remoteBranchHeaderPattern, $line, $matches)) {
234 7
                $groups['remoteBranches'] = $lineno;
235 7
            } elseif (is_null($groups['localBranches']) && preg_match($localBranchPullHeaderPattern, $line, $matches)) {
236 7
                $groups['localBranches'] = $lineno;
237 7
            } elseif (is_null($groups['localRefs']) && preg_match($localRefPushHeaderPattern, $line, $matches)) {
238 7
                $groups['localRefs'] = $lineno;
239 7
            }
240 8
        }
241
242 8
        $this->setBranches($this->aggregateBranchDetails($groups, $remoteDetails));
243 8
    }
244
245
    /**
246
     * provided with the start points of the branch details, parse out the
247
     * branch details and return a structured representation of said details
248
     *
249
     * @param array $groupLines    Associative array whose values are line numbers
250
     * are respective of the "group" detail present in $remoteDetails
251
     * @param array $remoteDetails Output of git-remote show [name]
252
     *
253
     * @return array
254
     */
255 8
    protected function aggregateBranchDetails($groupLines, $remoteDetails)
256
    {
257 8
        $configuredRefs = array();
258 8
        arsort($groupLines);
259 8
        foreach ($groupLines as $type => $lineno) {
260 8
            $configuredRefs[$type] = array_splice($remoteDetails, $lineno);
261 8
            array_shift($configuredRefs[$type]);
262 8
        }
263 8
        $configuredRefs['remoteBranches'] = (isset($configuredRefs['remoteBranches'])) ?
264 8
            $this->parseRemoteBranches($configuredRefs['remoteBranches']) : array();
265 8
        $configuredRefs['localBranches'] = (isset($configuredRefs['localBranches'])) ?
266 8
            $this->parseLocalPullBranches($configuredRefs['localBranches']) : array();
267 8
        $configuredRefs['localRefs'] = (isset($configuredRefs['localRefs'])) ?
268 8
            $this->parseLocalPushRefs($configuredRefs['localRefs']) : array();
269 8
        $aggBranches = array();
270 8
        foreach ($configuredRefs as $branches) {
271 8
            foreach ($branches as $branchName => $data) {
272 7
                if (!isset($aggBranches[$branchName])) {
273 7
                    $aggBranches[$branchName] = array();
274 7
                }
275 7
                $aggBranches[$branchName] = $aggBranches[$branchName] + $data;
276 8
            }
277 8
        }
278
279 8
        return $aggBranches;
280
    }
281
282
    /**
283
     * parse the details related to remote branch references
284
     *
285
     * @param array $lines
286
     *
287
     * @return array
288
     */
289 8
    public function parseRemoteBranches($lines)
290
    {
291 8
        $branches = array();
292 8
        $delimiter = ' ';
293 8
        foreach ($lines as $line) {
294 7
            $line = trim($line);
295 7
            $line = preg_replace('/\s+/', ' ', $line);
296 7
            $parts = explode($delimiter, $line);
297 7
            if (count($parts) > 1) {
298 7
                $branches[$parts[0]] = array( 'local_relationship' => $parts[1]);
299 7
            }
300 8
        }
301
302 8
        return $branches;
303
    }
304
305
    /**
306
     * parse the details related to local branches and the remotes that they
307
     * merge with
308
     *
309
     * @param array $lines
310
     *
311
     * @return array
312
     */
313 8
    public function parseLocalPullBranches($lines)
314
    {
315 8
        $branches = array();
316 8
        $delimiter = ' merges with remote ';
317 8
        foreach ($lines as $line) {
318 7
            $line = trim($line);
319 7
            $line = preg_replace('/\s+/', ' ', $line);
320 7
            $parts = explode($delimiter, $line);
321 7
            if (count($parts) > 1) {
322 7
                $branches[$parts[0]] = array('merges_with' => $parts[1]);
323 7
            }
324 8
        }
325
326 8
        return $branches;
327
    }
328
329
    /**
330
     * parse the details related to local branches and the remotes that they
331
     * push to
332
     *
333
     * @param array $lines
334
     *
335
     * @return array
336
     */
337 8
    public function parseLocalPushRefs($lines)
338
    {
339 8
        $branches = array();
340 8
        $delimiter = ' pushes to ';
341 8
        foreach ($lines as $line) {
342 8
            $line = trim($line);
343 8
            $line = preg_replace('/\s+/', ' ', $line);
344 8
            $parts = explode($delimiter, $line);
345 8
            if (count($parts) > 1) {
346 7
                $value = explode(' ', $parts[1], 2);
347 7
                $branches[$parts[0]] = array( 'pushes_to' => $value[0], 'local_state' => $value[1]);
348 7
            }
349 8
        }
350
351 8
        return $branches;
352
    }
353
354
    /**
355
     * parse remote name from git-remote show [name] output line
356
     *
357
     * @param string $line
358
     *
359
     * @return string remote name or blank if invalid
360
     */
361 8
    public function parseName($line)
362
    {
363 8
        $matches = array();
364 8
        $pattern = '/^\*\s+remote\s+(.*)$/';
365 8
        preg_match($pattern, trim($line), $matches);
366 8
        if (!isset($matches[1])) {
367
            return '';
368
        }
369
370 8
        return $matches[1];
371
    }
372
373
    /**
374
     * get the matches from an output line
375
     *
376
     * @param string $remoteString remote line output
377
     *
378
     * @throws \InvalidArgumentException
379
     * @return array
380
     */
381 3
    public static function getMatches($remoteString)
382
    {
383 3
        $matches = array();
384 3
        preg_match('/^(\S+)\s*(\S[^\( ]+)\s*\((.+)\)$/', trim($remoteString), $matches);
385 3
        if (!count($matches)) {
386
            throw new \InvalidArgumentException(sprintf('the remote string is not valid: %s', $remoteString));
387
        }
388
389 3
        return array_map('trim', $matches);
390
    }
391
392
    /**
393
     * toString magic method
394
     *
395
     * @return string the named remote
396
     */
397 1
    public function __toString()
398
    {
399 1
        return $this->getName();
400
    }
401
402
    /**
403
     * name setter
404
     *
405
     * @param string $name the remote name
406
     */
407 1
    public function setName($name)
408
    {
409 1
        $this->name = $name;
410 1
    }
411
412
    /**
413
     * name getter
414
     *
415
     * @return string
416
     */
417 4
    public function getName()
418
    {
419 4
        return $this->name;
420
    }
421
422
    /**
423
     * fetchURL getter
424
     *
425
     * @return string
426
     */
427 2
    public function getFetchURL()
428
    {
429 2
        return $this->fetchURL;
430
    }
431
432
    /**
433
     * fetchURL setter
434
     *
435
     * @param string $url the fetch url
436
     */
437 1
    public function setFetchURL($url)
438
    {
439 1
        $this->fetchURL = $url;
440 1
    }
441
442
    /**
443
     * pushURL getter
444
     *
445
     * @return string
446
     */
447 2
    public function getPushURL()
448
    {
449 2
        return $this->pushURL;
450
    }
451
452
    /**
453
     * pushURL setter
454
     *
455
     * @param string $url the push url
456
     */
457 1
    public function setPushURL($url)
458
    {
459 1
        $this->pushURL = $url;
460 1
    }
461
462
    /**
463
     * remote HEAD branch getter
464
     *
465
     * @return string
466
     */
467 2
    public function getRemoteHEAD()
468
    {
469 2
        return $this->remoteHEAD;
470
    }
471
472
    /**
473
     * remote HEAD branch setter
474
     *
475
     * @param string $branchName
476
     */
477 1
    public function setRemoteHEAD($branchName)
478
    {
479 1
        $this->remoteHEAD = $branchName;
480 1
    }
481
482
    /**
483
     * get structured representation of branches
484
     *
485
     * @return array
486
     */
487 1
    public function getBranches()
488
    {
489 1
        return $this->branches;
490
    }
491
492
    /**
493
     * set structured representation of branches
494
     *
495
     * @param array $branches
496
     */
497 8
    public function setBranches(Array $branches)
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...
498
    {
499 8
        $this->branches = $branches;
500 8
    }
501
}
502