Created
November 6, 2013 14:44
-
-
Save benjifisher/7337143 to your computer and use it in GitHub Desktop.
This php command-line script will do a binary search to find the first git commit that breaks your patch. It will give simple usage instructions if you invoke it without arguments. I wrote this script to help in re-rolling patches for Drupal. I posted the original version as a comment on https://drupal.org/patch/reroll . The script makes a reaso…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env php | |
<?php | |
// Get the arguments. If there are not two, then print usage info. | |
if (count($argv) != 3) { | |
$scriptname = basename($argv[0]); | |
print("Usage: $scriptname <date> <patchfile>" . PHP_EOL); | |
print('where <date> is any date understood by git' . PHP_EOL); | |
print('and <patchfile> applied cleanly on <date>, not today.' . PHP_EOL); | |
print("Example: $scriptname 2013/05/17 foo.patch" . PHP_EOL); | |
return; | |
} | |
list($scriptpath, $date, $patchfile) = $argv; | |
// Check that there are no modified files. | |
exec('git status --porcelain', $git_status); | |
$modified = preg_filter('/^ M\s*/', '', $git_status); | |
if ($modified) { | |
print('Please commit, discard, or stash your changes before running this command.' . PHP_EOL); | |
print('modified: ' . implode(', ', $modified) . PHP_EOL); | |
return; | |
} | |
// Get a list of commits from $date to present. | |
exec("git log --reverse --format=format:%H --since=$date", $commits); | |
$num_commits = count($commits); | |
print("There are $num_commits commits since $date.\n"); | |
// Before doing any checkouts, figure out how to restore the current state. | |
exec('git status --short --branch', $status); | |
if (preg_match('/no branch/', $status[0])) { | |
exec('git show --format=format:%H', $show); | |
$git_restore = $show[0]; | |
} | |
else { | |
$git_restore = preg_replace('/## /', '', $status[0]); | |
} | |
print("git restore: $git_restore\n"); | |
// Check that the patch applies to the oldest commit but not to the newest one. | |
if (_find_commit_apply_check($commits[$num_commits - 1], $patchfile, $git_restore)) { | |
print('The patch applies cleanly to the latest commit.' . PHP_EOL); | |
return; | |
} | |
if (!_find_commit_apply_check($commits[0], $patchfile, $git_restore)) { | |
print("The patch does not apply to the first commit after $date.\n"); | |
print('Perhaps try one day earlier.' . PHP_EOL); | |
return; | |
} | |
// Do a binary search for the commit that breaks the patch. | |
$clean_commit = 0; | |
$dirty_commit = $num_commits - 1; | |
while ($dirty_commit - $clean_commit > 1) { | |
$pivot = (int) (($clean_commit + $dirty_commit) / 2); | |
echo "testing commit {$commits[$pivot]}, $pivot of $num_commits.\n"; | |
if (_find_commit_apply_check($commits[$pivot], $patchfile, $git_restore)) { | |
$clean_commit = $pivot; | |
} | |
else { | |
$dirty_commit = $pivot; | |
} | |
} | |
// Output the result. | |
echo <<<EOS | |
The patch $patchfile applies cleanly to commmit {$commits[$clean_commit]} | |
but not to the following commit {$commits[$dirty_commit]} | |
EOS; | |
/** | |
* Check out $commit, see whether $patch applies, and restore the branch or | |
* commit $restore. Return TRUE if the patch applied cleanly. | |
*/ | |
function _find_commit_apply_check($commit, $patch, $restore) { | |
// Some git commands seem to write to stderr, so redirect it with 2>&1. | |
// Does this work on Windows? Maybe use proc_open() instead. | |
exec("git checkout $commit 2>&1"); | |
exec("git apply --check $patch 2>&1", $check); | |
$errors = preg_filter('/^error:/', '', $check); | |
exec("git checkout $restore 2>&1"); | |
return empty($errors); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment