Last updated 1 June 2016.

On this page:

# Introduction

The automated patch testing system is able to test whether a patch applies or not. You may see a patch on an issue with a message like "Unable to apply patch [patchname]. Unable to apply patch…" beside it:

A patch with the 'Unable to apply' message from the automated patch testing system shown.

# Instructions for "rerolling" a new patch that applies cleanly

  1. Ensure that you have cloned the project repository by following the steps on the Version control tab on the project page.
  2. Check out the project's latest branch of the repository. For example:
    git checkout 8.2.x
    git pull --rebase
    
  3. Unless otherwise specified in the issue comments, always reroll the most recent patch found on the issue. You can find the latest patch by clicking the 'Most recent attachment' link on the top right of the page.
    The top right section of an issue page with the 'Most recent attachment' link highlighted.
    Download the last patch from the relevant issue, and move it to the root directory of your development site.
  4. Double-check whether it's necessary to reroll the patch (sometimes the testbot gets confused):

    git apply --check 1497310-statistics_config_settings-5.patch
    

    If you see no output, it was a false alarm: the patch doesn't need to be rerolled. If the issue had the "needs reroll" tag, remove the tag and post a comment that says something like: "I could apply the patch, so no reroll is needed." Reset the status to "Needs review". You're done!

  5. Go to the issue and note the date of the comment that you downloaded the patch from. (Double click on the date to toggle between time-ago and the date and time.)
    A comment in an issue queue that's passing testbot, with the date circled

    Run the git log command with that date and time, in order to figure out a commit where the patch applied cleanly. For example:

    git log -1 --before="March 30, 2012 at 1:54pm"
    

    commit 72cf5a75537a651c79271a1a3a6781ffc98430e7
    Author: catch <catch@35733.no-reply.drupal.org>
    Date:   Fri Mar 30 13:42:49 2012 +0900
    
        Revert "Issue #787652 by larowlan, andypost: Fixed Forum vocabulary alterations possibly obsolete -- possible to delete forum vocab."
    
        This reverts commit e179ee1d643101ae9ac9c1a94990bdb161ac8a62.
    
  6. Take the first few characters of the commit hash you see there (in this case, 72cf5a755) and create a new branch (and switch to it) from that commit.

    git checkout -b test-branch 72cf5a755
    


    git should return a line like the following:

    Switched to a new branch 'test-branch'
    

    During this process, you might encounter a permissions error like:

    error: unable to unlink old 'sites/default/default.settings.php' (Permission denied)
    

    To fix this error, make sure you have write permission to the file and directory in which the file exists.

  7. Try applying the patch to test if it works:

    git apply --index 1497310-statistics_config_settings-5.patch
    

    (no output)
    

    If you get similar "patch does not apply" errors, delete your current branch and repeat from step 5 using an earlier date.

  8. Commit the patch's changes to your local branch.

    git commit -m "Applying patch from issue 1497310 comment 5804956"
    

    [test-branch 00329ef] Applying patch from issue 1497310 comment 5804956
     7 files changed, 99 insertions(+), 66 deletions(-)
     create mode 100644 core/modules/statistics/config/statistics.settings.xml
    
  9. Now, attempt to pull in all of the changes that have happened since the commit you branched from.

    git rebase 8.2.x
    

    If the rebase has CONFLICTs, this will take more work; you can follow Rerolling patches: Failed Rebase

  10. Finally, create your patch by diffing your local branch (test-branch) against the upstream branch:

    git diff -M 8.2.x test-branch > test-branch.patch
    

  11. # Hints for checking the rerolled patch

  12. Test your patch by applying it to the upstream branch.

    git checkout 8.2.x
    

    Switched to branch '8.2.x'
    

    git apply --check test-branch.patch
    

    (no output)
    

    No output is good. That means the patch applied cleanly.

  13. Check the size of the patch. If it is zero bytes go through these steps again until it larger than zero bytes. If it is a very different size from the original patch, then something probably went wrong during the reroll. There still needs to be more investigation on this patch.
    • Upload it to the issue.
    • Mark it back to "needs review!"
    • Indicate whether it was an auto merge, if there were conflicts or if the file sizes were very different. If there were conflicts, describe what you did to resolve them. This will make it easier for the person reviewing the patch to know how carefully they need to review.
    • Remove the "Needs reroll" tag.
  14. :) Congratulations! You've just rerolled a patch!

Comments

benjifisher’s picture

If you use vim with the fugitive plugin, then another way to resolve merge conflicts is described here: http://vimcasts.org/episodes/fugitive-vim-resolving-merge-conflicts-with....

A few people have told me that after watching this screencast they were looking forward to their next merge conflict!

Another option (not tested by me) is to use meld. See http://blog.wuwon.id.au/2010/09/painless-merge-conflict-resolution-in.html for a tutorial.

... you might even find that merging becomes fun, not dreaded.

benjifisher’s picture

This php script will do a binary search to find the first commit that breaks your patch. Note that it will give simple usage instructions if you invoke it without arguments.

#!/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  " . PHP_EOL);
  print('where  is any date understood by git' . PHP_EOL);
  print('and  applied cleanly on , 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 <<&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);
}
tlyngej’s picture

Haven't tested it yet, but if it works as described, it really will be a huge time saver.

THANK YOU! THANK YOU! THANK YOU!

benjifisher’s picture

@tlyngej: Did you try it out?

I have never made a gist before, but maybe this is what gist is for. The script in my comment above is now available on GitHub: https://gist.github.com/benjifisher/7337143

bburg’s picture

chmod is preferable to sudo git checkout as any files created as a result of the checkout will become owned by root, which can cause subsequent pulls that affect the root-owned files to fail.

scottrigby’s picture

Also, this neat trick from pwolanin - git help apply:

       -3, --3way
           When the patch does not apply cleanly, fall back on 3-way merge if the patch records the identity of blobs it is supposed to apply to, and we have those blobs available
           locally, possibly leaving the conflict markers in the files in the working tree for the user to resolve. This option implies the --index option, and is incompatible with
           the --reject and the --cached options.

Also now ask Druplicon on IRC "3way?" ;)

scottrigby’s picture

Also note re finding the last commit hash the patch applied cleanly to (when testbot results are not available): in the diff of the patch, the first hash is what you want.

benjifisher’s picture

If I look at the patch file, the hash will tell me what version was current when the patch was created. The commit that breaks the patch may have come much later. So probably I do not understand what you mean by looking at "the diff of the patch." Can you give more details?