Git::execute()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the egabor/composer-release-plugin package.
5
 *
6
 * (c) Gábor Egyed <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace egabor\Composer\ReleasePlugin\Util;
13
14
use Composer\IO\IOInterface;
15
use Composer\Util\ProcessExecutor;
16
use egabor\Composer\ReleasePlugin\Exception\ProcessFailedException;
17
use Symfony\Component\Process\ExecutableFinder;
18
19
class Git
20
{
21
    const STATUS_UP_TO_DATE = 0;
22
    const STATUS_NEED_TO_PULL = 1;
23
    const STATUS_NEED_TO_PUSH = 2;
24
    const STATUS_DIVERGED = 3;
25
26
    private $processExecutor;
27
    private $gitBinPath;
28
    private $branches;
29
    private $currentBranch;
30
    private $version;
31
    private $workingDirectory;
32
33 4
    public function __construct(ProcessExecutor $processExecutor, $gitBinPath, $workingDirectory = null)
34
    {
35 4
        $this->processExecutor = $processExecutor;
36 4
        $this->gitBinPath = $gitBinPath;
37 4
        if (null === $workingDirectory && false !== getcwd()) {
38 4
            $workingDirectory = realpath(getcwd());
39
        }
40
41 4
        $this->workingDirectory = $workingDirectory;
42
43
        try {
44 4
            $this->getVersion();
45 1
        } catch (ProcessFailedException $e) {
46 1
            throw new \RuntimeException('git was not found, please check that it is installed and in the "PATH" env variable.');
47
        }
48 3
    }
49
50
    public static function create(IOInterface $io, $gitBinPath = 'git')
51
    {
52
        $finder = new ExecutableFinder();
53
54
        return new self(new ProcessExecutor($io), $finder->find('git', $gitBinPath));
55
    }
56
57 2
    public function getLatestReachableTag($branch = 'master')
58
    {
59 2
        return $this->execute(sprintf('describe %s --first-parent --tags --abbrev=0', ProcessExecutor::escape($branch)));
60
    }
61
62 4
    public function getVersion()
63
    {
64 4
        if (null === $this->version) {
65 4
            $output = $this->execute('--version');
66 3
            $this->version = '';
67 3
            if (preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
68 3
                $this->version = $matches[1];
69
            }
70
        }
71
72 3
        return $this->version;
73
    }
74
75 1
    public function tag($name, $message)
76
    {
77 1
        $this->execute(sprintf('tag -m %s %s', ProcessExecutor::escape($message), ProcessExecutor::escape($name)));
78 1
    }
79
80
    public function push()
81
    {
82
        $this->execute('push');
83
    }
84
85
    public function pushTags()
86
    {
87
        $this->execute('push --tags');
88
    }
89
90
    public function fetch()
91
    {
92
        $this->execute('fetch --all');
93
    }
94
95 1
    public function countCommitsSince($rev)
96
    {
97 1
        return (int) $this->execute(sprintf('rev-list --count %s..HEAD', ProcessExecutor::escape($rev)));
98
    }
99
100
    public function getRemoteStatus()
101
    {
102
        // https://stackoverflow.com/a/3278427
103
        $local = $this->execute('rev-parse @{0}');
104
        $remote = $this->execute('rev-parse @{u}');
105
106
        try {
107
            $base = $this->execute('merge-base @{0} @{u}');
108
        } catch (\RuntimeException $e) {
109
            $base = null;
110
        }
111
112
        if ($local === $remote) {
113
            return self::STATUS_UP_TO_DATE;
114
        }
115
116
        if ($local === $base) {
117
            return self::STATUS_NEED_TO_PULL;
118
        }
119
120
        if ($remote === $base) {
121
            return self::STATUS_NEED_TO_PUSH;
122
        }
123
124
        return self::STATUS_DIVERGED;
125
    }
126
127
    public function hasUpstream()
128
    {
129
        try {
130
            // https://stackoverflow.com/a/9753364
131
            $this->execute('rev-parse --abbrev-ref --symbolic-full-name @{u}');
132
        } catch (\RuntimeException $e) {
133
            return false;
134
        }
135
136
        return true;
137
    }
138
139
    public function getBranches()
140
    {
141
        if (null === $this->branches) {
142
            $branches = [];
143
            $output = $this->execute('branch --no-color --no-abbrev -v');
144
            foreach ($this->processExecutor->splitLines($output) as $branch) {
145
                if (!$branch || preg_match('{^ *[^/]+/HEAD }', $branch)) {
146
                    continue;
147
                }
148
149
                if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) {
150
                    $branches[$match[1]] = $match[2];
151
                }
152
            }
153
154
            $this->branches = $branches;
155
        }
156
157
        return $this->branches;
158
    }
159
160
    public function getCurrentBranch()
161
    {
162
        if (null === $this->currentBranch) {
163
            $output = $this->execute('branch --no-color');
164
            $this->currentBranch = '';
165
            $branches = $this->processExecutor->splitLines($output);
166
            foreach ($branches as $branch) {
167
                if ($branch && preg_match('{^\* +(\S+)}', $branch, $match)) {
168
                    $this->currentBranch = $match[1];
169
                    break;
170
                }
171
            }
172
        }
173
174
        return $this->currentBranch;
175
    }
176
177
    public function setWorkingDirectory($workingDirectory)
178
    {
179
        $this->workingDirectory = $workingDirectory;
180
    }
181
182 6
    private function execute($command)
183
    {
184 6
        $output = '';
185 6
        if (0 !== $exitCode = $this->processExecutor->execute($cmd = "{$this->gitBinPath} $command", $output, $this->workingDirectory)) {
186 2
            $message = trim($this->processExecutor->getErrorOutput());
187
188 2
            throw new ProcessFailedException(sprintf('Command "%s" returned with exit code "%d" and message "%s".', $cmd, $exitCode, $message));
189
        }
190
191 5
        return trim($output);
192
    }
193
}
194