summaryrefslogtreecommitdiff
path: root/pwx/pwx_git_applier.sh
blob: 5db015373640f7999072e7b3dab40a3445c83a8f (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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#!/bin/bash
#
# (c) 2012 - 2016 PrydeWorX
#     Sven Eden, PrydeWorX - Bardowick, Germany
#     yamakuzure@users.sourceforge.net
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# History and ChangePWX_ERR_LOG:
#  Version   Date        Maintainer      Change(s)
#  0.0.1     2012-12-29  sed, PrydeWorX  First Design.
#  0.0.2     2013-01-08  sed, PrydeWorX  First working version.
#  0.1.0     2013-01-10  sed, PrydeWorX  Initial private release.
#  0.1.1     2013-06-01  sed, PrydeWorX  Use functions from pwx_git_funcs.sh.
#  0.2.0     2014-09-02  sed, PrydeWorX  Use GNU enhanced getopt for command
#                                         line parsing.
#  0.3.0     2016-11-18  sed, PrydeWorX  Added option -T|--theirs to force
#                                         merge errors to be resolved by
#                                         throwing away all local changes.
#  0.3.1     2017-03-22  sed, PrydeWorX  Show full part of a patch that is to be
#                                         deleted after manually fixing merge
#                                         conflicts.
#                                        Show tried command if we fail with
#                                        full rejects.
#  0.4.0     2017-04-24  sed, PrydeWorX  Remove some remote-is-right-automatisms
#  0.5.0     2017-07-04  sed, PrydeWorX  If normal processing fails, use
#                                        check_tree.pl to generate diffs for the
#                                        specific commit, and apply them after
#                                        letting the user have a look.

# Common functions
PROGDIR="$(readlink -f $(dirname $0))"
source ${PROGDIR}/pwx_git_funcs.sh

# Version, please keep this current
VERSION="0.5.0"


# Global values to be filled in:
PATCH_DIR="${PROGDIR}/patches"
TAG_TO_USE=""


# Editor to edit individual patches when needed
PWX_EDIT=/usr/bin/kate


# The options for the git am command:
GIT_AM_OPTS="--committer-date-is-author-date"


# Options and the corresponding help text
OPT_SHORT=hi:T
OPT_LONG=help,input:,theirs

HELP_TEXT="Usage: $0 [OPTIONS] <source dir> <tag>

  Take all commit patches from the input directory and
  apply them to the local tree.
  They are assumed to be from tag <tag> of some source
  tree.

OPTIONS:
  -h|--help           Show this help and exit.
  -i|--input <path> : Path to where to patches are.
                      The default is to read from the
                      subdirectory 'patches' of the
                      current directory.
Notes:
  - When the script succeeds, it adds a line to the commit
  - file \"${PWX_COMMIT_FILE}\" of the form:
    <tag>-merged <last used commit>
"


# =========================================
# === Use getopt (GNU enhanced version) ===
# =========================================

# Check the version first, so we do not run into trouble
getopt --test > /dev/null
if [[ $? -ne 4 ]]; then
	echo "ERROR: getopt is not the GNU enhanced version."
	exit 1
fi

# Store the output so we can check for errors.
OPT_PARSED=$(getopt --options $OPT_SHORT --longoptions $OPT_LONG --name "$0" -- "$@")

if [[ $? -ne 0 ]]; then
	# getopt has already complained about wrong arguments to stdout
	exit 2
fi

# Use eval with "$OPT_PARSED" to properly handle the quoting
eval set -- "$OPT_PARSED"

# --------------------
# --- Handle input ---
# --------------------
while true; do
	case "$1" in
		-h|--help)
			echo "$HELP_TEXT"
			exit 0
			;;
		-i|--input)
			PATCH_DIR="$2"
			shift 2
			;;
		--)
			shift
			break
			;;
		*)
			echo "Something went mysteriously wrong."
			exit 3
			;;
	esac
done

# At this point we must have <source dir> and <tag> left
if [[ $# -ne 2 ]]; then
    echo "$HELP_TEXT"
    exit 4
fi

# So these must be it:
SOURCE_DIR="$1"
TAG_TO_USE="$2"

if [[ ! -d "$SOURCE_DIR" ]]; then
	echo "$SOURCE_DIR does not exist"
	echo
    echo "$HELP_TEXT"
    exit 5
fi


# ===========================================
# === The PATCH_DIR directory must exist. ===
# ===========================================
if [[ -n "$PATCH_DIR" ]]; then
	if [[ ! -d "$PATCH_DIR" ]]; then
		echo "ERROR: $PATCH_DIR does not exist"
		exit 4
	fi
else
	echo "You have set the patch directory to be"
	echo "an empty string. Where should I find the"
	echo "patches, then?"
	exit 5
fi


# =============================================================
# === We need two file lists.                               ===
# === A) The list of root files.                            ===
# ===    These have not been used to generate commit        ===
# ===    patches and must be ignored by 'git am'.           ===
# === B) The patches to work with.                          ===
# =============================================================
echo -n "Building file lists ..."

# --- File list a) Root files
declare -a root_files=( $(find ./ -mindepth 1 -maxdepth 1 -type f \
	-not -name '*~' -and \
	-not -name '*.diff' -and \
	-not -name '*.orig' -and \
	-not -name '*.bak'  -printf "%f ") )

# --- File list b) Patch files
# --- Here we might find patches that failed for single files. Those
# --- must not clutter the list, they wouldn't apply anyway.
declare -a patch_files=( $(find "$PATCH_DIR"/ -mindepth 1 -maxdepth 1 -type f \
	-name '????-*.patch' -and -not -name '*-failed_patch_for-*' \
	-printf "%f\n" | sort -n) )
echo " done"

# --- Add an error log file to the list of temp files
PWX_ERR_LOG="/tmp/pwx_git_applier_$$.log"
add_temp "$PWX_ERR_LOG"
touch $PWX_ERR_LOG || die "Unable to create $PWX_ERR_LOG"


# ===================================================
# === Build a basic exclude string to begin with. ===
# ===================================================
basic_excludes=""
for e in "${root_files[@]}" ; do
	if [[ "" = "$(echo -n "$basic_excludes" | \
			grep -P "^\sexclude=$e")" ]]; then
		basic_excludes+=" --exclude=$e"
	fi
done


# ============================================
# === Main loop over the found patch files ===
# ============================================
for p in "${patch_files[@]}" ; do

	# For further processing the number and the full path
	# are needed.
	pnum=${p%%-*}
	psrc="${PATCH_DIR}/${p}"

	# We start with normal 3-way-patching
	GIT_USE_TWP="-3 "


	# ====================================================
	# === Step 1) Reset the exclude list of root files ===
	# ====================================================
	excludes="$basic_excludes"


	# ==============================================
	# === Step 2) Start applying the patch as is ===
	# ==============================================
	echo -n "Applying $p ..."
	git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG
	res=$?
	echo " done [$res]"


	# ===========================================================
	# === Step 3) Look for reasons to not use 3-way patching  ===
	# ===         Symptom   : "could not build fake ancestor" ===
	# ===         Reason    : No common root can be built     ===
	# ===         Resolution: Do not use "-3" option          ===
	# ===========================================================
	if [[ 0 -ne $res ]] && \
	   [[ $(grep -c "could not build fake ancestor" $PWX_ERR_LOG) -gt 0 ]];
	   then
		echo -n "Trying again without 3-way-patching ..."
		GIT_USE_TWP=""
		git am --abort 1>/dev/null 2>&1
		git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG
		res=$?
		echo " done [$res]"
	fi


	# ====================================================================
	# === Step 4) Look for more files to exclude                       ===
	# ===         Symptom   : "error: <file>: does not exist in index" ===
	# ===         Reason    : The file to patch isn't there            ===
	# ===         Resolution: Exclude the offending file(s)            ===
	# ====================================================================
	if [[ 0 -ne $res ]] && \
	   [[ $(grep -c "does not exist in index" $PWX_ERR_LOG) -gt 0 ]];
	   then
		declare -a nff_files=( $( \
			grep "does not exist in index" $PWX_ERR_LOG | \
			cut -d ':' -f 2) )

		for nff in "${nff_files[@]}" ; do
			echo "Excluding $nff ..."
			excludes+=" --exclude=$nff"

			# A special an evil case is a rename copy of something non-existent.
			# git am then needs *two* excludes, one for the (non-existing)
			# source and one for the still not existing target.
			nff_tgt="$(grep -A 1 "copy from $nff" $psrc | \
				grep "copy to " | \
				cut -d ' ' -f 3)"
			if [[ "x" != "x$nff_tgt" ]]; then
				echo "Excluding $nff_tgt ..."
				excludes+=" --exclude=$nff_tgt"
			fi
		done

		echo -n "Trying again without non-existing files ..."
		git am --abort 1>/dev/null 2>&1
		git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc 1>/dev/null 2>$PWX_ERR_LOG
		res=$?
		echo " done [$res]"
		unset nff_files
	fi


	# ====================================================================
	# === Step 5) If this still doesn't work, let check_tree.pl check  ===
	# ===         out the commit and build diffs for the touched files ===
	# ===         against the tree. Then let the user have a chance to ===
	# ===         decide what to apply before continuing.              ===
	# ====================================================================
	if [[ 0 -ne $res ]] && \
	   [[ $(grep -c "patch does not apply" $PWX_ERR_LOG) -gt 0 ]];
	   then
		res=0
		xCommit="$(head -n 1 $psrc | cut -d ' ' -f 2)"
		echo "git am failed to apply the patch automatically:"
		echo -e "\n--------"
		cat $PWX_ERR_LOG
		echo -e "--------\n"
		echo "Building patch diffs for commit $xCommit ..."

		# We need to know which files are relevant:
		xFiles=""
		xPatches=""
		for pF in $(grep "^diff \--git" $psrc | cut -d ' ' -f 3,4 | \
				while read a b ; do \
					echo -e "$a\n$b" | cut -d '/' -f 2- ; \
				done | sort -u) ; do
			xFiles+="-f $pF "
			xPatches+="${PROGDIR}/patches/${pF//\//_}.patch "
		done

		# If we have our lists, do it:
		if [[ "x" != "x$xFiles" ]] && [[ "x" != "x$xPatches" ]]; then
			$(PROGDIR)/check_tree.pl -c $xCommit $xFiles "$SOURCE_DIR"
			
			# Let's see which patches got built
			xResult=""
			for xP in $xPatches ; do
				echo -n "Checkin $xP : "
				if [[ -f "$xP" ]]; then
					echo "present"
					xResult+="$xP "
				else
					echo "missing"
				fi
			done
			
			# So, if no patches have been found, this whole thing
			# can be skipped.
			if [[ "x" = "x$xResult" ]]; then
				echo "No relevant patches found."
				echo -n "Skipping $psrc"
				git am --skip
			else
				# Okay, something to do found.
				echo "Please edit/delete the diffs as they are"
				echo "and then close $PWX_EDIT to continue"
				echo " ... to skip, just delete all patches"
				$PWX_EDIT $xResult 1>/dev/null 2>&1
				
				# Apply patch-by-patch and add to git.
				have_patch=0
				for xP in $xResult ; do
					while [[ -f "$xP" ]]; do
						have_patch=1
						echo "Applying $xP ..."
						patch -p 1 -i $xP
						if [[ 0 -ne $? ]]; then
							echo "Something is wrong with $xP"
							$PWX_EDIT $xP 1>/dev/null 2>&1
						else
							rm -f $xP
						fi
					done
				done
				
				if [[ 0 -eq $have_patch ]]; then
					echo "All patches deleted."
					git am --skip
				else
					git add man src
					
					git status
					echo "Patch: $psrc"
					echo -e -n "\nDoes this look in order to you? [Y/n]"
					read answer
					if [[ "xn" = "x$answer" ]]; then
						echo "Okay, then see what you can do"
						exit 0
					fi
					
					echo -n "Finishing $psrc ..."
					git am --continue 1>/dev/null 2>$PWX_ERR_LOG
					res=$?
					echo " done [$res]"
				fi
			fi
		else
			echo "ERROR: Could not get file list from $psrc"
			echo
			echo "You have to do this by hand. Sorry."
			exit 1
		fi
	fi


	# ===========================================
	# === Step 6) Exit if we couldn't help it ===
	# ===========================================
	if [[ $res -ne 0 ]]; then
		echo -e "\n ==> $psrc can not be applied"
		echo " ==> The command that failed was:"
		echo "git am $GIT_USE_TWP$GIT_AM_OPTS$excludes < $psrc"
		echo -e " ==> The Error message is:\n"
		cat $PWX_ERR_LOG
		echo -e "\nPlease try to apply the remaining parts by hand"
		echo "and then finish with 'git am --continue'"
		cleanup
		exit $res
	fi


	# ==================================
	# === Step 7) Clean up behind us ===
	# ==================================

	# The patch is applied or irrelevant, remove it.
	rm -f $psrc
	
	# Remove merge debris
	find -iname '*.orig' | xargs rm -f

	# Remove temp files
	cleanup
done