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\BranchCommand; |
23
|
|
|
use \GitElephant\Command\MainCommand; |
24
|
|
|
use \GitElephant\Command\RevListCommand; |
25
|
|
|
use \GitElephant\Command\RevParseCommand; |
26
|
|
|
use \GitElephant\Command\ShowCommand; |
27
|
|
|
use \GitElephant\Objects\Commit\Message; |
28
|
|
|
use \GitElephant\Repository; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* The Commit object represent a commit |
32
|
|
|
* |
33
|
|
|
* @author Matteo Giachino <[email protected]> |
34
|
|
|
*/ |
35
|
|
|
class Commit implements TreeishInterface, \Countable |
36
|
|
|
{ |
37
|
|
|
/** |
38
|
|
|
* @var \GitElephant\Repository |
39
|
|
|
*/ |
40
|
|
|
private $repository; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
private $ref; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* sha |
49
|
|
|
* |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
private $sha; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* tree |
56
|
|
|
* |
57
|
|
|
* @var string |
58
|
|
|
*/ |
59
|
|
|
private $tree; |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* the commit parents |
63
|
|
|
* |
64
|
|
|
* @var array |
65
|
|
|
*/ |
66
|
|
|
private $parents; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* the Author instance for author |
70
|
|
|
* |
71
|
|
|
* @var \GitElephant\Objects\Author |
72
|
|
|
*/ |
73
|
|
|
private $author; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* the Author instance for committer |
77
|
|
|
* |
78
|
|
|
* @var \GitElephant\Objects\Author |
79
|
|
|
*/ |
80
|
|
|
private $committer; |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* the Message instance |
84
|
|
|
* |
85
|
|
|
* @var \GitElephant\Objects\Commit\Message |
86
|
|
|
*/ |
87
|
|
|
private $message; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* the date for author |
91
|
|
|
* |
92
|
|
|
* @var \DateTime |
93
|
|
|
*/ |
94
|
|
|
private $datetimeAuthor; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* the date for committer |
98
|
|
|
* |
99
|
|
|
* @var \Datetime |
100
|
|
|
*/ |
101
|
|
|
private $datetimeCommitter; |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Class constructor |
105
|
|
|
* |
106
|
|
|
* @param \GitElephant\Repository $repository the repository |
107
|
|
|
* @param string $treeish a treeish reference |
108
|
|
|
*/ |
109
|
35 |
|
private function __construct(Repository $repository, $treeish = 'HEAD') |
110
|
|
|
{ |
111
|
35 |
|
$this->repository = $repository; |
112
|
35 |
|
$this->ref = $treeish; |
113
|
35 |
|
$this->parents = array(); |
114
|
35 |
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* factory method to create a commit |
118
|
|
|
* |
119
|
|
|
* @param Repository $repository repository instance |
120
|
|
|
* @param string $message commit message |
121
|
|
|
* @param bool $stageAll automatically stage the dirty working tree. Alternatively call stage() on the repo |
122
|
|
|
* @param string|Author $author override the author for this commit |
123
|
|
|
* |
124
|
|
|
* @throws \RuntimeException |
125
|
|
|
* @throws \Symfony\Component\Process\Exception\LogicException |
126
|
|
|
* @throws \InvalidArgumentException |
127
|
|
|
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException |
128
|
|
|
* @throws \Symfony\Component\Process\Exception\RuntimeException |
129
|
|
|
* @return Commit |
130
|
|
|
*/ |
131
|
3 |
|
public static function create(Repository $repository, $message, $stageAll = false, $author = null) |
132
|
|
|
{ |
133
|
3 |
|
$repository->getCaller()->execute(MainCommand::getInstance($repository)->commit($message, $stageAll, $author)); |
134
|
|
|
|
135
|
3 |
|
return $repository->getCommit(); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* pick an existing commit |
140
|
|
|
* |
141
|
|
|
* @param Repository $repository repository |
142
|
|
|
* @param TreeishInterface|string $treeish treeish |
143
|
|
|
* |
144
|
|
|
* @throws \RuntimeException |
145
|
|
|
* @throws \Symfony\Component\Process\Exception\RuntimeException |
146
|
|
|
* @return Commit |
147
|
|
|
*/ |
148
|
16 |
|
public static function pick(Repository $repository, $treeish = null) |
149
|
|
|
{ |
150
|
16 |
|
$commit = new self($repository, $treeish); |
|
|
|
|
151
|
16 |
|
$commit->createFromCommand(); |
152
|
|
|
|
153
|
16 |
|
return $commit; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* static generator to generate a single commit from output of command.show service |
158
|
|
|
* |
159
|
|
|
* @param \GitElephant\Repository $repository repository |
160
|
|
|
* @param array $outputLines output lines |
161
|
|
|
* |
162
|
|
|
* @return Commit |
163
|
|
|
*/ |
164
|
20 |
|
public static function createFromOutputLines(Repository $repository, $outputLines) |
165
|
|
|
{ |
166
|
20 |
|
$commit = new self($repository); |
167
|
20 |
|
$commit->parseOutputLines($outputLines); |
168
|
|
|
|
169
|
20 |
|
return $commit; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* get the commit properties from command |
174
|
|
|
* |
175
|
|
|
* @see ShowCommand::commitInfo |
176
|
|
|
*/ |
177
|
16 |
|
public function createFromCommand() |
178
|
|
|
{ |
179
|
16 |
|
$command = ShowCommand::getInstance($this->getRepository())->showCommit($this->ref); |
180
|
16 |
|
$outputLines = $this->getCaller()->execute($command, true, $this->getRepository()->getPath())->getOutputLines(); |
181
|
16 |
|
$this->parseOutputLines($outputLines); |
182
|
16 |
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* get the branches this commit is contained in |
186
|
|
|
* |
187
|
|
|
* @see BranchCommand::contains |
188
|
|
|
*/ |
189
|
|
|
public function getContainedIn() |
190
|
|
|
{ |
191
|
|
|
$command = BranchCommand::getInstance($this->getRepository())->contains($this->getSha()); |
192
|
|
|
|
193
|
|
|
return array_map('trim', (array)$this->getCaller()->execute($command)->getOutputLines(true)); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* number of commits that lead to this one |
198
|
|
|
* |
199
|
|
|
* @throws \RuntimeException |
200
|
|
|
* @throws \Symfony\Component\Process\Exception\LogicException |
201
|
|
|
* @throws \Symfony\Component\Process\Exception\InvalidArgumentException |
202
|
|
|
* @throws \Symfony\Component\Process\Exception\RuntimeException |
203
|
|
|
* @return int|void |
204
|
|
|
*/ |
205
|
3 |
|
public function count() |
206
|
|
|
{ |
207
|
3 |
|
$command = RevListCommand::getInstance($this->getRepository())->commitPath($this); |
208
|
|
|
|
209
|
3 |
|
return count($this->getCaller()->execute($command)->getOutputLines(true)); |
210
|
|
|
} |
211
|
|
|
|
212
|
1 |
|
public function getDiff() |
213
|
|
|
{ |
214
|
1 |
|
return $this->getRepository()->getDiff($this); |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* parse the output of a git command showing a commit |
219
|
|
|
* |
220
|
|
|
* @param array $outputLines output lines |
221
|
|
|
*/ |
222
|
35 |
|
private function parseOutputLines($outputLines) |
223
|
|
|
{ |
224
|
35 |
|
$message = ''; |
225
|
35 |
|
foreach ($outputLines as $line) { |
226
|
35 |
|
$matches = array(); |
227
|
35 |
|
if (preg_match('/^commit (\w+)$/', $line, $matches) > 0) { |
228
|
35 |
|
$this->sha = $matches[1]; |
229
|
35 |
|
} |
230
|
35 |
|
if (preg_match('/^tree (\w+)$/', $line, $matches) > 0) { |
231
|
35 |
|
$this->tree = $matches[1]; |
232
|
35 |
|
} |
233
|
35 |
|
if (preg_match('/^parent (\w+)$/', $line, $matches) > 0) { |
234
|
24 |
|
$this->parents[] = $matches[1]; |
235
|
24 |
|
} |
236
|
35 |
|
if (preg_match('/^author (.*) <(.*)> (\d+) (.*)$/', $line, $matches) > 0) { |
237
|
35 |
|
$author = new Author(); |
238
|
35 |
|
$author->setName($matches[1]); |
239
|
35 |
|
$author->setEmail($matches[2]); |
240
|
35 |
|
$this->author = $author; |
241
|
35 |
|
$date = \DateTime::createFromFormat('U O', $matches[3] . ' ' . $matches[4]); |
242
|
35 |
|
$date->modify($date->getOffset() . ' seconds'); |
243
|
35 |
|
$this->datetimeAuthor = $date; |
244
|
35 |
|
} |
245
|
35 |
|
if (preg_match('/^committer (.*) <(.*)> (\d+) (.*)$/', $line, $matches) > 0) { |
246
|
35 |
|
$committer = new Author(); |
247
|
35 |
|
$committer->setName($matches[1]); |
248
|
35 |
|
$committer->setEmail($matches[2]); |
249
|
35 |
|
$this->committer = $committer; |
250
|
35 |
|
$date = \DateTime::createFromFormat('U O', $matches[3] . ' ' . $matches[4]); |
251
|
35 |
|
$date->modify($date->getOffset() . ' seconds'); |
252
|
35 |
|
$this->datetimeCommitter = $date; |
253
|
35 |
|
} |
254
|
35 |
|
if (preg_match('/^ (.*)$/', $line, $matches)) { |
255
|
35 |
|
$message[] = $matches[1]; |
256
|
35 |
|
} |
257
|
35 |
|
} |
258
|
35 |
|
$this->message = new Message($message); |
259
|
35 |
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Returns true if the commit is a root commit. Usually the first of the repository |
263
|
|
|
* |
264
|
|
|
* @return bool |
265
|
|
|
*/ |
266
|
3 |
|
public function isRoot() |
267
|
|
|
{ |
268
|
3 |
|
return count($this->parents) == 0; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* toString magic method |
273
|
|
|
* |
274
|
|
|
* @return string the sha |
275
|
|
|
*/ |
276
|
8 |
|
public function __toString() |
277
|
|
|
{ |
278
|
8 |
|
return $this->sha; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
/** |
282
|
|
|
* @return \GitElephant\Command\Caller\Caller |
283
|
|
|
*/ |
284
|
16 |
|
private function getCaller() |
285
|
|
|
{ |
286
|
16 |
|
return $this->getRepository()->getCaller(); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Repository setter |
291
|
|
|
* |
292
|
|
|
* @param \GitElephant\Repository $repository repository variable |
293
|
|
|
*/ |
294
|
|
|
public function setRepository($repository) |
295
|
|
|
{ |
296
|
|
|
$this->repository = $repository; |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Repository getter |
301
|
|
|
* |
302
|
|
|
* @return \GitElephant\Repository |
303
|
|
|
*/ |
304
|
16 |
|
public function getRepository() |
305
|
|
|
{ |
306
|
16 |
|
return $this->repository; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* author getter |
311
|
|
|
* |
312
|
|
|
* @return Author |
313
|
|
|
*/ |
314
|
4 |
|
public function getAuthor() |
315
|
|
|
{ |
316
|
4 |
|
return $this->author; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* committer getter |
321
|
|
|
* |
322
|
|
|
* @return Author |
323
|
|
|
*/ |
324
|
4 |
|
public function getCommitter() |
325
|
|
|
{ |
326
|
4 |
|
return $this->committer; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* message getter |
331
|
|
|
* |
332
|
|
|
* @return \GitElephant\Objects\Commit\Message |
333
|
|
|
*/ |
334
|
11 |
|
public function getMessage() |
335
|
|
|
{ |
336
|
11 |
|
return $this->message; |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* parent getter |
341
|
|
|
* |
342
|
|
|
* @return mixed |
343
|
|
|
*/ |
344
|
2 |
|
public function getParents() |
345
|
|
|
{ |
346
|
2 |
|
return $this->parents; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* sha getter |
351
|
|
|
* |
352
|
|
|
* @param bool $short short version |
353
|
|
|
* |
354
|
|
|
* @return mixed |
355
|
|
|
*/ |
356
|
22 |
|
public function getSha($short = false) |
357
|
|
|
{ |
358
|
22 |
|
return $short ? substr($this->sha, 0, 7) : $this->sha; |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* tree getter |
363
|
|
|
* |
364
|
|
|
* @return mixed |
365
|
|
|
*/ |
366
|
3 |
|
public function getTree() |
367
|
|
|
{ |
368
|
3 |
|
return $this->tree; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* datetimeAuthor getter |
373
|
|
|
* |
374
|
|
|
* @return mixed |
375
|
|
|
*/ |
376
|
4 |
|
public function getDatetimeAuthor() |
377
|
|
|
{ |
378
|
4 |
|
return $this->datetimeAuthor; |
379
|
|
|
} |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* datetimeCommitter getter |
383
|
|
|
* |
384
|
|
|
* @return \DateTime |
385
|
|
|
*/ |
386
|
4 |
|
public function getDatetimeCommitter() |
387
|
|
|
{ |
388
|
4 |
|
return $this->datetimeCommitter; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* rev-parse command - often used to return a commit tag. |
393
|
|
|
* |
394
|
|
|
* @param array $options the options to apply to rev-parse |
395
|
|
|
* |
396
|
|
|
* @throws \RuntimeException |
397
|
|
|
* @throws \InvalidArgumentException |
398
|
|
|
* @throws \Symfony\Component\Process\Exception\RuntimeException |
399
|
|
|
* @return array |
400
|
|
|
*/ |
401
|
1 |
|
public function revParse(Array $options = array()) |
|
|
|
|
402
|
|
|
{ |
403
|
1 |
|
$c = RevParseCommand::getInstance()->revParse($this, $options); |
404
|
1 |
|
$caller = $this->repository->getCaller(); |
405
|
1 |
|
$caller->execute($c); |
406
|
|
|
|
407
|
1 |
|
return array_map('trim', $caller->getOutputLines(true)); |
408
|
|
|
} |
409
|
|
|
} |
410
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.