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\Command; |
21
|
|
|
|
22
|
|
|
use \GitElephant\Objects\Author; |
23
|
|
|
use \GitElephant\Objects\Branch; |
24
|
|
|
use \GitElephant\Objects\TreeishInterface; |
25
|
|
|
use \GitElephant\Repository; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* Main command generator (init, status, add, commit, checkout) |
29
|
|
|
* |
30
|
|
|
* @author Matteo Giachino <[email protected]> |
31
|
|
|
*/ |
32
|
|
|
class MainCommand extends BaseCommand |
33
|
|
|
{ |
34
|
|
|
const GIT_INIT = 'init'; |
35
|
|
|
const GIT_STATUS = 'status'; |
36
|
|
|
const GIT_ADD = 'add'; |
37
|
|
|
const GIT_COMMIT = 'commit'; |
38
|
|
|
const GIT_CHECKOUT = 'checkout'; |
39
|
|
|
const GIT_MOVE = 'mv'; |
40
|
|
|
const GIT_REMOVE = 'rm'; |
41
|
|
|
const GIT_RESET = 'reset'; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* constructor |
45
|
|
|
* |
46
|
|
|
* @param \GitElephant\Repository $repo The repository object this command |
47
|
|
|
* will interact with |
48
|
|
|
*/ |
49
|
103 |
|
public function __construct(Repository $repo = null) |
50
|
|
|
{ |
51
|
103 |
|
parent::__construct($repo); |
52
|
103 |
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Init the repository |
56
|
|
|
* |
57
|
|
|
* @param bool $bare |
58
|
|
|
* |
59
|
|
|
* @throws \RuntimeException |
60
|
|
|
* @return MainCommand |
61
|
|
|
*/ |
62
|
97 |
|
public function init($bare = false) |
63
|
|
|
{ |
64
|
97 |
|
$this->clearAll(); |
65
|
97 |
|
if ($bare) { |
66
|
5 |
|
$this->addCommandArgument('--bare'); |
67
|
5 |
|
} |
68
|
97 |
|
$this->addCommandName(self::GIT_INIT); |
69
|
|
|
|
70
|
97 |
|
return $this->getCommand(); |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Get the repository status |
75
|
|
|
* |
76
|
|
|
* @param bool $porcelain |
77
|
|
|
* |
78
|
|
|
* @throws \RuntimeException |
79
|
|
|
* @return string |
80
|
|
|
*/ |
81
|
12 |
|
public function status($porcelain = false) |
82
|
|
|
{ |
83
|
12 |
|
$this->clearAll(); |
84
|
12 |
|
$this->addCommandName(self::GIT_STATUS); |
85
|
12 |
|
if ($porcelain) { |
86
|
9 |
|
$this->addCommandArgument('--porcelain'); |
87
|
9 |
|
} else { |
88
|
3 |
|
$this->addConfigs(array('color.status' => 'false')); |
89
|
|
|
} |
90
|
|
|
|
91
|
12 |
|
return $this->getCommand(); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Add a node to the stage |
96
|
|
|
* |
97
|
|
|
* @param string $what what should be added to the repository |
98
|
|
|
* |
99
|
|
|
* @throws \RuntimeException |
100
|
|
|
* @return string |
101
|
|
|
*/ |
102
|
92 |
|
public function add($what = '.') |
103
|
|
|
{ |
104
|
92 |
|
$this->clearAll(); |
105
|
92 |
|
$this->addCommandName(self::GIT_ADD); |
106
|
92 |
|
$this->addCommandArgument('--all'); |
107
|
92 |
|
$this->addCommandSubject($what); |
108
|
|
|
|
109
|
92 |
|
return $this->getCommand(); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Remove a node from the stage and put in the working tree |
114
|
|
|
* |
115
|
|
|
* @param string $what what should be removed from the stage |
116
|
|
|
* |
117
|
|
|
* @throws \RuntimeException |
118
|
|
|
* @return string |
119
|
|
|
*/ |
120
|
2 |
|
public function unstage($what) |
121
|
|
|
{ |
122
|
2 |
|
$this->clearAll(); |
123
|
2 |
|
$this->addCommandName(self::GIT_RESET); |
124
|
2 |
|
$this->addCommandArgument('HEAD'); |
125
|
2 |
|
$this->addPath($what); |
126
|
|
|
|
127
|
2 |
|
return $this->getCommand(); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Commit |
132
|
|
|
* |
133
|
|
|
* @param string $message the commit message |
134
|
|
|
* @param bool $stageAll commit all changes |
135
|
|
|
* @param string|Author $author override the author for this commit |
136
|
|
|
* |
137
|
|
|
* @throws \RuntimeException |
138
|
|
|
* @throws \InvalidArgumentException |
139
|
|
|
* @return string |
140
|
|
|
*/ |
141
|
92 |
|
public function commit($message, $stageAll = false, $author = null, $allowEmpty = false) |
142
|
|
|
{ |
143
|
92 |
|
$this->clearAll(); |
144
|
92 |
|
if (trim($message) === '' || is_null($message)) { |
145
|
|
|
throw new \InvalidArgumentException(sprintf('You can\'t commit without message')); |
146
|
|
|
} |
147
|
92 |
|
$this->addCommandName(self::GIT_COMMIT); |
148
|
|
|
|
149
|
92 |
|
if ($stageAll) { |
150
|
86 |
|
$this->addCommandArgument('-a'); |
151
|
86 |
|
} |
152
|
|
|
|
153
|
92 |
|
if ($author !== null) { |
154
|
1 |
|
$this->addCommandArgument('--author'); |
155
|
1 |
|
$this->addCommandArgument($author); |
|
|
|
|
156
|
1 |
|
} |
157
|
|
|
|
158
|
92 |
|
if ($allowEmpty) { |
159
|
1 |
|
$this->addCommandArgument('--allow-empty'); |
160
|
1 |
|
} |
161
|
|
|
|
162
|
92 |
|
$this->addCommandArgument('-m'); |
163
|
92 |
|
$this->addCommandSubject($message); |
164
|
|
|
|
165
|
92 |
|
return $this->getCommand(); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Checkout a treeish reference |
170
|
|
|
* |
171
|
|
|
* @param string|Branch $ref the reference to checkout |
172
|
|
|
* |
173
|
|
|
* @throws \RuntimeException |
174
|
|
|
* @return string |
175
|
|
|
*/ |
176
|
23 |
|
public function checkout($ref) |
177
|
|
|
{ |
178
|
23 |
|
$this->clearAll(); |
179
|
|
|
|
180
|
23 |
|
$what = $ref; |
181
|
23 |
|
if ($ref instanceof Branch) { |
182
|
2 |
|
$what = $ref->getName(); |
183
|
23 |
|
} elseif ($ref instanceof TreeishInterface) { |
184
|
|
|
$what = $ref->getSha(); |
185
|
|
|
} |
186
|
|
|
|
187
|
23 |
|
$this->addCommandName(self::GIT_CHECKOUT); |
188
|
23 |
|
$this->addCommandArgument('-q'); |
189
|
23 |
|
$this->addCommandSubject($what); |
190
|
|
|
|
191
|
23 |
|
return $this->getCommand(); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Move a file/directory |
196
|
|
|
* |
197
|
|
|
* @param string|Object $from source path |
198
|
|
|
* @param string|Object $to destination path |
199
|
|
|
* |
200
|
|
|
* @throws \RuntimeException |
201
|
|
|
* @throws \InvalidArgumentException |
202
|
|
|
* @return string |
203
|
|
|
*/ |
204
|
1 |
|
public function move($from, $to) |
205
|
|
|
{ |
206
|
1 |
|
$this->clearAll(); |
207
|
|
|
|
208
|
1 |
|
$from = trim($from); |
209
|
1 |
|
if (!$this->validatePath($from)) { |
210
|
|
|
throw new \InvalidArgumentException('Invalid source path'); |
211
|
|
|
} |
212
|
|
|
|
213
|
1 |
|
$to = trim($to); |
214
|
1 |
|
if (!$this->validatePath($to)) { |
215
|
|
|
throw new \InvalidArgumentException('Invalid destination path'); |
216
|
|
|
} |
217
|
|
|
|
218
|
1 |
|
$this->addCommandName(self::GIT_MOVE); |
219
|
1 |
|
$this->addCommandSubject($from); |
220
|
1 |
|
$this->addCommandSubject2($to); |
221
|
|
|
|
222
|
1 |
|
return $this->getCommand(); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Remove a file/directory |
227
|
|
|
* |
228
|
|
|
* @param string|Object $path the path to remove |
229
|
|
|
* @param bool $recursive recurse |
230
|
|
|
* @param bool $force force |
231
|
|
|
* |
232
|
|
|
* @throws \RuntimeException |
233
|
|
|
* @throws \InvalidArgumentException |
234
|
|
|
* @return string |
235
|
|
|
*/ |
236
|
1 |
|
public function remove($path, $recursive, $force) |
237
|
|
|
{ |
238
|
1 |
|
$this->clearAll(); |
239
|
|
|
|
240
|
1 |
|
$path = trim($path); |
241
|
1 |
|
if (!$this->validatePath($path)) { |
242
|
|
|
throw new \InvalidArgumentException('Invalid path'); |
243
|
|
|
} |
244
|
|
|
|
245
|
1 |
|
$this->addCommandName(self::GIT_REMOVE); |
246
|
|
|
|
247
|
1 |
|
if ($recursive) { |
248
|
|
|
$this->addCommandArgument('-r'); |
249
|
|
|
} |
250
|
|
|
|
251
|
1 |
|
if ($force) { |
252
|
|
|
$this->addCommandArgument('-f'); |
253
|
|
|
} |
254
|
|
|
|
255
|
1 |
|
$this->addPath($path); |
256
|
|
|
|
257
|
1 |
|
return $this->getCommand(); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Validates a path |
262
|
|
|
* |
263
|
|
|
* @param string $path path |
264
|
|
|
* |
265
|
|
|
* @return bool |
266
|
|
|
*/ |
267
|
2 |
|
protected function validatePath($path) |
268
|
|
|
{ |
269
|
2 |
|
if (empty($path)) { |
270
|
|
|
return false; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
// we are always operating from root directory |
274
|
|
|
// so forbid relative paths |
275
|
2 |
|
if (false !== strpos($path, '..')) { |
276
|
|
|
return false; |
277
|
|
|
} |
278
|
|
|
|
279
|
2 |
|
return true; |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
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.