There were two issues with the new smart_makepatch script. 1) use of "local" declaration All function variables were declared "local" during the review. This caused the script to break, at least on FreeBSD 9.2. Given that it's not being seen on 9.3R or later, it might be a bug in Bourne shell that has since been fixed. e.g. This resulted in stderr error on second iteration: local contains=$(grep "^+++ " ${existing_patch} | awk '{x++; print x}') however, this works fine: local contains contains=$(grep "^+++ " ${existing_patch} | awk '{x++; print x}') To be safe, all local variables are assigned with $(<shell cmd>) on separate lines now. 2) The comment extraction was flawed for files that contain multiple patches. It was not counting the hunk lines properly which caused some portion of a patch to be considered as a comment for the next patch. The hunk traversal algorithm has been fixed. Since 1) involved the introduction of local declarations that broke the script and since only Scripts/smart_makepatch.sh is touched, I will piggy-back on the original approval. The fix was tested with devel/nspr, the port listed in the PR, which uses multi-patch files. Approved by: portmgr Differential Revision: D4136 PR: 204725
260 lines
6.5 KiB
Bash
260 lines
6.5 KiB
Bash
#!/bin/sh
|
|
# MAINTAINER: portmgr@FreeBSD.org
|
|
# $FreeBSD$
|
|
|
|
# This script regenerates patches. It conserves existing comments and
|
|
# file names, even if the file name does not meet any current or
|
|
# previous convention. It will keep multiple patches in the same file
|
|
# rather than splitting them into individual files.
|
|
#
|
|
# If a generated patch was not present before, it will create a file
|
|
# name where forward slashes are replaced with an underscore and
|
|
# underscores are appended by another underscore.
|
|
#
|
|
# Limitations:
|
|
# 1) If a file is modified by multiple patches, it will be regenerated
|
|
# as a single patch. That means if two multi-patch files modified
|
|
# the same source file, when regenerated, the source file's patch
|
|
# will only appear in one of patch file.
|
|
# 2) It's possible that trailing garbage at the end of a patch in a
|
|
# multipatch file might corrupt the comment (or be interpreted as
|
|
# a comment) of the following patch. (garbage in, garbage out)
|
|
#
|
|
# Reminder
|
|
# Don't forget to disable post-patch targets before regenerating patches
|
|
# if those targets modify source files (e.g. with sed). You may also
|
|
# want to disable EXTRA_PATCHES as well if that is being used.
|
|
|
|
|
|
if [ -z "${PATCHDIR}" -o -z "${PATCH_WRKSRC}" -o -z "${WRKDIR}" ]; then
|
|
echo "WRKDIR, PATCHDIR, and PATCH_WRKSRC required in environment." >&2
|
|
exit 1
|
|
fi
|
|
|
|
WORKAREA=${WRKDIR}/.makepatch-tmp
|
|
PATCHMAP=${WORKAREA}/pregen.map
|
|
COMMENTS=${WORKAREA}/comments
|
|
REGENNED=${WORKAREA}/regenerated
|
|
DESTDIR=${WORKAREA}/staged
|
|
SAVEDIR=${WORKAREA}/archived-patches
|
|
|
|
case "${STRIP_COMPONENTS}" in
|
|
[123456789]) ;;
|
|
1[0123456789]) ;;
|
|
*) STRIP_COMPONENTS=0
|
|
esac
|
|
|
|
strip_path() {
|
|
local raw_name=$1
|
|
if [ "${STRIP_COMPONENTS}" = "0" ]; then
|
|
echo ${raw_name}
|
|
else
|
|
echo ${raw_name} | awk -v sc=${STRIP_COMPONENTS} -F "/" \
|
|
'{ for (x = sc + 1; x <= NF; x++) { \
|
|
slash = (x>sc+1) ? "/" : ""; \
|
|
printf ("%s%s", slash, $x); \
|
|
}}'
|
|
fi
|
|
}
|
|
|
|
std_patch_filename() {
|
|
local sans_cwd
|
|
local raw_name
|
|
sans_cwd=$(echo $1 | sed 's|^\.\/||')
|
|
raw_name=$(strip_path ${sans_cwd})
|
|
echo patch-$(echo ${raw_name} | sed -e 's|_|&&|g; s|/|_|g')
|
|
}
|
|
|
|
patchdir_files_list() {
|
|
if [ -d "${PATCHDIR}" ]; then
|
|
(cd ${PATCHDIR} && \
|
|
find * -type f -name "patch-*" -maxdepth 0 \
|
|
2>/dev/null | sed -e '/\.orig$/d'
|
|
)
|
|
fi;
|
|
}
|
|
|
|
valid_name() {
|
|
local current_patch_name=$1
|
|
local result=$3
|
|
local first_target
|
|
local testres
|
|
local lps
|
|
first_target=$(echo $2 | sed 's|^\.\/||')
|
|
for lps in __ - + ; do
|
|
testres=patch-$(echo ${first_target} | sed -e "s|/|${lps}|g")
|
|
if [ "${testres}" = "${current_patch_name}" ]; then
|
|
result=${testres}
|
|
break
|
|
fi
|
|
done
|
|
echo ${result}
|
|
}
|
|
|
|
map_existing_patches() {
|
|
mkdir -p ${WORKAREA}
|
|
: > ${PATCHMAP}
|
|
local target
|
|
local future_name
|
|
local std_target
|
|
local P
|
|
local t
|
|
for P in ${old_patch_list}; do
|
|
target=$(cd ${PATCHDIR} && \
|
|
grep "^+++ " ${P} | awk '{print $2}'
|
|
)
|
|
# For single patches, we honor previous separators, but use
|
|
# a standard patch name if the current patch name does not
|
|
# conform. However, if two or more patches are contained in
|
|
# single file, then we do *NOT* rename the file
|
|
future_name=
|
|
for t in ${target}; do
|
|
if [ -n "${future_name}" ]; then
|
|
future_name=${P}
|
|
break;
|
|
fi
|
|
std_target=$(std_patch_filename ${t})
|
|
future_name=$(valid_name ${P} ${t} ${std_target})
|
|
done
|
|
for t in ${target}; do
|
|
std_target=$(std_patch_filename ${t})
|
|
echo "${future_name} ${std_target}" >> ${PATCHMAP}
|
|
done
|
|
done
|
|
}
|
|
|
|
extract_comment_from_patch() {
|
|
local existing_patch=${PATCHDIR}/$1
|
|
local contains
|
|
local rawname
|
|
local fname
|
|
local num
|
|
contains=$(grep "^+++ " ${existing_patch} | awk '{x++; print x}')
|
|
for num in ${contains}; do
|
|
rawname=$(grep "^+++ " ${existing_patch} | \
|
|
awk -v num=${num} '{x++; if (x==num) print $2}')
|
|
fname=$(std_patch_filename $rawname)
|
|
awk -v num=${num} '\
|
|
BEGIN { done=0; x=0; hunk=0; looking=(num==1) } \
|
|
{ \
|
|
if (!done) { \
|
|
if ($1 == "@@") { \
|
|
split ($2,a,","); \
|
|
split ($3,b,","); \
|
|
hca = a[2];
|
|
hcb = a[3];
|
|
hunk = 1;
|
|
} else if (hunk) { \
|
|
first=substr($1,1,1); \
|
|
if (first == "-") { hca-- } \
|
|
else if (first == "+") { hcb-- } \
|
|
else {hca--; hcb--} \
|
|
if (hca == 0 && hcb == 0) {hunk = 0} \
|
|
} \
|
|
if ($1 == "---") { \
|
|
x++; \
|
|
if (x == num) { done = 1 } \
|
|
if (x + 1 == num) { looking = 1 } \
|
|
} else if (!hunk && looking) { \
|
|
if ($1!="diff" && $1!="index" && $1!="+++") {\
|
|
print $0 \
|
|
} \
|
|
} \
|
|
} \
|
|
}' ${existing_patch} > ${COMMENTS}/${fname}
|
|
done
|
|
}
|
|
|
|
extract_comments() {
|
|
mkdir -p ${COMMENTS}
|
|
rm -f ${COMMENTS}/*
|
|
local P
|
|
for P in ${old_patch_list}; do
|
|
extract_comment_from_patch ${P}
|
|
done
|
|
}
|
|
|
|
regenerate_patches() {
|
|
mkdir -p ${REGENNED}
|
|
rm -f ${REGENNED}/*
|
|
[ ! -d "${PATCH_WRKSRC}" ] && return
|
|
|
|
local F
|
|
local NEW
|
|
local OUT
|
|
local ORIG
|
|
local new_list
|
|
new_list=$(cd ${PATCH_WRKSRC} && \
|
|
find -s * -type f -name '*.orig' 2>/dev/null)
|
|
(cd ${PATCH_WRKSRC} && for F in ${new_list}; do
|
|
ORIG=${F#./}
|
|
NEW=${ORIG%.orig}
|
|
cmp -s ${ORIG} ${NEW} && continue
|
|
OUT=${REGENNED}/$(std_patch_filename ${NEW})
|
|
TZ=UTC diff -udp ${ORIG} ${NEW} | sed \
|
|
-e '/^---/s|\.[0-9]* +0000$| UTC|' \
|
|
-e '/^+++/s|\([[:blank:]][-0-9:.+]*\)*$||' \
|
|
> ${OUT} || true
|
|
done)
|
|
}
|
|
|
|
get_patch_name() {
|
|
awk -v name=$1 '\
|
|
{ if ($2 == name) \
|
|
{ \
|
|
if (!done) { print $1 }; \
|
|
done = 1; \
|
|
} \
|
|
} \
|
|
END { if (!done) print name }' ${PATCHMAP}
|
|
}
|
|
|
|
stage_patches() {
|
|
mkdir -p ${DESTDIR}
|
|
rm -f ${DESTDIR}/*
|
|
local P
|
|
local name
|
|
local patch_list
|
|
patch_list=$(cd ${REGENNED} && find * -name "patch-*" 2>/dev/null)
|
|
for P in ${patch_list}; do
|
|
name=$(get_patch_name ${P})
|
|
[ -e ${COMMENTS}/${P} ] && cat ${COMMENTS}/${P} \
|
|
>> ${DESTDIR}/${name}
|
|
if [ "${P}" = "${name}" ]; then
|
|
echo "Generated ${P}"
|
|
else
|
|
echo "Generated ${P} >> ${name} (legacy)"
|
|
fi
|
|
cat ${REGENNED}/${P} >> ${DESTDIR}/${name}
|
|
done
|
|
}
|
|
|
|
conserve_old_patches() {
|
|
mkdir -p ${SAVEDIR}
|
|
rm -f ${SAVEDIR}/*
|
|
[ -z "${old_patch_list}" ] && return
|
|
|
|
local P
|
|
for P in ${old_patch_list}; do
|
|
mv ${PATCHDIR}/${P} ${SAVEDIR}/${P}
|
|
done
|
|
echo "The previous patches have been placed here:"
|
|
echo ${SAVEDIR}
|
|
}
|
|
|
|
install_regenerated_patches() {
|
|
local testdir=$(find ${DESTDIR} -empty)
|
|
if [ -z "${testdir}" ]; then
|
|
mkdir -p ${PATCHDIR}
|
|
find ${DESTDIR} -type f -exec mv {} ${PATCHDIR}/ \;
|
|
fi
|
|
}
|
|
|
|
old_patch_list=$(patchdir_files_list)
|
|
|
|
map_existing_patches
|
|
extract_comments
|
|
regenerate_patches
|
|
stage_patches
|
|
conserve_old_patches
|
|
install_regenerated_patches
|