1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* @internal |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
use Illuminate\Support\Str; |
10
|
|
|
|
11
|
|
|
require_once __DIR__.'/../../../vendor/autoload.php'; |
12
|
|
|
|
13
|
|
|
$timeStart = microtime(true); |
14
|
|
|
|
15
|
|
|
$filesChanged = 0; |
16
|
|
|
|
17
|
|
|
$linesCounted = 0; |
18
|
|
|
|
19
|
|
|
$links = []; |
20
|
|
|
|
21
|
|
|
$warnings = []; |
22
|
|
|
|
23
|
|
|
// Buffer headings so we can check for style |
24
|
|
|
$headings = []; // [filename => [line => heading]] |
25
|
|
|
$checksHeadings = false; |
26
|
|
|
|
27
|
|
|
function find_markdown_files($dir): array |
28
|
|
|
{ |
29
|
|
|
$markdown_files = []; |
30
|
|
|
|
31
|
|
|
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); |
32
|
|
|
foreach ($iterator as $file) { |
33
|
|
|
// Skip _data directory |
34
|
|
|
if (str_contains($file->getPathname(), '_data')) { |
35
|
|
|
continue; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
if ($file->isFile() && strtolower($file->getExtension()) == 'md') { |
39
|
|
|
$markdown_files[] = realpath($file->getPathname()); |
40
|
|
|
} |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
return $markdown_files; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
function handle_file(string $file): void |
47
|
|
|
{ |
48
|
|
|
echo 'Handling '.$file."\n"; |
49
|
|
|
|
50
|
|
|
normalize_lines($file); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
function normalize_lines($filename): void |
54
|
|
|
{ |
55
|
|
|
$stream = file_get_contents($filename); |
56
|
|
|
|
57
|
|
|
$text = $stream; |
58
|
|
|
$text = str_replace("\r\n", "\n", $text); |
59
|
|
|
$text = str_replace("\t", ' ', $text); |
60
|
|
|
|
61
|
|
|
if (empty(trim($text))) { |
62
|
|
|
// Warn |
63
|
|
|
global $warnings; |
64
|
|
|
$warnings[] = "File $filename is empty"; |
65
|
|
|
|
66
|
|
|
return; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
$lines = explode("\n", $text); |
70
|
|
|
$new_lines = []; |
71
|
|
|
|
72
|
|
|
$last_line = ''; |
73
|
|
|
$was_last_line_heading = false; |
74
|
|
|
$is_inside_fenced_code_block = false; |
75
|
|
|
$is_inside_fenced_fenced_code_block = false; |
76
|
|
|
$firstHeadingLevel = null; |
77
|
|
|
foreach ($lines as $index => $line) { |
78
|
|
|
global $linesCounted; |
79
|
|
|
$linesCounted++; |
80
|
|
|
|
81
|
|
|
/** Normalization */ |
82
|
|
|
|
83
|
|
|
// Remove multiple empty lines |
84
|
|
|
if (trim($line) == '' && trim($last_line) == '') { |
85
|
|
|
continue; |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
// Make sure there is a space after headings |
89
|
|
|
if ($was_last_line_heading && trim($line) != '') { |
90
|
|
|
$new_lines[] = ''; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
// Make sure there are two empty lines before level 2 headings (but not if it's the first l2 heading) |
94
|
|
|
if ($is_inside_fenced_code_block !== true && str_starts_with($line, '## ') && $index > $firstHeadingLevel + 3) { |
95
|
|
|
$new_lines[] = ''; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
if ($firstHeadingLevel === null && str_starts_with($line, '# ')) { |
99
|
|
|
$firstHeadingLevel = $index; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
// Check if line is a heading |
103
|
|
|
if (str_starts_with($line, '##')) { |
104
|
|
|
$was_last_line_heading = true; |
105
|
|
|
global $headings; |
106
|
|
|
$headings[$filename][$index + 1] = $line; |
107
|
|
|
} else { |
108
|
|
|
$was_last_line_heading = false; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
// Make sure there is a space before opening a fenced code block (search for ```language) |
112
|
|
|
if (str_starts_with($line, '```') && $line !== '```' && trim($last_line) != '') { |
113
|
|
|
if (! $is_inside_fenced_fenced_code_block) { |
114
|
|
|
$new_lines[] = ''; |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
// Check if line is a fenced code block |
119
|
|
|
if (str_starts_with($line, '``')) { |
120
|
|
|
$is_inside_fenced_code_block = ! $is_inside_fenced_code_block; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
// Check if line is an escaped fenced code block |
124
|
|
|
if (str_starts_with($line, '````')) { |
125
|
|
|
$is_inside_fenced_fenced_code_block = ! $is_inside_fenced_fenced_code_block; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
// Remove trailing spaces |
129
|
|
|
$line = rtrim($line); |
130
|
|
|
|
131
|
|
|
$new_lines[] = $line; |
132
|
|
|
$last_line = $line; |
133
|
|
|
|
134
|
|
|
/** Linting */ |
135
|
|
|
|
136
|
|
|
// if not inside fenced code block |
137
|
|
|
if (! $is_inside_fenced_code_block) { |
138
|
|
|
// Add any links to buffer, so we can check them later |
139
|
|
|
preg_match_all('/\[([^\[]+)]\((.*)\)/', $line, $matches); |
140
|
|
|
if (count($matches) > 0) { |
141
|
|
|
foreach ($matches[2] as $match) { |
142
|
|
|
// If link is for an anchor, prefix the filename |
143
|
|
|
if (str_starts_with($match, '#')) { |
144
|
|
|
$match = 'ANCHOR_'.basename($filename).$match; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
global $links; |
148
|
|
|
$links[] = [ |
149
|
|
|
'filename' => $filename, |
150
|
|
|
'line' => $index + 1, |
151
|
|
|
'link' => $match, |
152
|
|
|
]; |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
// Check for un-backtick-ed inline code |
157
|
|
|
// If line contains $ |
158
|
|
|
if (str_contains($line, '$') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php')) { |
159
|
|
|
// Check character before the $ is not a backtick |
160
|
|
|
$pos = strpos($line, '$'); |
161
|
|
|
if ($pos > 0) { |
162
|
|
|
$charBefore = substr($line, $pos - 1, 1); |
163
|
|
|
if ($charBefore !== '`') { |
164
|
|
|
global $warnings; |
165
|
|
|
$warnings['Inline code'][] = sprintf('Unformatted inline code found in %s:%s', $filename, $index + 1); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
// If line contains command |
170
|
|
|
if (str_contains($line, 'php hyde') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php')) { |
171
|
|
|
// Check character before the php hyde is not a backtick |
172
|
|
|
$pos = strpos($line, 'php hyde'); |
173
|
|
|
if ($pos > 0) { |
174
|
|
|
$charBefore = substr($line, $pos - 1, 1); |
175
|
|
|
if ($charBefore !== '`') { |
176
|
|
|
global $warnings; |
177
|
|
|
$warnings['Inline code'][] = sprintf('Unformatted inline command found in %s:%s', $filename, $index + 1); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
// If word ends in .php |
182
|
|
|
if (str_contains($line, '.php') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php') && ! str_contains($line, 'http') && ! str_contains(strtolower($line), 'filepath')) { |
183
|
|
|
// Check character after the .php is not a backtick |
184
|
|
|
$pos = strpos($line, '.php'); |
185
|
|
|
if ($pos > 0) { |
186
|
|
|
$charAfter = substr($line, $pos + 4, 1); |
187
|
|
|
if ($charAfter !== '`') { |
188
|
|
|
global $warnings; |
189
|
|
|
$warnings['Inline code'][] = sprintf('Unformatted inline filename found in %s:%s', $filename, $index + 1); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// If word ends in .json |
195
|
|
|
if (str_contains($line, '.json') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ json') && ! str_contains($line, 'http') && ! str_contains(strtolower($line), 'filepath')) { |
196
|
|
|
// Check character after the .json is not a backtick |
197
|
|
|
$pos = strpos($line, '.json'); |
198
|
|
|
if ($pos > 0) { |
199
|
|
|
$charAfter = substr($line, $pos + 5, 1); |
200
|
|
|
if ($charAfter !== '`') { |
201
|
|
|
global $warnings; |
202
|
|
|
$warnings['Inline code'][] = sprintf('Unformatted inline filename found in %s:%s', $filename, $index + 1); |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
// if word ends with () |
207
|
|
|
if (str_contains($line, '()') && ! str_contains($line, '[Blade]:')) { |
208
|
|
|
// Check character after the () is not a backtick |
209
|
|
|
$pos = strpos($line, '()'); |
210
|
|
|
if ($pos > 0) { |
211
|
|
|
$charAfter = substr($line, $pos + 2, 1); |
212
|
|
|
if ($charAfter !== '`') { |
213
|
|
|
global $warnings; |
214
|
|
|
$warnings['Inline code'][] = sprintf('Unformatted inline function found in %s:%s', $filename, $index + 1); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
// Check for invalid command signatures |
220
|
|
|
if (str_contains($line, 'php hyde')) { |
221
|
|
|
// Extract signature from line |
222
|
|
|
$start = strpos($line, 'php hyde'); |
223
|
|
|
$substr = substr($line, $start); |
224
|
|
|
$explode = explode(' ', $substr, 3); |
225
|
|
|
$signature = $explode[0].' '.$explode[1].' '.$explode[2]; |
226
|
|
|
$end = strpos($signature, '`'); |
227
|
|
|
if ($end === false) { |
228
|
|
|
$end = strpos($signature, '<'); |
229
|
|
|
if ($end === false) { |
230
|
|
|
$end = strlen($signature); |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
$signature = substr($signature, 0, $end); |
234
|
|
|
$signatures = getSignatures(); |
235
|
|
|
if (! in_array($signature, $signatures)) { |
236
|
|
|
global $warnings; |
237
|
|
|
$warnings['Invalid command signatures'][] = sprintf('Invalid command signature \'%s\' found in %s:%s', $signature, $filename, $index + 1); |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
// Check if line is too long |
243
|
|
|
if (strlen($line) > 120) { |
244
|
|
|
global $warnings; |
245
|
|
|
// $warnings[] = 'Line '.$linesCounted.' in file '.$filename.' is too long'; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
// Warn if documentation contains legacy markers (experimental, beta, etc) |
249
|
|
|
$markers = ['experimental', 'beta', 'alpha', 'v0.']; |
250
|
|
|
foreach ($markers as $marker) { |
251
|
|
|
if (str_contains($line, $marker)) { |
252
|
|
|
global $warnings; |
253
|
|
|
$warnings['Legacy markers'][] = sprintf('Legacy marker found in %s:%s Found "%s"', $filename, $index + 1, $marker); |
254
|
|
|
} |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
// Warn when legacy terms are used (for example slug instead of identifier/route key) |
258
|
|
|
$legacyTerms = [ |
259
|
|
|
'slug' => '"identifier" or "route key"', |
260
|
|
|
'slugs' => '"identifiers" or "route keys"', |
261
|
|
|
]; |
262
|
|
|
|
263
|
|
|
foreach ($legacyTerms as $legacyTerm => $newTerm) { |
264
|
|
|
if (str_contains(strtolower($line), $legacyTerm)) { |
265
|
|
|
global $warnings; |
266
|
|
|
$warnings['Legacy terms'][] = sprintf('Legacy term found in %s:%s Found "%s", should be %s', $filename, $index + 1, $legacyTerm, $newTerm); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
$new_content = implode("\n", $new_lines); |
272
|
|
|
$new_content = trim($new_content)."\n"; |
273
|
|
|
file_put_contents($filename, $new_content); |
274
|
|
|
|
275
|
|
|
if ($new_content !== $stream) { |
276
|
|
|
global $filesChanged; |
277
|
|
|
$filesChanged++; |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
$dir = __DIR__.'/../../../docs'; |
282
|
|
|
$markdownFiles = find_markdown_files($dir); |
283
|
|
|
|
284
|
|
|
foreach ($markdownFiles as $file) { |
285
|
|
|
handle_file($file); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
// Just to make PhpStorm happy |
289
|
|
|
$links[] = [ |
290
|
|
|
'filename' => '', |
291
|
|
|
'line' => 1, |
292
|
|
|
'link' => '', |
293
|
|
|
]; |
294
|
|
|
|
295
|
|
|
if (count($links) > 0) { |
296
|
|
|
$uniqueLinks = []; |
297
|
|
|
|
298
|
|
|
foreach ($links as $data) { |
299
|
|
|
$link = $data['link']; |
300
|
|
|
$filename = $data['filename']; |
301
|
|
|
$line = $data['line']; |
302
|
|
|
|
303
|
|
|
if (str_starts_with($link, 'http')) { |
304
|
|
|
// Check for outdated links |
305
|
|
|
// laravel.com/docs/9.x |
306
|
|
|
if (str_contains($link, 'laravel.com/docs/9.x')) { |
307
|
|
|
$warnings['Outdated links'][] = "Outdated documentation link to $link found in $filename:$line"; |
308
|
|
|
} |
309
|
|
|
continue; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
if (str_starts_with($link, '#')) { |
313
|
|
|
continue; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
// Remove hash for anchors |
317
|
|
|
$link = explode('#', $link)[0]; |
318
|
|
|
// Remove anything before spaces (image alt text) |
319
|
|
|
$link = explode(' ', $link)[0]; |
320
|
|
|
// Trim any non-alphanumeric characters from the end of the link |
321
|
|
|
$link = rtrim($link, '.,;:!?)'); |
322
|
|
|
|
323
|
|
|
if (! str_starts_with($link, 'ANCHOR_')) { |
324
|
|
|
// Add to new unique array |
325
|
|
|
$uniqueLinks[$link] = "$filename:$line"; |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
$base = __DIR__.'/../../../docs'; |
330
|
|
|
// find all directories in the docs folder |
331
|
|
|
$directories = array_filter(glob($base.'/*'), 'is_dir'); |
332
|
|
|
|
333
|
|
|
foreach ($uniqueLinks as $link => $location) { |
334
|
|
|
// Check uses pretty urls |
335
|
|
|
if (str_ends_with($link, '.html')) { |
336
|
|
|
$warnings['Bad links'][] = "Link to $link in $location should not use .html extension"; |
337
|
|
|
continue; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
// Check does not end with .md |
341
|
|
|
if (str_ends_with($link, '.md')) { |
342
|
|
|
$warnings['Bad links'][] = "Link to $link in $location must not use .md extension"; |
343
|
|
|
continue; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
// Check if file exists |
347
|
|
|
if (! file_exists($base.'/'.$link)) { |
348
|
|
|
$hasMatch = false; |
349
|
|
|
foreach ($directories as $directory) { |
350
|
|
|
if (file_exists($directory.'/'.$link.'.md')) { |
351
|
|
|
$hasMatch = true; |
352
|
|
|
break; |
353
|
|
|
} |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
if (! $hasMatch) { |
357
|
|
|
// Check that link is not for search (dynamic page) |
358
|
|
|
if (! str_contains($link, 'search')) { |
359
|
|
|
$warnings['Broken links'][] = "Broken link to $link found in $location"; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
function getSignatures(): array |
367
|
|
|
{ |
368
|
|
|
static $signatures = null; |
369
|
|
|
|
370
|
|
|
if ($signatures === null) { |
371
|
|
|
$cache = __DIR__.'/../cache/hyde-signatures.php'; |
372
|
|
|
if (file_exists($cache)) { |
373
|
|
|
$signatures = include $cache; |
374
|
|
|
} else { |
375
|
|
|
$signatures = [ |
376
|
|
|
// Adds any hidden commands we know exist |
377
|
|
|
'php hyde list', |
378
|
|
|
'php hyde change:sourceDirectory', |
379
|
|
|
]; |
380
|
|
|
$commandRaw = shell_exec('cd ../../../ && php hyde list --raw'); |
381
|
|
|
foreach (explode("\n", $commandRaw) as $command) { |
382
|
|
|
$command = Str::before($command, ' '); |
383
|
|
|
$signatures[] = trim('php hyde '.$command); |
384
|
|
|
} |
385
|
|
|
file_put_contents($cache, '<?php return '.var_export($signatures, true).';'); |
386
|
|
|
} |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
return $signatures; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
// Just to make PhpStorm happy |
393
|
|
|
$headings['foo.md'][1] = '## Bar'; |
394
|
|
|
|
395
|
|
|
if ($checksHeadings && count($headings)) { |
396
|
|
|
foreach ($headings as $filename => $fileHeadings) { |
397
|
|
|
$headingLevels = []; |
398
|
|
|
foreach ($fileHeadings as $heading) { |
399
|
|
|
$headingLevel = substr_count($heading, '#'); |
400
|
|
|
$headingLevels[] = $headingLevel; |
401
|
|
|
|
402
|
|
|
// Check for style: 1-2 headings should be title case, 3+ should be sentence case |
403
|
|
|
$headingText = trim(str_replace('#', '', $heading)); |
404
|
|
|
$titleCase = Hyde\make_title($headingText); |
405
|
|
|
$alwaysUppercase = ['PHP', 'HTML', 'CLI']; |
406
|
|
|
$alwaysLowercase = ['to']; |
407
|
|
|
$titleCase = str_ireplace($alwaysUppercase, $alwaysUppercase, $titleCase); |
408
|
|
|
$titleCase = str_ireplace($alwaysLowercase, $alwaysLowercase, $titleCase); |
409
|
|
|
|
410
|
|
|
$isTitleCase = $headingText === $titleCase; |
411
|
|
|
$sentenceCase = Str::ucfirst($headingText); |
412
|
|
|
$isSentenceCase = $headingText === $sentenceCase; |
413
|
|
|
$something = false; |
414
|
|
|
if ($headingLevel < 3) { |
415
|
|
|
if (! $isTitleCase) { |
416
|
|
|
$warnings['Headings'][] = "Heading '$headingText' should be title case in $filename (expected '$titleCase')"; |
417
|
|
|
} |
418
|
|
|
} else { |
419
|
|
|
if (! $isSentenceCase) { |
420
|
|
|
$warnings['Headings'][] = "Heading '$headingText' should be sentence case in $filename (expected '$sentenceCase')"; |
421
|
|
|
} |
422
|
|
|
} |
423
|
|
|
} |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
if (count($warnings) > 0) { |
428
|
|
|
echo "\n\033[31mWarnings:\033[0m \033[33m".count($warnings, COUNT_RECURSIVE) - count($warnings)." found \033[0m \n"; |
429
|
|
|
foreach ($warnings as $type => $messages) { |
430
|
|
|
echo "\n\033[33m$type:\033[0m \n"; |
431
|
|
|
foreach ($messages as $message) { |
432
|
|
|
echo " - $message\n"; |
433
|
|
|
} |
434
|
|
|
} |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
$time = round((microtime(true) - $timeStart) * 1000, 2); |
438
|
|
|
$linesTransformed = number_format($linesCounted); |
439
|
|
|
$fileCount = count($markdownFiles); |
440
|
|
|
|
441
|
|
|
echo "\n\n\033[32mAll done!\033[0m Formatted, normalized, and validated $linesTransformed lines of Markdown in $fileCount files in {$time}ms\n"; |
442
|
|
|
|
443
|
|
|
if ($filesChanged > 0) { |
444
|
|
|
echo "\n\033[32m$filesChanged files were changed.\033[0m "; |
445
|
|
|
} else { |
446
|
|
|
echo "\n\033[32mNo files were changed.\033[0m "; |
447
|
|
|
} |
448
|
|
|
$warningCount = count($warnings, COUNT_RECURSIVE) - count($warnings); |
449
|
|
|
if ($warningCount > 0) { |
450
|
|
|
echo sprintf("\033[33m%s %s found.\033[0m", $warningCount, $warningCount === 1 ? 'warning' : 'warnings'); |
451
|
|
|
if (file_exists(__DIR__.'/../cache/last-run-warnings-count.txt')) { |
452
|
|
|
$lastRunWarningsCount = (int) file_get_contents(__DIR__.'/../cache/last-run-warnings-count.txt'); |
453
|
|
|
if ($warningCount < $lastRunWarningsCount) { |
454
|
|
|
echo sprintf(' Good job! You fixed %d %s!', $lastRunWarningsCount - $warningCount, $lastRunWarningsCount - $warningCount === 1 ? 'warning' : 'warnings'); |
455
|
|
|
} elseif ($warningCount > $lastRunWarningsCount) { |
456
|
|
|
echo sprintf(' Uh oh! You introduced %d new %s!', $warningCount - $lastRunWarningsCount, $warningCount - $lastRunWarningsCount === 1 ? 'warning' : 'warnings'); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
} |
460
|
|
|
file_put_contents(__DIR__.'/../cache/last-run-warnings-count.txt', $warningCount); |
461
|
|
|
echo "\n"; |
462
|
|
|
|
463
|
|
|
// If --git flag is passed, make a git commit |
464
|
|
|
if (isset($argv[1]) && $argv[1] === '--git') { |
465
|
|
|
if ($filesChanged > 0) { |
466
|
|
|
echo "\n\033[33mCommitting changes to git...\033[0m\n"; |
467
|
|
|
passthru('git commit -am "Format Markdown"'); |
468
|
|
|
} else { |
469
|
|
|
echo "\n\033[33mNo changes to commit\033[0m\n"; |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
|