17

I have a very large patch file that I'm trying to apply to my code. The problem is, some of the changes in my patch already exist in the code. Is there a way to make patch gracefully ignore the changes that have already been applied?

The -N option doesn't do what I want. If it encounters an already applied hunk it will generate a reject file and will not apply any more hunks to that file. I want it to just ignore that hunk and continue applying the rest of the patch. The only time I want it to generate a .rej file is if a hunk can't be applied and doesn't appear to already be applied.

Is there a way to do this?

2 Answers 2

8

You'll need patchutils installed for this.

This script will split one large patch into smaller separate paches, each of them containing only one hunk for one file. You can then apply these patches with patch --forward.

#!/bin/sh -eu

PATCH=$1
OUTDIR=$2

test -f "$PATCH" && test -d "$OUTDIR"

TDIR=$(mktemp -d)
trap 'rm -rf $TDIR' 0

INDEX=0
TEMPHUNK=$TDIR/current_hunk

lsdiff $1 | while read FNAME
do
    HUNK=1
    while :
    do
        filterdiff --annotate --hunks=$HUNK -i "$FNAME" "$PATCH" > "$TEMPHUNK"
        HUNK=$((HUNK+1))
        test -s "$TEMPHUNK" && \
            {
                mv "$TEMPHUNK" "$OUTDIR/$INDEX.diff"
                INDEX=$((INDEX+1))
            } || break
    done
done

Edit: save script to hunks.sh, and call it:

./hunks.sh path/to/big.diff path/to/output/directory
2

I eventually solved this using a solution similar to artyom's.

Step 1: Explode the patch into lots of separate patches, one for each hunk.

I used this script to do this:

#!/usr/bin/python2

import sys

header = []
writing_header = False
patchnum = 0

patch = open(sys.argv[1], "r")
out = open("/dev/null", "w")

for line in patch.readlines():
    if line.startswith("diff"):
        header = []
        writing_header = True
    if line.startswith("@@"):
        out.close()
        out = open(str(patchnum) + ".diff", "w")
        patchnum += 1
        writing_header = False
        out.writelines(header)
    if writing_header:
        header.append(line)
    else:
        out.write(line)

out.close()

Example usage:

$ cd directory_containing_patch
$ mkdir foo
$ cd foo
$ explode.py ../huge_patch.diff

This will populate the current directory with files called 0.diff 1.diff et cetera.

Step 2: Apply each patch, discarding already applied patches.

I used this script to do this:

#!/bin/bash

if [[ $# -ne 1 || ! -d "${1}/" ]]; then
    echo "Usage: $0 dirname"
    exit 1
fi

find "$1" -name \*.diff | while read f; do
    OUTPUT=$(patch -s -p1 -r- -i"$f")
    if [ $? -eq 0 ]; then
        rm "$f"
    else
        if echo "$OUTPUT" | grep -q "Reversed (or previously applied) patch detected!"; then
            rm "$f"
        fi
    fi
done

Example usage:

$ cd directory_containing_code
$ apply_patches.bash directory_containing_patch/foo

This will delete any of the previously generated patches that apply cleanly or that have already been applied. Any patches left in foo are rejects that need to be manually examined and merged.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .