smart-apply-patch.sh 4.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
#!/usr/bin/env bash
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

set -e

#
# Determine if the patch file is a git diff file with prefixes.
# These files are generated via "git diff" *without* the --no-prefix option.
#
# We can apply these patches more easily because we know that the a/ and b/
# prefixes in the "diff" lines stands for the project root directory.
# So we don't have to hunt for the project root.
# And of course, we know that the patch file was generated using git, so we
# know git apply can handle it properly.
#
# Arguments: file name.
# Return: 0 if it is a git diff; 1 otherwise.
#
is_git_diff_with_prefix() {
  DIFF_TYPE="unknown"
  while read -r line; do
    if [[ "$line" =~ ^diff\  ]]; then
      if [[ "$line" =~ ^diff\ \-\-git ]]; then
        DIFF_TYPE="git"
      else
        return 1 # All diff lines must be diff --git lines.
      fi
    fi
    if [[ "$line" =~ ^\+\+\+\  ]] ||
       [[ "$line" =~ ^\-\-\-\  ]]; then
      if ! [[ "$line" =~ ^....[ab]/ ]]; then
        return 1 # All +++ and --- lines must start with a/ or b/.
      fi
    fi
  done < $1
  [ x$DIFF_TYPE == x"git" ] || return 1
  return 0 # return true (= 0 in bash)
}

PATCH_FILE=$1
DRY_RUN=$2
if [ -z "$PATCH_FILE" ]; then
  echo usage: $0 patch-file
  exit 1
fi

PATCH=${PATCH:-patch} # allow overriding patch binary

# Cleanup handler for temporary files
TOCLEAN=""
cleanup() {
  rm $TOCLEAN
  exit $1
}
trap "cleanup 1" HUP INT QUIT TERM

# Allow passing "-" for stdin patches
if [ "$PATCH_FILE" == "-" ]; then
  PATCH_FILE=/tmp/tmp.in.$$
  cat /dev/fd/0 > $PATCH_FILE
  TOCLEAN="$TOCLEAN $PATCH_FILE"
fi

# Special case for git-diff patches without --no-prefix
if is_git_diff_with_prefix "$PATCH_FILE"; then
  GIT_FLAGS="--binary -p1 -v"
  if [[ -z $DRY_RUN ]]; then
      GIT_FLAGS="$GIT_FLAGS --stat --apply "
      echo Going to apply git patch with: git apply "${GIT_FLAGS}"
  else
      GIT_FLAGS="$GIT_FLAGS --check "
  fi
  git apply ${GIT_FLAGS} "${PATCH_FILE}"
  exit $?
fi

# Come up with a list of changed files into $TMP
TMP=/tmp/tmp.paths.$$
TOCLEAN="$TOCLEAN $TMP"

if $PATCH -p0 -E --dry-run < $PATCH_FILE 2>&1 > $TMP; then
  PLEVEL=0
  #if the patch applied at P0 there is the possability that all we are doing
  # is adding new files and they would apply anywhere. So try to guess the
  # correct place to put those files.

  TMP2=/tmp/tmp.paths.2.$$
  TOCLEAN="$TOCLEAN $TMP2"

  egrep '^patching file |^checking file ' $TMP | awk '{print $3}' | grep -v /dev/null | sort | uniq > $TMP2

  if [ ! -s $TMP2 ]; then
    echo "Error: Patch dryrun couldn't detect changes the patch would make. Exiting."
    cleanup 1
  fi

  #first off check that all of the files do not exist
  FOUND_ANY=0
  for CHECK_FILE in $(cat $TMP2)
  do
    if [[ -f $CHECK_FILE ]]; then
      FOUND_ANY=1
    fi
  done

  if [[ "$FOUND_ANY" = "0" ]]; then
    #all of the files are new files so we have to guess where the correct place to put it is.

    # if all of the lines start with a/ or b/, then this is a git patch that
    # was generated without --no-prefix
    if ! grep -qv '^a/\|^b/' $TMP2 ; then
      echo Looks like this is a git patch. Stripping a/ and b/ prefixes
      echo and incrementing PLEVEL
      PLEVEL=$[$PLEVEL + 1]
      sed -i -e 's,^[ab]/,,' $TMP2
    fi
  fi
elif $PATCH -p1 -E --dry-run < $PATCH_FILE 2>&1 > /dev/null; then
  PLEVEL=1
elif $PATCH -p2 -E --dry-run < $PATCH_FILE 2>&1 > /dev/null; then
  PLEVEL=2
else
  echo "The patch does not appear to apply with p0 to p2";
  cleanup 1;
fi

# If this is a dry run then exit instead of applying the patch
if [[ -n $DRY_RUN ]]; then
  cleanup 0;
fi

echo Going to apply patch with: $PATCH -p$PLEVEL
$PATCH -p$PLEVEL -E < $PATCH_FILE

cleanup $?