4
4
$ opts = parseOpts ();
5
5
6
6
$ changes = array ();
7
+ $ data_from = load ($ opts ['from ' ], $ opts ['path ' ], $ opts ['vcs ' ], '' );
8
+ $ data_to = load ($ opts ['to ' ], $ opts ['path ' ], $ opts ['vcs ' ], 'composer.lock ' );
7
9
8
10
if (! $ opts ['only-dev ' ]) {
9
- $ changes ['changes ' ] = diff ('packages ' , $ opts [ ' from ' ] , $ opts [ ' to ' ], $ opts [ ' path ' ] );
11
+ $ changes ['changes ' ] = diff ('packages ' , $ data_from , $ data_to );
10
12
}
11
13
12
14
if (! $ opts ['only-prod ' ]) {
13
- $ changes ['changes-dev ' ] = diff ('packages-dev ' , $ opts [ ' from ' ] , $ opts [ ' to ' ], $ opts [ ' path ' ] );
15
+ $ changes ['changes-dev ' ] = diff ('packages-dev ' , $ data_from , $ data_to );
14
16
}
15
17
16
18
if ($ opts ['json ' ]) {
@@ -31,28 +33,24 @@ if ($opts['md']) {
31
33
));
32
34
}
33
35
34
- $ table_titles = [
36
+ $ table_titles = array (
35
37
'changes ' => 'Production Changes ' ,
36
38
'changes-dev ' => 'Dev Changes ' ,
37
- ] ;
39
+ ) ;
38
40
39
41
foreach ($ changes as $ k => $ diff ) {
40
42
print tableize ($ table_titles [$ k ], $ diff , $ table_opts );
41
43
}
42
44
43
- function diff ($ key , $ from , $ to , $ base_path ) {
45
+ function diff ($ key , $ data_from , $ data_to ) {
44
46
45
47
$ pkgs = array ();
46
48
47
- $ data = load ($ from , $ base_path );
48
-
49
- foreach ($ data ->$ key as $ pkg ) {
49
+ foreach ($ data_from ->$ key as $ pkg ) {
50
50
$ pkgs [$ pkg ->name ] = array (version ($ pkg ), 'REMOVED ' , '' );
51
51
}
52
52
53
- $ data = load ($ to , $ base_path );
54
-
55
- foreach ($ data ->$ key as $ pkg ) {
53
+ foreach ($ data_to ->$ key as $ pkg ) {
56
54
if (! array_key_exists ($ pkg ->name , $ pkgs )) {
57
55
$ pkgs [$ pkg ->name ] = array ('NEW ' , version ($ pkg ), '' );
58
56
continue ;
@@ -150,46 +148,72 @@ function urlFormatterMd($url, $text) {
150
148
return sprintf ('[%s](%s) ' , $ text , $ url );
151
149
}
152
150
153
- function load ($ fileish , $ base_path = '' ) {
154
- $ orig = $ fileish ;
155
-
156
- if (empty ($ base_path )) {
157
- $ base_path = '. ' . DIRECTORY_SEPARATOR ;
158
- } else {
159
- $ base_path = rtrim ($ base_path , DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR ;
151
+ // $fileish is what the user actually requested.
152
+ // $default_fileish is what it should be if $fileish is empty
153
+ function load ($ fileish , $ base_path , $ force_vcs , $ default_fileish ) {
154
+ $ loaders = ($ force_vcs || (empty ($ fileish ) && empty ($ default_fileish ))) ? array () : array ('loadFile ' );
155
+
156
+ $ vcses = $ force_vcs ? array ($ force_vcs ) : getVcses ();
157
+ $ errors = array ();
158
+
159
+ foreach ($ vcses as $ vcs ) {
160
+ $ detector = 'vcsDetect ' . ucfirst ($ vcs );
161
+ if ($ vcs != $ force_vcs && function_exists ($ detector )) {
162
+ list ($ available , $ err ) = call_user_func ($ detector , $ fileish , $ base_path , $ default_fileish );
163
+ if ($ err ) {
164
+ $ errors [] = $ err ;
165
+ continue ;
166
+ }
167
+ if (!$ available ) continue ;
168
+ }
169
+ $ loaders [] = 'vcsLoad ' . ucfirst ($ vcs );
160
170
}
161
171
162
- if (empty ($ fileish )) {
163
- $ fileish = $ base_path . 'composer.lock ' ;
172
+ if (empty ($ loaders )) {
173
+ error_log (implode ("\n" , $ errors ));
174
+ if ($ force_vcs ) {
175
+ error_log ("Requested vcs ' $ force_vcs' not installed or otherwise unavailable " );
176
+ } else {
177
+ error_log ("No loaders were found; perhaps your vcs cli tools are not installed, not in PATH, or otherwise unavailable " );
178
+ }
179
+ exit (1 );
164
180
}
165
181
166
- if (isUrl ($ fileish )) {
167
- if (! in_array (parse_url ($ fileish , PHP_URL_SCHEME ), stream_get_wrappers ())) {
168
- error_log ("Error: no stream wrapper to open ' $ fileish' " );
169
- exit (1 );
182
+ $ errors = array ();
183
+ foreach ($ loaders as $ loader ) {
184
+ list ($ result , $ err ) = call_user_func_array ($ loader , array ($ fileish , $ base_path , $ default_fileish ));
185
+ if (empty ($ err )) {
186
+ return $ result ;
170
187
}
171
-
172
- return mustDecodeJson (file_get_contents ($ fileish ), $ fileish );
188
+ $ errors [] = "Failed to find ' $ fileish' with ' $ loader'; $ err " ;
173
189
}
174
190
175
- if ( file_exists ( $ fileish ) ) {
176
- return mustDecodeJson ( file_get_contents ( $ fileish ), $ fileish );
191
+ foreach ( $ errors as $ e ) {
192
+ error_log ( $ e );
177
193
}
178
194
179
- if (strpos ($ orig , ': ' ) === false ) {
180
- $ fileish .= ': ' . $ base_path . 'composer.lock ' ;
181
- }
195
+ exit (1 );
196
+ }
182
197
183
- $ lines = array ();
198
+ function loadFile ($ fileish , $ base_path , $ default_fileish ) {
199
+ if (empty ($ fileish )) {
200
+ $ fileish = $ default_fileish ;
201
+ if (!empty ($ base_path )) {
202
+ $ fileish = joinPath ($ base_path , $ fileish );
203
+ }
204
+ }
184
205
185
- exec ('git show ' . escapeshellarg ($ fileish ), $ lines , $ exit );
206
+ // Does it look like a url that we can handle with stream wrappers?
207
+ if (isUrl ($ fileish ) && in_array (parse_url ($ fileish , PHP_URL_SCHEME ), stream_get_wrappers ())) {
208
+ return array (mustDecodeJson (file_get_contents ($ fileish ), $ fileish ), false );
209
+ }
186
210
187
- if ( $ exit !== 0 ) {
188
- error_log ( " Error: cannot open $ orig or find it in git as $ fileish" );
189
- exit ( 1 );
211
+ // Is it a file in the local filesystem?
212
+ if ( file_exists ( $ fileish)) {
213
+ return array ( mustDecodeJson ( file_get_contents ( $ fileish ), $ fileish ), false );
190
214
}
191
215
192
- return mustDecodeJson ( implode ( "\n" , $ lines ), $ fileish );
216
+ return array ( false , " Candidate ' $ fileish' does not look loadable from the fs or php stream wrappers " );
193
217
}
194
218
195
219
function isUrl ($ string ) {
@@ -278,8 +302,126 @@ function formatCompareDrupal($url, $from, $to) {
278
302
return sprintf ('%s/compare/8.x-%s...8.x-%s ' , $ url , substr (urlencode ($ from ), 0 , -2 ), substr (urlencode ($ to ), 0 , -2 ));
279
303
}
280
304
305
+ //
306
+ // ## VCSes ####################
307
+ //
308
+
309
+ function getVcses () {
310
+ return array ('git ' , 'svn ' );
311
+ }
312
+
313
+ function vcsDetectGit ($ _fileish ) {
314
+ // Is there a git executable?
315
+ exec ('sh -c "git --version" > /dev/null 2>&1 ' , $ _out , $ exit );
316
+ if ($ exit !== 0 ) return array (false , "'git --version' exited with non-zero code ' $ exit' " );
317
+
318
+ // Does this look like a git repo?
319
+ $ path = findUp ('. ' , '.git ' );
320
+ return array (!! $ path , ($ path ) ? false : "Could not find .git in current directory or parents " );
321
+ }
322
+
323
+ function vcsLoadGit ($ fileish , $ base_path , $ _default_fileish ) {
324
+ // We don't care about $default_fileish here - we are expected to load from
325
+ // git and we must make a filename to do that.
326
+ if (empty ($ fileish )) {
327
+ $ fileish = 'HEAD ' ;
328
+ }
329
+
330
+ if (strpos ($ fileish , ': ' ) === false ) {
331
+ $ fileish .= ': ' . $ base_path . 'composer.lock ' ;
332
+ }
333
+
334
+ $ lines = array ();
335
+ exec ('git show ' . escapeshellarg ($ fileish ), $ lines , $ exit );
336
+
337
+ if ($ exit !== 0 ) {
338
+ return array ('' , "'git show $ fileish' exited with non-zero code ' $ exit' " );
339
+ }
340
+
341
+ return array (mustDecodeJson (implode ("\n" , $ lines ), $ fileish ), false );
342
+ }
343
+
344
+ function vcsDetectSvn ($ fileish , $ base_path , $ default_fileish ) {
345
+ // Is there a git executable?
346
+ exec ('sh -c "svn --version" > /dev/null 2>&1 ' , $ _out , $ exit );
347
+ if ($ exit !== 0 ) return array (false , "'svn --version' exited with non-zero code ' $ exit' " );
348
+
349
+ if (strpos ('svn:// ' , $ fileish ) === 0 ) {
350
+ return array (true , false );
351
+ }
352
+
353
+ // Does this look like a svn repo?
354
+ $ path = findUp ('. ' , '.svn ' );
355
+ return array (!! $ path , ($ path ) ? false : "Could not find .svn in current directory or parents " );
356
+ }
357
+
358
+ function vcsLoadSvn ($ fileish , $ base_path , $ _default_fileish ) {
359
+ // We don't care about $default_fileish here - we are expected to load from
360
+ // svn and we must make a filename to do that.
361
+ if (empty ($ fileish )) {
362
+ $ fileish = 'BASE ' ;
363
+ }
364
+
365
+ // If $fileish starts with a url scheme that 'svn cat' can handle or ^, or
366
+ // if it contains a @, assume it is already a proper svn identifier.
367
+ // - file:// http:// https:// svn:// svn+ssh:// => absolute url of
368
+ // repository (file/http/https may have been handled with stream wrappers
369
+ // if '--vcs svn' wasn't specified)
370
+ // - ^ => relative url from current workspace repository
371
+ // - @ => repository url with revision
372
+ if (preg_match ('#^\^|^(file|http|https|svn|svn\+ssh)://|@#i ' , $ fileish ) === 0 ) {
373
+ $ fileish = $ base_path . 'composer.lock@ ' .$ fileish ;
374
+ }
375
+
376
+ exec ('svn cat ' . escapeshellarg ($ fileish ), $ lines , $ exit );
377
+
378
+ if ($ exit !== 0 ) {
379
+ return array ('' , "'svn cat $ fileish' exited with non-zero code ' $ exit' " );
380
+ }
381
+
382
+ return array (mustDecodeJson (implode ("\n" , $ lines ), $ fileish ), false );
383
+ }
384
+
385
+ function findUp ($ path , $ filename , $ tries = 10 ) {
386
+ if (empty ($ path )) {
387
+ $ path = '. ' ;
388
+ }
389
+
390
+ // > Trailing delimiters, such as \ and /, are also removed
391
+ // > returns false on failure, e.g. if the file does not exist.
392
+ $ path = realpath ($ path );
393
+ if ($ path === false ) return false ;
394
+
395
+ do {
396
+ $ candidate = joinPath ($ path , $ filename );
397
+
398
+ if (file_exists ($ candidate )) {
399
+ return $ candidate ;
400
+ }
401
+
402
+ $ path = dirnameSafe ($ path );
403
+ } while ($ path !== false && --$ tries > 0 );
404
+
405
+ return false ;
406
+ }
407
+
408
+ function dirnameSafe ($ path ) {
409
+ $ parent = dirname ($ path );
410
+ return ($ parent != $ path && !empty ($ parent )) ? $ parent : false ;
411
+ }
412
+
413
+ function joinPath (/* path parts */ ) {
414
+ return implode (DIRECTORY_SEPARATOR , array_map (function ($ part ) {
415
+ return trim ($ part , DIRECTORY_SEPARATOR );
416
+ }, func_get_args ()));
417
+ }
418
+
419
+ function ensureTrailingPathSep ($ path ) {
420
+ return trim ($ path , DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR ;
421
+ }
422
+
281
423
function parseOpts () {
282
- $ given = getopt ('hp: ' , array ('path: ' , 'from: ' , 'to: ' , 'md ' , 'json ' , 'pretty ' , 'no-links ' , 'only-prod ' , 'only-dev ' , 'help ' ));
424
+ $ given = getopt ('hp: ' , array ('path: ' , 'from: ' , 'to: ' , 'md ' , 'json ' , 'pretty ' , 'no-links ' , 'only-prod ' , 'only-dev ' , 'help ' , ' vcs: ' ));
283
425
284
426
foreach (array ('help ' => 'h ' , 'path ' => 'p ' ) as $ long => $ short ) {
285
427
if (array_key_exists ($ short , $ given )) {
@@ -292,38 +434,49 @@ function parseOpts() {
292
434
usage ();
293
435
}
294
436
437
+ $ vcs = array_key_exists ('vcs ' , $ given ) ? $ given ['vcs ' ] : '' ;
438
+ if ($ vcs && !function_exists ('vcsLoad ' . ucfirst ($ vcs ))) {
439
+ error_log ("Unsupported vcs ' $ vcs' \n" );
440
+ usage ();
441
+ }
442
+
295
443
return array (
296
- 'path ' => array_key_exists ('path ' , $ given ) ? $ given ['path ' ] : '' ,
297
- 'from ' => array_key_exists ('from ' , $ given ) ? $ given ['from ' ] : 'HEAD ' ,
444
+ 'path ' => array_key_exists ('path ' , $ given ) ? ensureTrailingPathSep ( $ given ['path ' ]) : '' ,
445
+ 'from ' => array_key_exists ('from ' , $ given ) ? $ given ['from ' ] : '' ,
298
446
'to ' => array_key_exists ('to ' , $ given ) ? $ given ['to ' ] : '' ,
299
447
'md ' => array_key_exists ('md ' , $ given ),
300
448
'json ' => array_key_exists ('json ' , $ given ),
301
449
'pretty ' => version_compare (PHP_VERSION , '5.4.0 ' , '>= ' ) && array_key_exists ('pretty ' , $ given ),
302
450
'no-links ' => array_key_exists ('no-links ' , $ given ),
303
451
'only-prod ' => array_key_exists ('only-prod ' , $ given ),
304
452
'only-dev ' => array_key_exists ('only-dev ' , $ given ),
453
+ 'vcs ' => $ vcs ,
305
454
);
306
455
}
307
456
308
457
function usage () {
458
+ $ vcses = implode (', ' , getVcses ());
309
459
print <<<EOF
310
460
Usage: composer-lock-diff [options]
311
461
312
462
Options:
313
463
-h --help Print this message
314
464
--path, -p Base to with which to prefix paths. Default "./"
315
465
E.g. `-p app` would look for HEAD:app/composer.lock and app/composer.lock
316
- --from The file, git ref, or git ref with filename to compare from (HEAD:composer.lock)
466
+ --from The file, git ref, or git ref with filename to compare from
467
+ (git: HEAD:composer.lock, svn: composer.lock@BASE)
317
468
--to The file, git ref, or git ref with filename to compare to (composer.lock)
318
469
--json Format output as JSON
319
470
--pretty Pretty print JSON output (PHP >= 5.4.0)
320
471
--md Use markdown instead of plain text
321
472
--no-links Don't include Compare links in plain text or any links in markdown
322
473
--only-prod Only include changes from `packages`
323
474
--only-dev Only include changes from `packages-dev`
475
+ --vcs Force vcs ( $ vcses). Default: attempt to auto-detect
324
476
325
477
EOF ;
326
478
327
479
exit (0 );
328
480
}
481
+ # vim: ff=unix ts=4 ss=4 sr et
329
482
0 commit comments