summaryrefslogtreecommitdiff
path: root/scripts/patch-rework.sh
blob: 795c3c3e74da3234fc47fed6eaf9a25a531f1ddf (plain)
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
146
147
148
149
150
151
152
153
#!/bin/sh

# Get our required options
base="$1"
src="$2"
dst="$3"
shift 3

# The remainder is for diff
diff="$@"

# This function checks that the files listed in the file in "$1"
# do exist, at the given depth-stripping level (aka diff -p#)
do_check_files_at_depth() {
  local flist="$1"
  local depth="$2"
  local ok=0  # 0: OK,  !0: KO

  exec 6<&0
  exec 7<"${flist}"

  while read -u7 f; do
    f="$( echo "${f}" |sed -r -e "s:^([^/]+/){${depth}}::;" )"
    [ -f "${f}" ] || ok=1
  done

  exec 7<&-
  exec <&6

  return ${ok}
}

mkdir -p "${dst}"
dst="$( cd "${dst}"; pwd )"

# Iterate through patches
for p in "${src}/"*.patch; do
  pname="$( basename "${p}" )"

  printf "Handling patch '${pname}'...\n"

  printf "  creating reference..."
  cp -a "${base}" "${base}.orig"
  printf " done\n"

  printf "  retrieving patch comment..."
  comment="$( awk '
BEGIN { mark=0; }
$0~/^diff --/ { nextfile; }
$1=="---" { mark=1; next; }
$1=="+++" && mark==1 { nextfile; }
{ mark=0; print; }
' "${p}" )"
  printf " done\n"

  printf "  creating patched file list..."
  diffstat -f 4 -r 2 -u -p 0 "${p}"                         \
  |head -n -1                                               \
  |awk '{ for(i=NF;i>=NF-5;i--) { $(i) = ""; } print; }'    \
  |sort                                                     \
  >"diffstat.orig"
  printf " done\n"

  pushd "${base}" >/dev/null 2>&1

  # Check all files exist, up to depth 3
  printf "  checking depth:"
  for((d=0;d<4;d++)); do
    printf " ${d}"
    if do_check_files_at_depth "../diffstat.orig" ${d}; then
      printf " ok, using depth '${d}'\n"
      break
    fi
  done
  if [ ${d} -ge 4 ]; then
    printf "\n"
    printf "  checking depth failed\n"
    read -p "  --> enter patch depth (or Ctrl-C to abort): " d
  fi

  # Store the original list of fiels touched by the patch,
  # removing the $d leading components
  sed -r -e "s:^([^/]+/){${d}}::;" "../diffstat.orig" >"${dst}/${pname}.diffstat.orig"

  # Apply the patch proper, and check it applied cleanly.
  # We can't check with --dry-run because of patches that
  # contain multiple accumulated patches onto a single file.
  printf "  applying patch..."
  if ! patch -g0 -F1 -f -p${d} <"${p}" >"../patch.out" 2>&1; then
    printf " ERROR\n"
    # Revert the patch
    popd >/dev/null 2>&1
    printf "  restoring '${base}'..."
    rm -f "diffstat.tmp"
    rm -rf "${base}"
    mv "${base}.orig" "${base}"
    printf " done\n\n"
    printf "There was an error while applying:\n  -->  ${p}  <--\n"
    printf "'${base}' was restored to the state it was prior to applying this faulty patch.\n"
    printf "Here's the 'patch' command, and its output:\n"
    printf "  ----8<----\n"
    printf "  patch -g0 -F1 -f -p${d} <'${p}'\n"
    cat "patch.out" |(IFS=$(printf "\n"); while read line; do printf "  ${line}\n"; done)
    rm -f "patch.out"
    printf "  ----8<----\n"
    exit 1
  fi
  printf " done\n"

  printf "  removing '.orig' files..."
  find . -type f -name '*.orig' -exec rm -f {} +
  printf " done\n"

  popd >/dev/null 2>&1

  printf "  re-diffing the patch..."
  printf "%s\n\n" "${comment}" >"${dst}/${pname}"
  diff -durN "${base}.orig" "${base}" >>"${dst}/${pname}"
  printf " done\n"

  if [ -n "${diff}" ]; then
    printf "  applying diff filter..."
    filterdiff -x "${diff}" "${dst}/${pname}" >"tmp-diff"
    mv "tmp-diff" "${dst}/${pname}"
    printf " done\n"
  fi

  printf "  creating new patched file list..."
  diffstat -f 4 -r 2 -u -p 1 "${dst}/${pname}"              \
  |head -n -1                                               \
  |awk '{ for(i=NF;i>=NF-5;i--) { $(i) = ""; } print; }'    \
  |sort                                                     \
  >"${dst}/${pname}.diffstat.new"
  printf " done\n"

  printf "  removing temporary files/dirs..."
  rm -f "patch.out"
  rm -f "diffstat.tmp"
  rm -rf "${base}.orig"
  printf " done\n"
done

# Scan all new patches to see if they touch
# more files than the original patches
printf "\nChecking resulting patchset:\n"
for p in "${dst}/"*.patch; do
  pname="$( basename "${p}" )"

  if ! cmp "${p}.diffstat.orig" "${p}.diffstat.new" >/dev/null; then
    printf "  --> '${pname}' differ in touched files <--\n"
  fi
done
printf "  done.\n"