summaryrefslogtreecommitdiffstats
path: root/scripts/git-ptx-patches
blob: b27dbe7905d9074ec71de48ef7972866697df394 (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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
#!/bin/bash

# Create a pristine environment to minimize unnecessary fuzz when different
# users use git-ptx-patches on the same patch stack. That is, don't load any
# config files, and pin down environment variables which could influence git's
# behaviour or patch output.
pristine_git() {
	# Notes from the git(1) manpage:
	# - GIT_DIFF_OPTS takes takes precedence over -U command line parameter
	HOME=/nonexistent \
	XDG_CONFIG_HOME=/nonexistent \
	GIT_CONFIG_NOSYSTEM=true \
	GIT_DIFF_OPTS="-u3" \
	git "$@"
}
GIT="pristine_git"

PTX_PATCHES_HEADER="# generated by git-ptx-patches"

function _md5sum() {
	local sum=$(md5sum)
	echo "# $sum git-ptx-patches magic"
}

if [ ! -L .ptxdist/patches ]; then
	echo "Error: This is not patched by ptxdist. Aborting."
	exit 1
fi

if [ ! -L .ptxdist/series ]; then
	echo "Error: .ptxdist/series must be a symbolic link. Aborting."
	exit 1
fi

remove_old=no
tag=
numbered_patches=true
all_tags=false

if grep -q "$PTX_PATCHES_HEADER" .ptxdist/series; then
	echo "Found series file generated by git-ptx-patches."
	lines=$(wc -l < .ptxdist/series)
	lines=$[lines-1]
	magic=$(head -n$lines .ptxdist/series | _md5sum)
	if grep -q "^$magic" .ptxdist/series; then
		remove_old=yes
	else
		echo "Warning: .ptxdist/series was modified."
	fi
fi

if [ "x$1" = "x--force-remove" ]; then
	remove_old="force"
	shift
fi

while getopts "aft:n" opt; do
	case "${opt}" in
		a)
			all_tags=true
			;;
		f)
			remove_old="force"
			;;
		t)
			tag="${OPTARG}"
			;;
		n)
			numbered_patches=false
			;;
	esac
done
shift $((${OPTIND} - 1))


tags=( $(sed -n 's/^#tag:\([^ ]*\) .*/\1/p' .ptxdist/series 2>/dev/null) )

# default to the first tag if none was specified,
# use 'base' if there is no tag at all
if [ -z "${tag}" ]; then
	tag="${tags[0]}"
fi
if [ -z "${tag}" ]; then
	tag="base"
fi

# only look for the second tag if there are tags in the series
# otherwise assume all commits should be exported
if [ ${#tags[*]} -gt 0 ]; then
	tag2=$(awk -F "[ :]" "
/^#tag:/ {
	if (state == 1) {
		print \$2
		state=2
	}
}
/^#tag:${tag}( |\$)/ {
	state=1
}
END {
	if (!state)
		exit 1
}" < .ptxdist/series)

	if [ $? -ne 0 ]; then
		echo "Failed to find tag '${tag}' in the series"
		exit 1
	fi
else
	tags=( "${tag}" )
	tag2=""
fi

range="${tag}..${tag2}"

if "${all_tags}" ; then
	args=()
	if [ "${remove_old}" = "force" ]; then
		args[${#args[*]}]="-f"
	fi
	if ! "${numbered_patches}"; then
		args[${#args[*]}]="-n"
	fi
	for tag in "${tags[@]}"; do
		echo "Updating patches for '${tag}'..."
		"$0" "${args[@]}" -t "${tag}" || break
		echo
	done
	exit
fi

echo "$PTX_PATCHES_HEADER" > .ptxdist/series.0
:> .ptxdist/series.1
touch .ptxdist/series.append
if grep -q "^#tag:" .ptxdist/series .ptxdist/series.append; then
	tagline=$(cat .ptxdist/series .ptxdist/series.append | grep "#tag:${tag}")
	t=$(echo "${tagline}"|cut -d' ' -f1)
	if [ "#tag:${tag}" == "${t}" ]; then
		tagopt=$(echo "${tagline}"|cut -d' ' -s -f2-)
		sed -e "/$PTX_PATCHES_HEADER/d" -n \
			-e '/git-ptx-patches magic/d' \
			-e "0,/#tag:${tag}/p" \
			.ptxdist/series .ptxdist/series.append >> .ptxdist/series.0
		# Remove patches before #tag:${tag} so they don't get rm'd with remove_old=yes
		sed -i --follow-symlinks "0,/#tag:${tag}/d" .ptxdist/series
		if [ -n "${tag2}" ]; then
			sed -n -e "/#tag:${tag2}/,/git-ptx-patches magic/p" .ptxdist/series > .ptxdist/series.1
			sed -i "/git-ptx-patches magic/d" .ptxdist/series.1
			sed -i --follow-symlinks "/#tag:${tag2}/,/git-ptx-patches magic/d" .ptxdist/series
		fi
	else
		echo "series contains #tag:* lines, but could not find #tag:${tag} line in series. Aborting."
		exit 1
	fi
else
	if [ "${tag}" != "base" ]; then
		echo "When using series with no #tag:* lines, you must use base tag."
		exit 1
	fi
	echo "#tag:${tag} --start-number 1" >> .ptxdist/series.0
fi
rm .ptxdist/series.append

case "$remove_old" in
	"no") ;;
	"yes")
		echo "Removing old patches ..."
		while read patch para; do
			case "${patch}" in
				""|"#"*) continue ;;
				*) rm .ptxdist/patches/$patch ;;
			esac
		done < .ptxdist/series
		;;
	"force")
		echo "Removing old patches (forced) ..."
		find .ptxdist/patches/ | while read file; do
			case "$file" in
				".ptxdist/patches/") continue ;;
				".ptxdist/patches/series") continue ;;
				".ptxdist/patches/autogen.sh") continue ;;
				*)
					if grep -q "${file##\.ptxdist/patches/}" .ptxdist/series.{0,1}; then
						echo "Keep base patch ${file}"
					else
						rm -rf "$file"
					fi
					;;
			esac
		done
		;;
esac

# git-format-patch --no-signature is supported since git 1.7.2
if ${GIT} format-patch -h 2>&1 | grep -q signature; then
	GIT_EXTRA_ARGS="--no-signature"
fi

# git-format-patch --notes is supported since git 1.7.6, but actually you want
# git 1.8.1-rc0 to get the notes below the --- marker
if man git-format-patch | grep -e --notes > /dev/null; then
	GIT_EXTRA_ARGS="$GIT_EXTRA_ARGS --notes"
fi

GIT_EXTRA_ARGS="$GIT_EXTRA_ARGS --summary --stat=80"

${GIT} format-patch -N $GIT_EXTRA_ARGS ${tagopt} -o .ptxdist/patches/ ${range} | while read patch; do
	if "$numbered_patches"; then
		patchname="${patch#.ptxdist/patches/}"
	else
		patchname="${patch#.ptxdist/patches/[0-9][0-9][0-9][0-9]-}"
		mv -n "$patch" ".ptxdist/patches/$patchname"
	fi
	echo "$patchname"
done > .ptxdist/series.auto

cat .ptxdist/series.0 .ptxdist/series.auto .ptxdist/series.1 > .ptxdist/series
cat .ptxdist/series | _md5sum >> .ptxdist/series

# The first line of the patch is 'From <some-git-hash> ...'
# remove it to avoid unnecessary changes in the patch files.
while read patch para; do
	# There are no comments or empty lines in series.auto, so no need to
	# handle these. Also be a bit cautious to only remove lines matching
	# "^From ".
	sed -i '1{/^From /d}' ".ptxdist/patches/$patch"
done < .ptxdist/series.auto

find .ptxdist/patches/ ! -type d | sed -e 's,^.ptxdist/patches/,,' | \
while read patch para; do
	case "$patch" in
		"series"|"autogen.sh") continue ;;
		*) ;;
	esac
	if grep -q "$patch" .ptxdist/series.auto; then
		# ok, this is one of the patches we just touched
		:
	elif grep -q "$patch" .ptxdist/series.{0,1}; then
		echo "Base patch \"$patch\"!"
	else
		echo "Old patch \"$patch\"!"
	fi
done | sort