Skip to content

Instantly share code, notes, and snippets.

@dmsnell
Last active October 22, 2015 03:28
Show Gist options
  • Save dmsnell/23b1189b33106babbb68 to your computer and use it in GitHub Desktop.
Save dmsnell/23b1189b33106babbb68 to your computer and use it in GitHub Desktop.
Generate linter args from SVN repository
#!/usr/local/bin/php
<?php
/**
* Generates a structured list of files related to an
* SVN commit or working directory, or generates the
* diff between a particular SVN commit and its parent
* or between the working directory and the base revision.
*
* Calling:
* ./generate-linter-args.php [-rREVISION_NUMBER] --type ['changeset' or 'diff'] [file list]
*
* Get current changes:
* ./generate-linter-args.php --type changeset
* ./generate-linter-args.php --type diff
*
* Get changes for specific revision:
* ./generate-linter-args.php -r12345 --type changeset
* ./generate-linter-args.php -r12345 --type diff
*
* Get changes for specific files in working directory
* ./generate-linter-args.php --type changeset "wp-content/lib/class.splines.php" "wp-content/lib/class.reticulator.php"
* ./generate-linter-args.php --type diff "wp-content/lib/class.splines.php" "wp-content/lib/class.reticulator.php"
*/
/**
* Will return an item if it's a member of an array
* or `null` if it doesn't exist within that array.
*
* @param Array $array Containing array
* @param mixed $member Key to check for existence in the array
* @return bool Whether or not $member is in $array
*/
function maybeMember( Array $array, $member ) {
return isset( $array[ $member ] ) ? $array[ $member ] : null;
}
/**
* Returns the shell arguments assuming they are what
* they should be. Does not perform input validation.
*
* @return Array [ type of info requested, revision or null, list of filenames or blank ]
*/
function getShellArgs() {
global $argv;
$options = getopt( 'r::', [ 'type:' ] );
// Which parameter to create - required
$type = maybeMember( $options, 'type' );
// Revision is either given or "current"
$revision = (int) maybeMember( $options, 'r' );
// Get the remaining args after the script name and params
// Should be a space-separated list of filenames
$filenames = getRealFiles( array_slice( $argv, 1 ) );
return [ $type, $revision, $filenames ];
}
/**
* Returns only files that exist.
* Filters out special names that might also
* be svn commands coming through.
*
* @param Array $names List of potential filenames
* @return Array List of filenames for files that exist
*/
function getRealFiles( $names ) {
$isIrrelevant = function( $name ) {
return ! array_key_exists ( $name,
[
'commit' => true,
'ci' => true
] );
};
$files = $names;
$files = array_filter( $files, $isIrrelevant );
$files = array_filter( $files, 'file_exists' );
return $files;
}
/**
* Returns only lines that start with the specified character.
*
* @param string $type The letter describing which svn operation is desired
* @return Closure produces a bool output for the passed line if it starts with $type
*/
function typeFilter( $type ) {
return function( $line ) use ( $type ) {
return $type === substr( $line, 0, 1 );
};
}
/**
* Converts the `svn status` output line into a filename
*
* @param string $line one line of output from `svn status`
* @return string Trimmed version of $line without the status info prefix
*/
function statusLineToFilename( $line ) {
return trim( substr( $line, 7 ) );
}
/**
* Returns an array of files of a specified type
*
* @param Array $lines lines of output from `svn status`
* @param string $type Which type of `svn` operation to return
* @return Array Filenames for files that exist and match the $type predicate
*/
function filesByType( $lines, $type ) {
return array_values( array_map( 'statusLineToFilename', array_filter( $lines, typeFilter( $type ) ) ) );
}
/**
* Splits the output of `svn status` into a structured
* object with added, modified, missing, and deleted files
* as arrays.
*
* @param Array $lines output lines from `svn status`
* @return stdClass Structured view of changed files
*/
function statusLinesToChangeSet( $lines ) {
return (object) [
"addedFiles" => array_merge(
filesByType( $lines, 'A' ),
filesByType( $lines, 'R' )
),
"deletedFiles" => filesByType( $lines, 'D' ),
"missingFiles" => filesByType( $lines, '!' ),
"modifiedFiles" => filesByType( $lines, 'M' )
];
}
/**
* Computes the changeset between two revisions
*
* @param int $revision Specific revision to examine
* @return Array output from svn command
*/
function statusAcrossRevisions( $revision ) {
if ( empty( $revision ) ) {
$revision = getCurrentRevision();
}
exec( sprintf(
'svn diff -c%d --summarize', $revision
), $output, $exitStatus );
if ( 0 !== $exitStatus ) {
exit( $exitStatus );
}
return $output;
}
/**
* Computes the changeset assuming we are basing the
* comparison off of the local working copy.
*
* @param Array $filenames possible list of filenames to only examine
* @return Array output from svn command
*/
function statusForWorkingCopy( $filenames ) {
exec( sprintf( 'svn status %s 2>/dev/null',
implode( ' ', array_map( 'escapeshellarg', $filenames ) )
), $output, $exitStatus );
if ( 0 !== $exitStatus ) {
exit( $exitStatus );
}
return $output;
}
/**
* Returns the current revision number for the checked-out
* copy of the svn repository.
*
* @return int Revision of current local working svn repository
*/
function getCurrentRevision() {
exec( 'svn info | grep "Revision" | awk \'{print $2}\'', $raw_revision );
return (int) array_shift( $raw_revision );
}
/**
* Returns the output of `svn diff` across revisions
*
* @param int $revision A specific svn revision to examine
* @return Array output from svn command
*/
function createRevisionDiff( $revision ) {
if ( empty( $revision ) ) {
$revision = getCurrentRevision();
}
exec( sprintf(
'svn diff -c%d 2>/dev/null', $revision
), $output, $exitStatus );
if ( 0 !== $exitStatus ) {
exit( $exitStatus );
}
return $output;
}
/**
* Returns the output of `svn diff` for the working copy
*
* @param Array $filenames List of filenames to only examine
* @return Array output from svn command
*/
function createWorkingCopyDiff( $filenames ) {
exec( sprintf( 'svn diff %s 2>/dev/null',
implode( ' ', array_map( 'escapeshellarg', $filenames ) )
), $output, $exitStatus );
if ( 0 !== $exitStatus ) {
exit( $exitStatus );
}
return $output;
}
/**
* Returns the desired `svn` diff
*
* @param int $revision A specific revision to examine
* @param Array $filenames Specific list of files to examine only
* @return string Newline-terminated list of output lines for `svn diff`
*/
function createDiff( $revision, $filenames ) {
return implode( PHP_EOL,
empty( $revision )
? createWorkingCopyDiff( $filenames )
: createRevisionDiff( $revision )
);
}
/**
* Computes the desired changeset
*
* @param int $revision A specific revision to examine
* @param Array $filenames Specific list of files to examine only
* @return string JSON-encoded structured list of changed files for commit
*/
function listRelevantFiles( $revision, $filenames ) {
return json_encode( statusLinesToChangeSet(
empty( $revision )
? statusForWorkingCopy( $filenames )
: statusAcrossRevisions( $revision )
) );
}
/**
* Generates args suitable for passing into the linters
*
* @return int Whether or not the program was able to complete successfully
*/
function main() {
list( $type, $revision, $filenames ) = getShellArgs();
if ( empty( $type ) ) {
die( 'Please choose type of information requested: "--type changeset" or "--type diff"' );
}
if ( 'changeset' === $type ) {
echo listRelevantFiles( $revision, $filenames );
}
if ( 'diff' === $type ) {
echo createDiff( $revision, $filenames );
}
echo "\n";
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment