Completed
Push — master ( f505db...682a0d )
by Stefan
9s
created

Repository::getDiff()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5.0031

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 19
cts 20
cp 0.95
rs 8.439
c 0
b 0
f 0
cc 5
eloc 16
nc 4
nop 1
crap 5.0031
1
<?php
2
/*
3
 * Copyright (C) 2017 by TEQneers GmbH & Co. KG
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy
6
 * of this software and associated documentation files (the "Software"), to deal
7
 * in the Software without restriction, including without limitation the rights
8
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
 * copies of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in
13
 * all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
 * THE SOFTWARE.
22
 */
23
24
/**
25
 * Git Stream Wrapper for PHP
26
 *
27
 * @category   TQ
28
 * @package    TQ_VCS
29
 * @subpackage SVN
30
 * @copyright  Copyright (C) 2017 by TEQneers GmbH & Co. KG
31
 */
32
33
namespace TQ\Svn\Repository;
34
use TQ\Vcs\FileSystem;
35
use TQ\Vcs\Repository\AbstractRepository;
36
use TQ\Svn\Cli\Binary;
37
use TQ\Vcs\Cli\CallResult;
38
39
/**
40
 * Provides access to a SVN repository
41
 *
42
 * @uses       TQ\Svn\Cli\Binary
43
 * @author     Stefan Gehrig <gehrigteqneers.de>
44
 * @category   TQ
45
 * @package    TQ_VCS
46
 * @subpackage SVN
47
 * @copyright  Copyright (C) 2017 by TEQneers GmbH & Co. KG
48
 */
49
class Repository extends AbstractRepository
50
{
51
    /**
52
     * The SVN binary
53
     *
54
     * @var Binary
55
     */
56
    protected $svn;
57
58
    /**
59
     * Opens a SVN repository on the file system
60
     *
61
     * @param   string               $repositoryPath        The full path to the repository
62
     * @param   Binary|string|null   $svn                   The SVN binary
63
     * @return  Repository
64
     * @throws  \RuntimeException                       If the path cannot be created
65
     * @throws  \InvalidArgumentException               If the path is not valid or if it's not a valid SVN repository
66
     */
67 94
    public static function open($repositoryPath, $svn = null)
68
    {
69 94
        $svn = Binary::ensure($svn);
70
71 94
        if (!is_string($repositoryPath)) {
72
            throw new \InvalidArgumentException(sprintf(
73
                '"%s" is not a valid path', $repositoryPath
74
            ));
75
        }
76
77 94
        $repositoryRoot = self::findRepositoryRoot($svn, $repositoryPath);
78
79 94
        if ($repositoryRoot === null) {
80 2
            throw new \InvalidArgumentException(sprintf(
81 2
                '"%s" is not a valid SVN repository', $repositoryPath
82 2
            ));
83
        }
84
85 92
        return new static($repositoryRoot, $svn);
86
    }
87
88
    /**
89
     * Tries to find the root directory for a given repository path
90
     *
91
     * @param   Binary      $svn        The SVN binary
92
     * @param   string      $path       The file system path
93
     * @return  string|null             NULL if the root cannot be found, the root path otherwise
94
     */
95
    public static function findRepositoryRoot(Binary $svn, $path)
0 ignored issues
show
Unused Code introduced by
The parameter $svn 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...
96
    {
97 94
        $hasSvnDir  = function($path) {
98 94
            $svnDir = $path.'/'.'.svn';
99 94
            return file_exists($svnDir) && is_dir($svnDir);
100 94
        };
101
102 94
        $pathWithSvnDir = FileSystem::bubble($path, $hasSvnDir);
103
104 94
        $root       = $pathWithSvnDir;
105 94
        $parentDir  = dirname($pathWithSvnDir);
106 94
        while ($hasSvnDir($parentDir) && strlen($root) > 1) {
107 11
            $root      = dirname($root);
108 11
            $parentDir = dirname($parentDir);
109 11
        }
110 94
        return $root;
111
    }
112
113
    /**
114
     * Creates a new repository instance - use {@see open()} instead
115
     *
116
     * @param   string     $repositoryPath
117
     * @param   Binary     $svn
118
     */
119 92
    protected function __construct($repositoryPath, Binary $svn)
120
    {
121 92
        $this->svn   = $svn;
122 92
        parent::__construct($repositoryPath);
123 92
    }
124
125
    /**
126
     * Returns the SVN binary
127
     *
128
     * @return  Binary
129
     */
130 73
    public function getSvn()
131
    {
132 73
        return $this->svn;
133
    }
134
135
    /**
136
     * Returns the current commit hash
137
     *
138
     * @return  string
139
     * @throws  \RuntimeException
140
     */
141 52
    public function getCurrentCommit()
142
    {
143
        /** @var $result CallResult */
144 52
        $result = $this->getSvn()->{'info'}($this->getRepositoryPath(), array(
145 52
            '--xml',
146
            '--revision' => 'HEAD'
147 52
        ));
148 52
        $result->assertSuccess(sprintf('Cannot get info for "%s"', $this->getRepositoryPath()));
149
150 52
        $xml    = simplexml_load_string($result->getStdOut());
151 52
        if (!$xml) {
152
            throw new \RuntimeException(sprintf('Cannot read info XML for "%s"', $this->getRepositoryPath()));
153
        }
154
155 52
        $commit = $xml->xpath('/info/entry/commit[@revision]');
156 52
        if (empty($commit)) {
157
            throw new \RuntimeException(sprintf('Cannot read info XML for "%s"', $this->getRepositoryPath()));
158
        }
159
160 52
        $commit = reset($commit);
161 52
        return (string)($commit['revision']);
162
    }
163
164
    /**
165
     * Commits the currently staged changes into the repository
166
     *
167
     * @param   string       $commitMsg         The commit message
168
     * @param   array|null   $file              Restrict commit to the given files or NULL to commit all staged changes
169
     * @param   array        $extraArgs         Allow the user to pass extra args eg array('-i')
170
     * @param   string|null  $author            The author
171
     */
172 53
    public function commit($commitMsg, array $file = null, $author = null, array $extraArgs = array())
173
    {
174 53
        $author = $author ?: $this->getAuthor();
175
        $args   = array(
176
            '--message'   => $commitMsg
177 53
        );
178 53
        if ($author !== null) {
179 5
            $args['--username']  = $author;
180 5
        }
181 53
        if ($file !== null) {
182 31
            $args[] = '--';
183 31
            $args   = array_merge($args, $this->resolveLocalGlobPath($file));
184 31
        }
185
186
        /** @var $result CallResult */
187 53
        $result = $this->getSvn()->{'commit'}($this->getRepositoryPath(), $args);
188 53
        $result->assertSuccess(sprintf('Cannot commit to "%s"', $this->getRepositoryPath()));
189 53
    }
190
191
    /**
192
     * Resets the working directory and/or the staging area and discards all changes
193
     *
194
     * @throws  \RuntimeException
195
     */
196 2
    public function reset()
197
    {
198
        /** @var $result CallResult */
199 2
        $result = $this->getSvn()->{'revert'}($this->getRepositoryPath(), array(
200 2
            '--recursive',
201 2
            '--',
202
            '.'
203 2
        ));
204 2
        $result->assertSuccess(sprintf('Cannot reset "%s"', $this->getRepositoryPath()));
205
206 2
        $status = $this->getStatus();
207 2
        foreach ($status as $item) {
208 2
            $file   = $this->resolveFullPath($item['file']);
209 2
            if (@unlink($file) !== true || $item['status'] !== 'unversioned') {
210
                throw new \RuntimeException('Cannot delete file "'.$item['file'].'"');
211
            }
212 2
        }
213 2
    }
214
215
    /**
216
     * Adds one or more files to the staging area
217
     *
218
     * @param   array   $file       The file(s) to be added or NULL to add all new and/or changed files to the staging area
219
     * @param   boolean $force
220
     */
221 40
    public function add(array $file = null, $force = false)
222
    {
223 40
        $args   = array();
224 40
        if ($force) {
225
            $args[]  = '--force';
226
        }
227 40
        if ($file !== null) {
228 37
            $status = $this->getStatus();
229 37
            if (empty($status)) {
230
                return;
231
            }
232
233 37
            $files  = $this->resolveLocalGlobPath($file);
234 37
            foreach ($this->getStatus() as $status) {
235 37
                if (   $status['status'] != 'unversioned'
236 37
                    && in_array($status['file'], $files)
237 37
                ) {
238 8
                    array_splice($files, array_search($status['file'], $files), 1);
239 8
                }
240 37
            }
241
242 37
            if (empty($files)) {
243 8
                return;
244
            }
245
246 32
            $args[] = '--parents';
247 32
            $args[] = '--';
248 32
            $args   = array_merge($args, $files);
249 32
        } else {
250 3
            $toAdd      = array();
251 3
            $toRemove   = array();
252 3
            foreach ($this->getStatus() as $status) {
253 2
                if ($status['status'] == 'missing') {
254 1
                    $toRemove[] = $this->resolveLocalPath($status['file']);
255 2
                } else if ($status['status'] == 'unversioned') {
256 2
                    $toAdd[] = $this->resolveLocalPath($status['file']);
257 2
                }
258 3
            }
259
260 3
            if (!empty($toRemove)) {
261 1
                $this->remove($toRemove, false, $force);
262 1
            }
263 3
            if (empty($toAdd)) {
264 1
                return;
265
            }
266
267 2
            $args['--depth']    = 'infinity';
268 2
            $args[]             = '--';
269 2
            $args               = array_merge($args, $toAdd);
270
        }
271
272
        /** @var $result CallResult */
273 34
        $result = $this->getSvn()->{'add'}($this->getRepositoryPath(), $args);
274 34
        $result->assertSuccess(sprintf('Cannot add "%s" to "%s"',
275 34
            ($file !== null) ? implode(', ', $file) : '*', $this->getRepositoryPath()
276 34
        ));
277 34
    }
278
279
    /**
280
     * Removes one or more files from the repository but does not commit the changes
281
     *
282
     * @param   array   $file           The file(s) to be removed
283
     * @param   boolean $recursive      True to recursively remove subdirectories
284
     * @param   boolean $force          True to continue even though SVN reports a possible conflict
285
     */
286 11
    public function remove(array $file, $recursive = false, $force = false)
0 ignored issues
show
Unused Code introduced by
The parameter $recursive 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...
287
    {
288 11
        $args   = array();
289 11
        if ($force) {
290
            $args[] = '--force';
291
        }
292 11
        $args[] = '--';
293 11
        $args   = array_merge($args, $this->resolveLocalGlobPath($file));
294
295
        /** @var $result CallResult */
296 11
        $result = $this->getSvn()->{'delete'}($this->getRepositoryPath(), $args);
297 11
        $result->assertSuccess(sprintf('Cannot remove "%s" from "%s"',
298 11
            implode(', ', $file), $this->getRepositoryPath()
299 11
        ));
300 11
    }
301
302
    /**
303
     * Renames a file but does not commit the changes
304
     *
305
     * @param   string  $fromPath   The source path
306
     * @param   string  $toPath     The destination path
307
     * @param   boolean $force      True to continue even though SVN reports a possible conflict
308
     */
309 8
    public function move($fromPath, $toPath, $force = false)
310
    {
311 8
        $args   = array();
312 8
        if ($force) {
313
            $args[] = '--force';
314
        }
315 8
        $args[] = $this->resolveLocalPath($fromPath);
316 8
        $args[] = $this->resolveLocalPath($toPath);
317
318
        /** @var $result CallResult */
319 8
        $result = $this->getSvn()->{'move'}($this->getRepositoryPath(), $args);
320 8
        $result->assertSuccess(sprintf('Cannot move "%s" to "%s" in "%s"',
321 8
            $fromPath, $toPath, $this->getRepositoryPath()
322 8
        ));
323 8
    }
324
325
    /**
326
     * Writes data to a file and commit the changes immediately
327
     *
328
     * @param   string          $path           The file path
329
     * @param   string|array    $data           The data to write to the file
330
     * @param   string|null     $commitMsg      The commit message used when committing the changes
331
     * @param   integer|null    $fileMode       The mode for creating the file
332
     * @param   integer|null    $dirMode        The mode for creating the intermediate directories
333
     * @param   boolean         $recursive      Create intermediate directories recursively if required
334
     * @param   string|null     $author         The author
335
     * @return  string                          The current commit hash
336
     * @throws  \RuntimeException               If the file could not be written
337
     */
338 21
    public function writeFile($path, $data, $commitMsg = null, $fileMode = null,
339
        $dirMode = null, $recursive = true, $author = null
340
    ) {
341 21
        $file       = $this->resolveFullPath($path);
342
343 21
        $fileMode   = $fileMode ?: $this->getFileCreationMode();
344 21
        $dirMode    = $dirMode ?: $this->getDirectoryCreationMode();
345
346 21
        $directory  = dirname($file);
347 21
        if (!file_exists($directory) && !mkdir($directory, (int)$dirMode, $recursive)) {
348
            throw new \RuntimeException(sprintf('Cannot create "%s"', $directory));
349 21
        } else if (!file_exists($file)) {
350 21
            if (!touch($file)) {
351
                throw new \RuntimeException(sprintf('Cannot create "%s"', $file));
352
            }
353 21
            if (!chmod($file, (int)$fileMode)) {
354
                throw new \RuntimeException(sprintf('Cannot chmod "%s" to %d', $file, (int)$fileMode));
355
            }
356 21
        }
357
358 21
        if (file_put_contents($file, $data) === false) {
359
            throw new \RuntimeException(sprintf('Cannot write to "%s"', $file));
360
        }
361
362 21
        $this->add(array($file));
363
364 21
        if ($commitMsg === null) {
365 21
            $commitMsg  = sprintf('%s created or changed file "%s"', __CLASS__, $path);
366 21
        }
367
368 21
        $this->commit($commitMsg, null, $author);
369
370 21
        return $this->getCurrentCommit();
371
    }
372
373
    /**
374
     * Writes data to a file and commit the changes immediately
375
     *
376
     * @param   string          $path           The directory path
377
     * @param   string|null     $commitMsg      The commit message used when committing the changes
378
     * @param   integer|null    $dirMode        The mode for creating the intermediate directories
379
     * @param   boolean         $recursive      Create intermediate directories recursively if required
380
     * @param   string|null     $author         The author
381
     * @return  string                          The current commit hash
382
     * @throws  \RuntimeException               If the directory could not be created
383
     */
384 4
    public function createDirectory($path, $commitMsg = null, $dirMode = null, $recursive = true, $author = null) {
385 4
        $directory  = $this->resolveFullPath($path);
386 4
        $dirMode    = $dirMode ?: $this->getDirectoryCreationMode();
387
388 4
        if (file_exists($directory) || !mkdir($directory, (int)$dirMode, $recursive)) {
389
            throw new \RuntimeException(sprintf('Cannot create "%s"', $directory));
390
        }
391
392 3
        $this->add(array($this->resolveLocalPath($directory)));
393
394 3
        if ($commitMsg === null) {
395 2
            $commitMsg  = sprintf('%s created directory "%s"', __CLASS__, $path);
396 2
        }
397
398 3
        $this->commit($commitMsg, null, $author);
399
400 3
        return $this->getCurrentCommit();
401
    }
402
403
    /**
404
     * Removes a file and commit the changes immediately
405
     *
406
     * @param   string          $path           The file path
407
     * @param   string|null     $commitMsg      The commit message used when committing the changes
408
     * @param   boolean         $recursive      True to recursively remove subdirectories
409
     * @param   boolean         $force          True to continue even though SVN reports a possible conflict
410
     * @param   string|null     $author         The author
411
     * @return  string                          The current commit hash
412
     */
413 10
    public function removeFile($path, $commitMsg = null, $recursive = false, $force = false, $author = null)
414
    {
415 10
        $this->remove(array($path), $recursive, $force);
416
417 10
        if ($commitMsg === null) {
418 7
            $commitMsg  = sprintf('%s deleted file "%s"', __CLASS__, $path);
419 7
        }
420
421 10
        $this->commit($commitMsg, array($path), $author);
422
423 10
        return $this->getCurrentCommit();
424
    }
425
426
    /**
427
     * Renames a file and commit the changes immediately
428
     *
429
     * @param   string          $fromPath       The source path
430
     * @param   string          $toPath         The destination path
431
     * @param   string|null     $commitMsg      The commit message used when committing the changes
432
     * @param   boolean         $force          True to continue even though SVN reports a possible conflict
433
     * @param   string|null     $author         The author
434
     * @return  string                          The current commit hash
435
     */
436 8
    public function renameFile($fromPath, $toPath, $commitMsg = null, $force = false, $author = null)
437
    {
438 8
        $this->move($fromPath, $toPath, $force);
439
440 8
        if ($commitMsg === null) {
441 7
            $commitMsg  = sprintf('%s renamed/moved file "%s" to "%s"', __CLASS__, $fromPath, $toPath);
442 7
        }
443
444 8
        $this->commit($commitMsg, array($fromPath, $toPath), $author);
445
446 8
        return $this->getCurrentCommit();
447
    }
448
449
    /**
450
     * Returns the current repository log
451
     *
452
     * @param   integer|null    $limit      The maximum number of log entries returned
453
     * @param   integer|null    $skip       Number of log entries that are skipped from the beginning
454
     * @return  array
455
     * @throws  \RuntimeException
456
     */
457 2
    public function getLog($limit = null, $skip = null)
458
    {
459
        $arguments  = array(
460 2
            '--xml',
461
            '--revision'    => 'HEAD:0'
462 2
        );
463
464
465 2
        $skip   = ($skip === null) ? 0 : (int)$skip;
466 2
        if ($limit !== null) {
467 2
            $arguments['--limit']    = (int)($limit + $skip);
468 2
        }
469
470
        /** @var $result CallResult */
471 2
        $result = $this->getSvn()->{'log'}($this->getRepositoryPath(), $arguments);
472 2
        $result->assertSuccess(sprintf('Cannot retrieve log from "%s"',
473 2
            $this->getRepositoryPath()
474 2
        ));
475
476 2
        $xml    = simplexml_load_string($result->getStdOut());
477 2
        if (!$xml) {
478
            throw new \RuntimeException(sprintf('Cannot read log XML for "%s"', $this->getRepositoryPath()));
479
        }
480 2
        $logEntries = new \ArrayIterator($xml->xpath('/log/logentry'));
481
482 2
        if ($limit !== null) {
483 2
            $logEntries = new \LimitIterator($logEntries, $skip, $limit);
484 2
        }
485
486 2
        $log = array();
487 2
        foreach ($logEntries as $item) {
488 2
            $log[]   = array(
489 2
                (string)$item['revision'],
490 2
                (string)$item->author,
491 2
                (string)$item->date,
492 2
                (string)$item->msg
493 2
            );
494 2
        }
495 2
        return $log;
496
    }
497
498
    /**
499
     * Returns a string containing information about the given commit
500
     *
501
     * @param  string  $hash       The commit ref
502
     * @return  string
503
     */
504 40
    public function showCommit($hash)
505
    {
506
        /** @var $result CallResult */
507 40
        $result = $this->getSvn()->{'log'}($this->getRepositoryPath(), array(
508 40
            '-v',
509
            '-r' => $hash
510 40
        ));
511 40
        $result->assertSuccess(sprintf('Cannot retrieve commit "%s" from "%s"',
512 40
            $hash, $this->getRepositoryPath()
513 40
        ));
514
515 40
        return $result->getStdOut();
516
    }
517
518
    /**
519
     * Returns the content of a file at a given version
520
     *
521
     * @param   string  $file       The path to the file
522
     * @param   string  $ref        The version ref
523
     * @return  string
524
     */
525 3
    public function showFile($file, $ref = 'HEAD')
526
    {
527
        /** @var $result CallResult */
528 3
        $result = $this->getSvn()->{'cat'}($this->getRepositoryPath(), array(
529 3
            '--revision'    => $ref,
530
            $file
531 3
        ));
532 3
        $result->assertSuccess(sprintf('Cannot show "%s" at "%s" from "%s"',
533 3
            $file, $ref, $this->getRepositoryPath()
534 3
        ));
535
536 3
        return $result->getStdOut();
537
    }
538
539
    /**
540
     * Returns information about an object at a given version
541
     *
542
     * The information returned is an array with the following structure
543
     * array(
544
     *      'type'  => blob|tree|commit,
545
     *      'mode'  => 0040000 for a tree, 0100000 for a blob, 0 otherwise,
546
     *      'size'  => the size
547
     * )
548
     *
549
     * @param   string  $path       The path to the object
550
     * @param   string  $ref        The version ref
551
     * @return  array               The object info
552
     * @throws  \RuntimeException
553
     */
554 4
    public function getObjectInfo($path, $ref = 'HEAD')
555
    {
556
        /** @var $result CallResult */
557 4
        $result = $this->getSvn()->{'info'}($this->getRepositoryPath(), array(
558 4
            '--xml',
559 4
            '--revision' => $ref,
560 4
            $this->resolveLocalPath($path)
561 4
        ));
562 4
        $result->assertSuccess(sprintf('Cannot get info for "%s" at "%s" from "%s"',
563 4
            $path, $ref, $this->getRepositoryPath()
564 4
        ));
565
566 4
        $xml    = simplexml_load_string($result->getStdOut());
567 4
        if (!$xml) {
568
            throw new \RuntimeException(sprintf('Cannot read info XML for "%s" at "%s" from "%s"',
569
                $path, $ref, $this->getRepositoryPath()
570
            ));
571
        }
572
573 4
        $entry = $xml->xpath('/info/entry');
574 4
        if (count($entry) !== 1) {
575
            throw new \RuntimeException(sprintf('Cannot read info XML for "%s" at "%s" from "%s"',
576
                $path, $ref, $this->getRepositoryPath()
577
            ));
578
        }
579 4
        $entry  = reset($entry);
580 4
        $mode   = 0;
581 4
        switch ((string)$entry['kind']) {
582 4
            case 'dir':
583 2
                $mode   |= 0040000;
584 2
                break;
585 3
            case 'file':
586 3
                $mode   |= 0100000;
587 3
                break;
588
        }
589
        return array(
590 4
            'type'  => (string)$entry['kind'],
591 4
            'mode'  => (int)$mode,
592
            'size'  => 0
593 4
        );
594
    }
595
596
    /**
597
     * List the directory at a given version
598
     *
599
     * @param   string  $directory      The path ot the directory
600
     * @param   string  $ref            The version ref
601
     * @return  array
602
     * @throws  \RuntimeException
603
     */
604 12
    public function listDirectory($directory = '.', $ref = 'HEAD')
605
    {
606 12
        $directory  = FileSystem::normalizeDirectorySeparator($directory);
607 12
        $directory  = rtrim($directory, '/').'/';
608
609
        $args   = array(
610 12
            '--xml',
611 12
            '--revision' => $ref,
612 12
            $this->resolveLocalPath($directory)
613 12
        );
614
615
        /** @var $result CallResult */
616 12
        $result = $this->getSvn()->{'list'}($this->getRepositoryPath(), $args);
617 12
        $result->assertSuccess(sprintf('Cannot list directory "%s" at "%s" from "%s"',
618 12
            $directory, $ref, $this->getRepositoryPath()
619 12
        ));
620
621 12
        $xml    = simplexml_load_string($result->getStdOut());
622 12
        if (!$xml) {
623
            throw new \RuntimeException(sprintf('Cannot read list XML for "%s" at "%s" from "%s"',
624
                $directory, $ref, $this->getRepositoryPath()
625
            ));
626
        }
627
628 12
        $list = array();
629 12
        foreach ($xml->xpath('/lists/list/entry') as $item) {
630 12
            $list[]   = (string)$item->name;
631 12
        }
632 12
        return $list;
633
    }
634
635
    /**
636
     * Returns the current status of the working directory
637
     *
638
     * The returned array structure is
639
     *      array(
640
     *          'file'      => '...',
641
     *          'status'    => '...'
642
     *      )
643
     *
644
     * @return  array
645
     * @throws  \RuntimeException
646
     */
647 52
    public function getStatus()
648
    {
649
        /** @var $result CallResult */
650 52
        $result = $this->getSvn()->{'status'}($this->getRepositoryPath(), array(
651
            '--xml'
652 52
        ));
653 52
        $result->assertSuccess(
654 52
            sprintf('Cannot retrieve status from "%s"', $this->getRepositoryPath())
655 52
        );
656
657 52
        $xml    = simplexml_load_string($result->getStdOut());
658 52
        if (!$xml) {
659
            throw new \RuntimeException(sprintf('Cannot read status XML for "%s"', $this->getRepositoryPath()));
660
        }
661
662 52
        $status = array();
663 52
        foreach ($xml->xpath('/status/target/entry') as $entry) {
664 41
            $status[]   = array(
665 41
                'file'      => (string)$entry['path'],
666 41
                'status'    => (string)$entry->{'wc-status'}['item']
667 41
            );
668 52
        }
669 52
        return $status;
670
    }
671
672
    /**
673
     * Returns the diff of a file
674
     *
675
     * @param   array  $files       The path to the file
676
     * @return  string[]
677
     */
678 1
    public function getDiff(array $files = null)
679
    {
680 1
        $diffs  = array();
681
682 1
        if (is_null($files)) {
683 1
            $status = $this->getStatus();
684
685 1
            foreach ($status as $entry) {
686 1
                if ($entry['status'] !== 'modified') {
687
			continue;
688
		}
689
690 1
                $files[] = $entry['file'];
691 1
            }
692
693 1
            asort($files);
694 1
        }
695
696 1
        $files = array_map(array($this, 'resolveLocalPath'), $files);
697
698 1
        foreach ($files as $file) {
699 1
            $result = $this->getSvn()->{'diff'}($this->getRepositoryPath(), $file);
700 1
            $result->assertSuccess(sprintf('Cannot show diff for "%s" from "%s"',
701 1
                $file, $this->getRepositoryPath()
702 1
            ));
703
704 1
            $diffs[$file] = $result->getStdOut();
705 1
        }
706
707 1
        return $diffs;
708
    }
709
710
    /**
711
     * Returns true if there are uncommitted changes in the working directory and/or the staging area
712
     *
713
     * @return  boolean
714
     */
715 31
    public function isDirty()
716
    {
717 31
        $status = $this->getStatus();
718 31
        return !empty($status);
719
    }
720
721
    /**
722
     * Resolves an absolute path containing glob wildcards into a path relative to the repository path
723
     *
724
     * @param   array       $files      The list of files
725
     * @return  array
726
     */
727 53
    protected function resolveLocalGlobPath(array $files)
728
    {
729 53
        $absoluteFiles  = $this->resolveFullPath($files);
730 53
        $expandedFiles  = array();
731 53
        foreach ($absoluteFiles as $absoluteFile) {
0 ignored issues
show
Bug introduced by
The expression $absoluteFiles of type array|string 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...
732 53
            $globResult     = glob($absoluteFile);
733 53
            if (   empty($globResult)
734 53
                && stripos($absoluteFile, '*') === false
735 53
                && !file_exists($absoluteFile)
736 53
            ) {
737 15
                $expandedFiles[]    = $absoluteFile;
738 15
            } else {
739 52
                $expandedFiles  = array_merge($expandedFiles, $globResult);
740
            }
741 53
        }
742 53
        return $this->resolveLocalPath($expandedFiles);
743
    }
744
}
745
746