GitCommitCommand::stageFiles()   C
last analyzed

Complexity

Conditions 17
Paths 28

Size

Total Lines 62
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
eloc 29
c 1
b 0
f 0
nc 28
nop 1
dl 0
loc 62
rs 5.2166

How to fix   Long Method    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
/*
4
 * This file is part of the GitCommandBundle package.
5
 *
6
 * (c) Paul Schweppe <[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 VersionControl\GitCommandBundle\GitCommands\Command;
13
14
use RuntimeException;
15
use VersionControl\GitCommandBundle\Entity\GitFile;
16
use VersionControl\GitCommandBundle\GitCommands\Exception\RunGitCommandException;
17
18
/**
19
 * Description of GitFilesCommand.
20
 *
21
 * @author Paul Schweppe <[email protected]>
22
 */
23
class GitCommitCommand extends AbstractGitCommand
24
{
25
    /**
26
     * @var string
27
     */
28
    private $statusHash;
29
30
    /**
31
     * Stage files for commit.
32
     * In the short-format, the status of each path is shown as
33
     * XY PATH1 -> PATH2
34
     * where PATH1 is the path in the HEAD, and the ` -> PATH2` part is shown only when PATH1 corresponds to a
35
     * different path in the index/worktree (i.e. the file is renamed). The XY is a two-letter status code.
36
     *
37
     * The fields (including the ->) are separated from each other by a single space. If a filename contains whitespace
38
     * or other nonprintable characters, that field will be quoted in the manner of a C string literal: surrounded by
39
     * ASCII double quote (34) characters, and with interior special characters backslash-escaped.
40
     * For paths with merge conflicts, X and Y show the modification states of each side of the merge. For paths that
41
     * do not have merge conflicts, X shows the status of the index, and Y shows the status of the work tree. For
42
     * untracked paths, XY are ??. Other status codes can be interpreted as follows:
43
     * ' ' = unmodified
44
     * M = modified
45
     * A = added
46
     * D = deleted
47
     * R = renamed
48
     * C = copied
49
     * U = updated but unmerged
50
     *
51
     * Ignored files are not listed, unless --ignored option is in effect, in which case XY are !!.
52
     *
53
     * X          Y     Meaning
54
     * -------------------------------------------------
55
     * [MD]   not updated
56
     * M        [ MD]   updated in index
57
     * A        [ MD]   added to index
58
     * D         [ M]   deleted from index
59
     * R        [ MD]   renamed in index
60
     * C        [ MD]   copied in index
61
     * [MARC]           index and work tree matches
62
     * [ MARC]     M    work tree changed since index
63
     * [ MARC]     D    deleted in work tree
64
     * -------------------------------------------------
65
     * D           D    unmerged, both deleted
66
     * A           U    unmerged, added by us
67
     * U           D    unmerged, deleted by them
68
     * U           A    unmerged, added by them
69
     * D           U    unmerged, deleted by us
70
     * A           A    unmerged, both added
71
     * U           U    unmerged, both modified
72
     * -------------------------------------------------
73
     * ?           ?    untracked
74
     * !           !    ignored
75
     * -------------------------------------------------
76
     * If -b is used the short-format status is preceded by a line
77
     *
78
     * @TODO: No Support for copy yet
79
     *
80
     * @param array $files
81
     *
82
     * @throws RunGitCommandException
83
     * @throws RuntimeException
84
     */
85
    public function stageFiles(array $files): void
86
    {
87
        $gitFiles = $this->getFilesToCommit();
88
89
        //Validated that this status is same as previous
90
        $deleteFiles = array();
91
        $addFiles = array();
92
93
        $flippedFiles = array_flip($files);
94
95
        foreach ($gitFiles as $fileEntity) {
96
            if ($fileEntity->getWorkTreeStatus() === '!' || !isset($flippedFiles[$fileEntity->getPath1()])) {
97
                continue;
98
            }
99
100
            if ($fileEntity->getWorkTreeStatus() === 'D'
101
                && ($fileEntity->getIndexStatus() === ' '
102
                    || $fileEntity->getIndexStatus() === 'M'
103
                    || $fileEntity->getIndexStatus() === 'A')
104
            ) {
105
                //Delete files
106
                //[ MA]     D    deleted in work tree
107
                $deleteFiles[] = escapeshellarg($fileEntity->getPath1());
108
                continue;
109
            }
110
111
            if ($fileEntity->getIndexStatus() === 'R' && ($fileEntity->getWorkTreeStatus() === 'D')) {
112
                //Rename delete
113
                //[R]     D    deleted in work tree
114
                //$deleteFiles[] = escapeshellarg($fileEntity->getPath1());
115
                $deleteFiles[] = escapeshellarg($fileEntity->getPath2());
116
                continue;
117
            }
118
119
            if ($fileEntity->getIndexStatus() === 'R'
120
                && ($fileEntity->getWorkTreeStatus() === 'M'
121
                    || $fileEntity->getWorkTreeStatus() === 'A'
122
                    || $fileEntity->getWorkTreeStatus() === ' ')
123
            ) {
124
                //Rename ADD
125
                //[R]     [ M]
126
                //$deleteFiles[] = escapeshellarg($fileEntity->getPath1());
127
                $addFiles[] = escapeshellarg($fileEntity->getPath2());
128
                continue;
129
            }
130
131
            if ($fileEntity->getWorkTreeStatus() === ' ') {
132
                //[MARC]           index and work tree matches
133
                //Do Nothing
134
                continue;
135
            }
136
137
            $addFiles[] = escapeshellarg($fileEntity->getPath1());
138
        }
139
140
        //Run the commands once for add and delete
141
        if (count($deleteFiles) > 0) {
142
            $this->command->runCommand('git rm ' . implode(' ', $deleteFiles));
143
        }
144
145
        if (count($addFiles) > 0) {
146
            $this->command->runCommand('git add ' . implode(' ', $addFiles));
147
        }
148
    }
149
150
    /**
151
     * Stages the file to be committed.
152
     * Currently supports adding and removing file.
153
     *
154
     * @TODO Make it more efficent
155
     *
156
     * @param string $file path to file to commit
157
     *
158
     * @throws RuntimeException
159
     * @throws RunGitCommandException
160
     */
161
    public function stageFile($file): void
162
    {
163
        $this->stageFiles(array($file));
164
    }
165
166
    /**
167
     * Shortcut to stage all (new, modified, deleted) files.
168
     *
169
     * @return string Command response
170
     * @throws RunGitCommandException
171
     * @throws RuntimeException
172
     */
173
    public function stageAll(): string
174
    {
175
        return $this->command->runCommand('git add -A');
176
    }
177
178
    /**
179
     * Commits any file that was been staged.
180
     *
181
     * @param string $message
182
     * @param string $author name<email>
183
     *
184
     * @return string response
185
     * @throws RunGitCommandException
186
     * @throws RuntimeException
187
     */
188
    public function commit($message, $author): string
189
    {
190
        return $this->command->runCommand(
191
            $this->initGitCommand() . ' commit -m '
192
            . escapeshellarg($message) . ' --author=' . escapeshellarg($author)
193
        );
194
    }
195
196
    /**
197
     * Gets all files that need to be commited.
198
     *
199
     * @return array Array of GitFile objects
200
     * @throws RuntimeException
201
     * @throws RunGitCommandException
202
     */
203
    public function getFilesToCommit(): array
204
    {
205
        $statusData = $this->getStatus();
206
        $this->statusHash = hash('md5', $statusData);
207
208
        return $this->processStatus($statusData);
209
    }
210
211
    /**
212
     * Git status command
213
     * Response:
214
     *  D feedback.html
215
     *  ?? time-selectors/work.html.
216
     *
217
     * @return string Command Response
218
     * @throws RunGitCommandException
219
     * @throws RuntimeException
220
     */
221
    public function getStatus(): string
222
    {
223
        return $this->command->runCommand('git status -u --porcelain', true, false);
224
    }
225
226
    /**
227
     * Process the git status data into GitFile objects.
228
     *
229
     * @param string $statusData
230
     *
231
     * @return array Array of GitFile objects
232
     */
233
    protected function processStatus($statusData): array
234
    {
235
        $lines = $this->splitOnNewLine($statusData, false);
236
237
        if (!is_array($lines) || count($lines) === 0) {
0 ignored issues
show
introduced by
The condition is_array($lines) is always true.
Loading history...
238
            return [];
239
        }
240
241
        $files = [];
242
        foreach ($lines as $line) {
243
            if (trim($line)) {
244
                $files[] = new GitFile($line, $this->command->getGitPath());
245
            }
246
        }
247
248
        return $files;
249
    }
250
251
    /**
252
     * Get hash of git status.
253
     *
254
     * @return string hash
255
     * @throws RuntimeException
256
     * @throws RunGitCommandException
257
     */
258
    public function getStatusHash(): string
259
    {
260
        if (!$this->statusHash) {
261
            $stausData = $this->getStatus();
262
            $this->statusHash = hash('md5', $stausData);
263
        }
264
265
        return $this->statusHash;
266
    }
267
268
    /**
269
     * Counts the number of files not commited.
270
     *
271
     * @return int
272
     * @throws RuntimeException
273
     */
274
    public function countStatus(): int
275
    {
276
        $total = 0;
277
        $command = 'git status -u -s';
278
279
        try {
280
            $response = $this->command->runCommand($command);
281
            $lines = $this->splitOnNewLine($response, false);
282
            $total = count($lines);
283
        } catch (RunGitCommandException $e) {
284
            //continue
285
        }
286
287
        return $total;
288
    }
289
}
290