1# vim:set syntax=sh noet ts=8 sw=8:
2# $Id$
3#
4# portshaker.subr
5# 	functions used by portshaker(8)
6#
7
8if [ -z "${_portshaker_subr_loaded}" ]; then
9
10_portshaker_subr_loaded="YES"
11verbose="0"
12
13if [ "${_portshaker_load_config}" != "no" ]; then
14	config_dir="${portshaker_config_dir:=@@ETCDIR@@}"
15	. ${config_dir}/portshaker.conf
16fi
17
18# User tunable variables
19
20#CP_FLAGS="-v"
21#CSUP_FLAGS=""
22CVS="${CVS:=cvs}"
23CVS_FLAGS="${CVS_FLAGS:=-q}"		# Quiet
24DIFF_FLAGS="${DIFF_FLAGS:=-uN}"		# Unified diff, New files
25GIT="${GIT:=git}"
26HG="${HG:=hg}"
27MAKE="${MAKE:=make}"
28PAGER="${PAGER:=less}"
29PATCH_FLAGS="${PATCH_FLAGS:=-s}"	# Silent
30#PORTSNAP_FLAGS=""
31#RM_FLAGS=""
32RSYNC=${RSYNC:=rsync}
33RSYNC_FLAGS=""
34SVN="${SVN:=svn}"
35#SVN_FLAGS=""
36SVNADMIN="${SVNADMIN:=svnadmin}"
37#SVNADMIN_FLAGS=""
38VCS_ID_KEYWORDS="${VCS_ID_KEYWORDS:=FreeBSD Id MCom}"
39
40# Tinderbox related variables
41PB="${PB:=/usr/local/tinderbox}"
42TC="${TC:=${PB}/tc}"
43
44# Use pkg(8) if the old pkg_* tools are not found or if WITH_PKGNG defined in
45# make.conf
46if [ -z `/usr/bin/whereis -q -b pkg_version` ]; then
47	PKG_VERSION="pkg version"
48elif [ -f "/etc/make.conf" ]; then
49	case `make -f "/etc/make.conf" -V WITH_PKGNG` in
50	[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
51		PKG_VERSION="pkg version"
52		;;
53	esac
54fi
55PKG_VERSION="${PKG_VERSION:=pkg_version}"
56
57# Convenient variables
58DIFF_IGNORE_FLAGS_MK="--ignore-matching-lines='\$\(FreeBSD\|Id\|MCom\)\$'"
59DIFF_IGNORE_FLAGS_PORT="--ignore-space-change --ignore-blank-lines --ignore-matching-lines='^\(BROKEN\|IGNORE\|PORTSCOUT\)='"
60RSYNC_ARGS="--archive --delete"
61RSYNC_EXCLUDE="--exclude packages --exclude distfiles"
62
63_keywords="update clone_to copy_to merge_to"
64_portshaker_arg=""
65
66_pretend=0
67
68_use_zfs=0
69use_zfs=${use_zfs:=0}
70
71case $use_zfs in
72[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
73	_use_zfs=1
74	;;
75esac
76
77_fail_on_conflict=0
78fail_on_conflict=${fail_on_conflict:=0}
79
80case $fail_on_conflict in
81[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
82        _fail_on_conflict=1
83        ;;
84esac
85
86source_zfs_compression=${source_zfs_compression:=lz4}
87target_zfs_compression=${target_zfs_compression:=off}
88
89#
90#	functions
91#	---------
92
93# err
94# 	Display error message to stderr and exit.
95# 	$1 - Exit code.
96#
97err()
98{
99	exitval=$1
100	shift
101
102	printf "\\033[31;3m[Error $(date +%T)]\\033[0m $*\\n" >&2
103	exit ${exitval}
104}
105
106# warn
107# 	Display warning message to stderr.
108#
109warn()
110{
111	printf "\\033[33;3m[Warn  $(date +%T)]\\033[0m $*\\n" >&2
112}
113
114# info
115# 	Display information message to stderr.
116#
117info()
118{
119	case ${portshaker_info} in
120		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
121			printf "\\033[32;3m[Info  $(date +%T)]\\033[0m $*\\n" >&2
122	esac
123}
124
125# debug
126# 	Display debug message to stderr.
127#
128debug()
129{
130	case ${portshaker_debug} in
131		[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|[Oo][Nn]|1)
132			printf "\\033[34;3m[Debug $(date +%T)]\\033[0m $*\\n" >&2
133	esac
134}
135
136# portshaker_usage
137# 	Display command line arguments for portshaker scripts.
138#
139portshaker_usage()
140{
141	(
142	echo -n "usage: $0 "
143	case ${_portshaker_arg} in
144		update)
145			echo "update"
146			;;
147		clone_to)
148			echo "clone_to [-p] -t target [-z zfs_target_dataset]"
149			echo "Supported arguments"
150			echo "  -p  Show what is going to happen but do not do anything (pretend mode)"
151			echo "  -t  Target directory to clone the ports tree to"
152			echo "  -z  ZFS dataset to use and mount as target directory"
153			;;
154		copy_to)
155			echo "copy_to -t target"
156			echo "Supported arguments"
157			echo "  -t  Target directory to copy the ports tree to"
158			;;
159		merge_to)
160			echo "merge_to [-ap] [-b build] -m master -t target"
161			echo "Supported arguments:"
162			echo "  -a  Automatically copy port if files where touched but version is unchanged"
163			echo "  -b  Tinderboy build to add copied packages to"
164			echo "  -m  Master source ports tree (for comparing Mk/* files)"
165			echo "  -p  Show what is going to happen but do not do anything (pretend mode)"
166			echo "  -t  Target directory to merge the ports tree in"
167			;;
168		*)
169			echo "($(echo $_keywords | sed -e "s/ /|/g"))"
170			;;
171	esac
172	) 1>&2
173	exit 1
174}
175
176# read_makefile_value
177# 	Reads the value of a variable from a Makefile in the current
178# 	directory.
179# 	Try to do it without calling make(1) at first to avoid some overhead,
180# 	but fallback to it if:
181# 	  - the variable is empty;
182# 	    (e.g. the Makefile include another Makefile that sets the value)
183# 	  - the variable value starts with '$'
184# 	    (e.g. PORTREVISION= ${OPENLDAP_PORTREVISION})
185# 	In case of failure, return "0"
186#
187# 	$1 - Name of the variable to read.
188read_makefile_value()
189{
190	value=`awk 'BEGIN { r = ""} /^'$1'\??=/ { split($0, a, /=[[:space:]]*/); r = a[2]; split(r, b, /[[:space:]]*#/); r = b[1] } END { print r }  ' Makefile`
191	if [ -z "${value}" -o "${value%${value#?}}" = '$' ]; then
192		debug "Can't read variable '$1' from Makefile in '$PWD'.  Falling back to make(1)."
193		value=`make -V $1`
194		if [ -z "${value}" ]; then
195			warn "Can't read variable '$1' from Makefile in '$PWD'.  '0' substitued."
196			value="0"
197		fi
198	fi
199	echo $value
200}
201
202# read_file_vcs_keyword
203# 	Look for VCS $Keyword$ information string in the provided file.
204# 	$1 - Filename
205# 	$1 - Keyword
206read_file_vcs_keyword()
207{
208	grep -o "\\\$$2:[^\$]*\\\$" "$1"
209}
210
211# newer_or_same_vcs_id
212# 	Compare VCS $Ids...$ in files and returns 0 unless the version is going
213# 	backwards in which case 1 is returned.
214# 	$1 - File to compare
215# 	$2 - Reference File
216newer_or_same_vcs_id()
217{
218	_ret=0
219
220	for _keyword in ${VCS_ID_KEYWORDS}; do
221		new_rev=$(read_file_vcs_keyword "$1" ${_keyword} | cut -d' ' -f3)
222		old_rev=$(read_file_vcs_keyword "$2" ${_keyword} | cut -d' ' -f3)
223
224		if [ -z "${new_rev}" -o -z "${old_rev}" ]; then
225			continue
226		fi
227
228		_ret=`echo "${new_rev} ${old_rev}" | awk ' {
229			if ($1 > $2) {
230				print 1;
231			} else if ($1 == $2) {
232				print 0;
233			} else {
234				print -1;
235			}
236		}'`
237
238	done
239
240	#echo ${_ret}
241
242	if [ ${_ret} -eq -1 ]; then
243		return 1
244	else
245		return 0
246	fi
247}
248
249# newer_portepoch
250# 	Compare PORTEPOCH of two ports (``new'' and ``old'') and assert
251# 	that the ``new'' port is newer than the ``old'' one.
252# 	$1 - Directory of the ``new'' port.
253# 	$2 - Directory of the ``old'' port.
254#
255newer_portepoch()
256{
257	new_epoch=$(cd $1 && read_makefile_value PORTEPOCH)
258	old_epoch=$(cd $2 && read_makefile_value PORTEPOCH)
259
260	if [ -z "${old_epoch}" -a -z "${new_epoch}" ]; then
261		return 1
262	fi
263	[ "${old_epoch}" -lt "${new_epoch}" ]
264}
265
266# newer_portversion
267# 	Compare PORTVERSION of two ports (``new'' and ``old'') and assert
268# 	that the ``new'' port is newer than the ``old'' one.
269# 	$1 - Directory of the ``new'' port.
270# 	$2 - Directory of the ``old'' port.
271#
272newer_portversion()
273{
274	new_version=$(cd $1 && read_makefile_value PORTVERSION).
275	old_version=$(cd $2 && read_makefile_value PORTVERSION).
276
277	if [ ${old_version} = "0." -a ${new_version} = "0." ]; then
278		return 0 # Always update
279	fi
280
281	case $(${PKG_VERSION} -t ${old_version} ${new_version}) in
282		'<') return 0;;
283		'=') return 1;;
284		'>') return 2;;
285		*) err 1 "Unsupported '${PKG_VERSION}' reply";;
286	esac
287}
288
289# newer_portrevision
290# 	Compare PORTREVISION of two ports (``new'' and ``old'') and assert
291# 	that the ``new'' port is newer than the ``old'' one.
292# 	$1 - Directory of the ``new'' port.
293# 	$2 - Directory of the ``old'' port.
294#
295newer_portrevision()
296{
297	new_revision=$(cd $1 && read_makefile_value PORTREVISION)
298	old_revision=$(cd $2 && read_makefile_value PORTREVISION)
299
300	if [ -z "${old_revision}" -a -z "${new_revision}" ]; then
301		return 1
302	fi
303
304	if [ "${old_revision:=0}" -lt "${new_revision:=0}" ]; then
305		return 0
306	elif [ "${old_revision:=0}" -gt "${new_revision:=0}" ]; then
307		return 2
308	else
309		return 1
310	fi
311}
312
313# unsubstitute_keywords
314#	Unexpand keywords in source file, writing the new file in dest file.
315#	$1 -- source file
316#	$2 -- dest file
317unsubstitute_keywords()
318{
319	sed -E -e 's#\$('$(echo ${VCS_ID_KEYWORDS} | sed -e 's/ /|/g')'):[^\$]*\$#$\1$#g' < $1 > $2
320}
321
322# compare_noid
323#	Compare provided files ignoring any VCS Ids. Returns 0 if files are
324#	identiqual.
325#	$1 -- Original file
326#	$2 -- New file
327compare_noid()
328{
329	ret=1
330	original="$1"  new="$2"
331
332	if [ -e "${original}" -a -e "${new}" ]; then
333		cmp -s "${original}" "${new}"
334		ret=$?
335		if [ ${ret} -ne 0 ]; then
336			# Files are different: un-substitutes keywords and try again.
337			original_noid=`mktemp -t MFC`
338			new_noid=`mktemp -t MFC`
339
340			unsubstitute_keywords "${original}" "${original_noid}"
341			unsubstitute_keywords "${new}"      "${new_noid}"
342
343			eval diff ${DIFF_IGNORE_FLAGS_PORT} "${original_noid}" "${new_noid}" > /dev/null
344			ret=$?
345			rm -f ${RM_FLAGS} "${original_noid}" "${new_noid}"
346		fi
347	fi
348	return ${ret}
349}
350
351# run_portshaker_command
352# 	Do all the magic!
353#
354run_portshaker_command()
355{
356	_return=0
357
358	_portshaker_arg=$1
359	shift 1
360
361	debug "${_portshaker_arg} $*"
362
363	# if ${extra_info} contains `:`, the port infrastructure will produce
364	# warnings when attempting to launch make(1) from the source directory
365	# (required when parsing e.g. PORTVERSION fails).  Workaround this by
366	# substituting the `:` "reserved" char with an inocent one.
367	extra_info=`echo ${extra_info} | sed 's/:/_/g'`
368
369	name="`basename $0`${extra_info}"
370
371	_portsdir="${mirror_base_dir}/${name}"
372
373	# If the source ports tree directory does not exist, create it.
374	if [ ! -d "${mirror_base_dir}" ]; then
375		info "Creating the '${mirror_base_dir}' directory."
376		if [ $_use_zfs -eq 1 ]; then
377			err 1 "\$mirror_base_dir must be set to an existing  ZFS filesystem when \$use_zfs is set to '${use_zfs}'.  You have to manually create the proper ZFS filesystem according to your local setup."
378		fi
379		mkdir -p "${mirror_base_dir}" || err 1 "Failed to create '${mirror_base_dir}' directory."
380	fi
381
382	# Only proceed known commands.
383	for _elem in ${_keywords}; do
384		if [ "${_elem}" != "${_portshaker_arg}" ]; then
385			continue
386		fi
387
388		# Look for a pre-command and execute it.
389		if type ${name}_pre${_portshaker_arg} 1>/dev/null 2>&1; then
390			info "Executing pre-command '${name}_pre${_portshaker_arg}'."
391			${name}_pre${_portshaker_arg} "$@" || exit 1
392		fi
393
394		if [ $_use_zfs -eq 1 ]; then
395			_source_dataset=`zfs list -H | awk "\\\$5 == \"${_portsdir}\" {print \\\$1}"`
396		fi
397
398		# Proceed with the command
399		case "${_portshaker_arg}" in
400		clone_to|copy_to)
401			_target=""
402			_zfs_target_dataset=""
403			while getopts aP:pt:z: _i; do
404				case "${_i}" in
405					p) _pretend=1 ;;
406					P) _poudriere_tree="$OPTARG" ;;
407					t) _target="$OPTARG" ;;
408					z) _zfs_target_dataset="$OPTARG" ;;
409					?) portshaker_usage ;;
410				esac
411			done
412			if [ -z "${_target}" ]; then
413				portshaker_usage
414			fi
415
416			if [ ! -d "${_portsdir}" ]; then
417				err 1 "'${_portsdir}' does not exists."
418			fi
419			if [ -z "${_target}" ]; then
420				err 1 "Missing target directory."
421			fi
422			if [ ! -d "${_target}" ]; then
423				debug "Creating the '${_target}' directory."
424				mkdir -p "${_target}" || err 1 "Cannot create '${_target}' directory."
425			fi
426			if [ $_use_zfs -eq 1 ]; then
427				if [ -d "${_target}/.zfs" ]; then
428					_target_dataset=`zfs list -H | awk "\\\$5 == \"${_target}\" {print \\\$1}"`
429				elif [ -d "${_target}/ports/.zfs" ]; then
430					_target_dataset=`zfs list -H | awk "\\\$5 == \"${_target}/ports\" {print \\\$1}"`
431				fi
432				if [ -z "${_source_dataset}" ]; then
433					warn "'${_portsdir}' shall be an existing ZFS filesystem when \$use_zfs=yes. Falling back to rsync(1)."
434					_use_zfs=0
435				fi
436			fi
437			case "${_portshaker_arg}" in
438			clone_to)
439				info "Cloning '${_portsdir}' to '${_target}'."
440				if [ $_use_zfs -eq 1 ]; then
441					if [ -z "${_target_dataset}" ]; then
442						warn "No ZFS dataset already exists with mountpoint '${_target}'.  Will use '${_zfs_target_dataset}'."
443						_target_dataset="$_zfs_target_dataset"
444						if [ -z "${_target_dataset}" ]; then
445							warn "No '\$zfs_target_dataset' provided (hint: use '-z zfs_target_dataset')."
446							warn "Falling back to rsync(1)."
447							_use_zfs=0
448						fi
449					fi
450				fi # _use_zfs -eq 1
451				if [ $_use_zfs -eq 1 ]; then
452					if [ -d "${_target}/.zfs" ]; then
453						debug "Running 'zfs destroy -r ${_target_dataset}'."
454						if [ $_pretend -eq 0 ]; then
455							zfs destroy -r ${_target_dataset}
456							if [ $? -ne 0 ]; then
457								echo "The following processes are using '${_target}':"
458								fstat -f "${_target}"
459								err 1 "Can't destroy ZFS filesystem '${_target_dataset}'."
460							fi
461						fi
462					fi
463					# Get the latests snapshot available.
464					_snapshot=`cd ${_portsdir}/.zfs/snapshot && ls -d portshaker-* | tail -n 1`
465					if [ -z "${_snapshot}" ]; then
466						err 1 "No snapshot for '${_source_dataset}'.  Update the '${name}' source port tree then retry."
467					fi
468					debug "Running 'zfs clone ${_source_dataset}@${_snapshot} ${_target_dataset}'."
469					if [ $_pretend -eq 0 ]; then
470						zfs clone ${_source_dataset}@${_snapshot} ${_target_dataset} || err 1 "Can't clone '${_source_dataset}@${_snapshot}' to '${_target_dataset}'."
471					fi
472					debug "Running 'zfs set mountpoint="${_target}" "${_target_dataset}"'."
473					if [ $_pretend -eq 0 ]; then
474						zfs set mountpoint="${_target}" "${_target_dataset}" || err 1 "Can't set '${_target_dataset}' mountpoint to '${_target}'."
475					fi
476					debug "Running 'zfs set compression="${target_zfs_compression}" "${_target_dataset}"'."
477					if [ $_pretend -eq 0 ]; then
478						zfs set compression=${target_zfs_compression} ${_target_dataset} || warn "Cannot set ${target_zfs_compression} compression on the ${_target_dataset} ZFS filesystem."
479					fi
480					if [ -n "${_poudriere_tree}" ]; then
481						debug "Setting poudriere ports properties on '${_target_dataset}'"
482						if [ $_pretend -eq 0 ]; then
483							zfs set "poudriere:type=ports" "${_target_dataset}"
484							zfs set "poudriere:name=${_poudriere_tree}" "${_target_dataset}"
485						fi
486					fi
487				else
488					debug "Running 'rsync ${RSYNC_FLAGS} ${RSYNC_ARGS} ${RSYNC_EXCLUDE} \"${_portsdir}/\" \"${_target}\"'."
489					if [ $_pretend -eq 0 ]; then
490						rsync ${RSYNC_FLAGS} ${RSYNC_ARGS} ${RSYNC_EXCLUDE} "${_portsdir}/" "${_target}"
491						if [ $? -ne 0 ]; then
492							err 1 "Failed to clone '${_portsdir}' to '${_target}'."
493						fi
494					fi
495				fi
496				;;
497			copy_to)
498				info "Copying '${_portsdir}' to '${_target}'."
499				debug Running "'cp -pr ${CP_FLAGS} \"${_portsdir}/\" \"${_target}\"'."
500				cp -pr ${CP_FLAGS} "${_portsdir}/" "${_target}"
501				if [ $? -ne 0 ]; then
502					err 1 "Failed to copy '${_portsdir}' to '${_target}'."
503				fi
504				;;
505			esac
506			;;
507		merge_to)
508			_auto_install=0
509			_master=""
510			_target=""
511			_tinderbox_builds=""
512			while getopts ab:m:pt: _i; do
513				case "${_i}" in
514					a) _auto_install=1 ;;
515					b) _tinderbox_builds="${_tinderbox_builds} $OPTARG" ;;
516					m) _master="$OPTARG" ;;
517					p) _pretend=1 ;;
518					t) _target="$OPTARG" ;;
519					?) portshaker_usage ;;
520				esac
521			done
522			if [ -z "${_master}" -o -z "${_target}" ]; then
523				portshaker_usage
524			fi
525
526			if [ ! -d "${_portsdir}" ]; then
527				err 1 "${_portsdir} does not exists."
528			fi
529			if [ -z "${_target}" ]; then
530				err 1 "Missing target directory."
531			fi
532			if [ ! -d "${_target}" ]; then
533				err 1 "Target directory does not exist."
534			fi
535
536			info "Merging '${_portsdir}' to '${_target}'."
537
538			# Merge Mk/*.mk files
539			if [ -d "${_portsdir}/Mk" ]; then
540				if [ ! -d "${_target}/Mk" ]; then
541					err 1 "'${_target}/Mk' missing! copy / clone repository before merging."
542				fi
543				cd "${_portsdir}/Mk" || exit 1
544				for _mk in `find . -type f | sed s:"./"::`; do
545					if [ ${_mk} = "CVS" -o ${_mk} = ".svn" ]; then
546						continue
547					fi
548					if [ -e "${_target}/Mk/${_mk}" -a -e "${mirror_base_dir}/${_master}/Mk/${_mk}" ]; then
549						_mk_subdir="`dirname "${_mk}"`/"
550						_mk_file=`basename "${_mk}"`
551						_mk_patched="${_target}/Mk/${_mk_subdir}.${_mk_file}-${name}-patched"
552						if [ -e "${_mk_patched}" ]; then
553							warn "'Mk/${_mk}' has already been patched (ignoring)."
554							continue
555						fi
556						if ! newer_or_same_vcs_id "${_mk}" "${mirror_base_dir}/${_master}/Mk/${_mk}"; then
557							err 1 "'Mk/${_mk}' from '${name}' is not in sync with the one provided in the FreeBSD ports."
558						fi
559						debug "Updating 'Mk/${_mk}'."
560						if [ ${_pretend} -eq 0 ]; then
561							_patch=`diff ${DIFF_FLAGS} ${DIFF_IGNORE_FLAGS_MK} "${mirror_base_dir}/${_master}/Mk/${_mk}" "${_mk}"`
562							if [ -z "${_patch}" ]; then
563								debug "No patch required for {$_mk}"
564							elif echo "${_patch}" | (cd ${_target}/Mk && patch ${PATCH_FLAGS}); then
565								touch "${_mk_patched}"
566							else
567								err 1 "Unable to patch 'Mk/${_mk}'."
568							fi
569						fi
570					else
571						debug "Copying 'Mk/${_mk}'."
572						if [ ${_pretend} -eq 0 ]; then
573							cp ${CP_FLAGS} "${_mk}" "${_target}/Mk/${_mk}" || err 1 "Unable to copy 'Mk/${_mk}'."
574						fi
575					fi
576				done
577			fi
578
579			# To determine a port version, we sometimes need to run
580			# make.  Ensure ${PORTSDIR}/Mk can be used.  Since we
581			# have already merged the Mk directory, any local Mk
582			# files should be found where they are supposed to be
583			# found.
584			export PORTSDIR=${_target}
585
586			# Merge ports
587			cd "${_portsdir}" || exit 1
588			for _category in *; do
589				_regen_category_makefile=0
590				if [ ! -d ${_category} ] || [ ${_category} = "Mk" -o ${_category} = "CVS" -o ${_category} = ".svn" -o ${_category} = ".git" -o ${_category} = ".hg" -o ${_category} = "Tools" ]; then
591					continue
592				fi
593				cd ${_category} || exit 1
594				if [ ! -d "${_target}/${_category}" ]; then
595					debug "Creating category '${_category}'."
596					if [ ${_pretend} -eq 0 ]; then
597						mkdir "${_target}/${_category}" || exit 1
598					fi
599					if [ -f Makefile ]; then
600						debug "Copying '${_category}/Makefile'."
601						if [ ${_pretend} -eq 0 ]; then
602							cp ${CP_FLAGS} Makefile "${_target}/${_category}" || err 1 "Unable to copy '${_category}/Makefile'."
603						fi
604					fi
605				fi
606				for _port in *; do
607					if [ ${_port} = "CVS" -o ${_port} = ".svn" -o ${_port} = "Makefile" ]; then
608						continue
609					fi
610					_copy_port=0
611					_version_going_backward=0
612
613					debug "Processing ${_category}/${_port}."
614
615					if [ ! -f "${_port}/Makefile" ]; then
616						warn "${_category}/${_port}: No Makefile in this directory!"
617						continue
618					fi
619
620					# Track packages version to help debugging failing update paths
621					_current_pkg_version="0"
622
623					# Determine wether the port has to be merged or not
624					if [ ! -d "${_target}/${_category}/${_port}" ]; then
625						debug "${_category}/${_port} is a new port."
626						_regen_category_makefile=1
627						_copy_port=1
628					else
629						_current_pkg_version=`${MAKE} -C "${_target}/${_category}/${_port}" -V PKGVERSION`
630						# Compare port versions
631						if newer_portepoch "${_portsdir}/${_category}/${_port}" "${_target}/${_category}/${_port}"; then
632							debug "${_category}/${_port} has been updated (higher PORTEPOCH)."
633							_copy_port=1
634						else
635							newer_portversion "${_portsdir}/${_category}/${_port}" "${_target}/${_category}/${_port}"
636							case $? in
637								0)
638									debug "${_category}/${_port} has been updated (higher PORTVERSION)."
639									_copy_port=1
640									;;
641								1)
642									newer_portrevision "${_portsdir}/${_category}/${_port}" "${_target}/${_category}/${_port}"
643									case $? in
644										0)
645											debug "${_category}/${_port} has been updated (higher PORTREVISION)."
646											_copy_port=1
647											;;
648										1)
649											debug "${_category}/${_port} has not been updated."
650											;;
651										2)
652											_version_going_backward=1
653											;;
654									esac
655									;;
656								2)
657									_version_going_backward=1
658									;;
659							esac
660						fi
661					fi
662					if [ ${_version_going_backward} -eq 1 ]; then
663						warn "${_category}/${_port}: port version going backward (I will not merge this port)!"
664						if [ -f "${_target}/.portshaker-merged-ports" ]; then
665							awk -F: 'BEGIN { first = 1 ; } $2 == "'${_category}/${_port}'" { if (first == 1) { printf("                 `-> Update path: %s", $3); } printf(" --[%s]--> %s", $1, $4); first = 0 } END { if (first == 0) { printf("\n"); } } ' < "${_target}/.portshaker-merged-ports"
666						fi
667					fi
668					# Ensure merging is consistent
669					if [ ${_copy_port} -eq 0 -a ${_version_going_backward} = 0 ]; then
670						_touched_files=""
671
672						# Look for modifications of port files
673						for _file in `find "${_portsdir}/${_category}/${_port}" -maxdepth 1 -type f -exec basename '{}' ';'`; do
674							if [ ! -e "${_target}/${_category}/${_port}/${_file}" ]; then
675								debug "${_category}/${_port}/${_file} has been created."
676								_touched_files="${_touched_files} ${_file}"
677							else
678								compare_noid "${_portsdir}/${_category}/${_port}/${_file}" "${_target}/${_category}/${_port}/${_file}"
679								if [ $? -ne 0 ]; then
680									debug "${_category}/${_port}/${_file} has been changed."
681									_touched_files="${_touched_files} ${_file}"
682								fi
683							fi
684						done
685						for _file in `find "${_target}/${_category}/${_port}" -maxdepth 1 -type f -exec basename '{}' ';'`; do
686							if [ ! -e "${_portsdir}/${_category}/${_port}/${_file}" ]; then
687								debug "${_category}/${_port}/${_file} has been removed."
688								_touched_files="${_touched_files} ${_file}"
689							fi
690						done
691
692						# Look for modification of patches
693						_patches=""
694						if [ -d "${_portsdir}/${_category}/${_port}/files" ]; then
695							for _patch in `find "${_portsdir}/${_category}/${_port}/files" -maxdepth 1 -type f -exec basename '{}' ';'`; do
696								if [ ! -e "${_target}/${_category}/${_port}/files/${_patch}" ]; then
697									debug "${_category}/${_port}/files/${_patch} has been created."
698									_patches="${_patches} ${_patch}"
699								else
700									compare_noid "${_portsdir}/${_category}/${_port}/files/${_patch}" "${_target}/${_category}/${_port}/files/${_patch}"
701									if [ $? -ne 0 ]; then
702										debug "${_category}/${_port}/files/${_patch} has been changed."
703										_patches="${_patches} ${_patch}"
704									fi
705								fi
706							done
707						fi
708						if [ -d "${_target}/${_category}/${_port}/files" ]; then
709							for _patch in `find "${_target}/${_category}/${_port}/files" -maxdepth 1 -type f -exec basename '{}' ';'`; do
710								if [ ! -e "${_portsdir}/${_category}/${_port}/files/${_patch}" ]; then
711									debug "${_category}/${_port}/files/${_patch} has been removed."
712									_patches="${_patches} ${_patch}"
713								fi
714							done
715						fi
716						for _patch in ${_patches}; do
717							compare_noid "${_portsdir}/${_category}/${_port}/files/${_patch}" "${_target}/${_category}/${_port}/files/${_patch}"
718							if [ $? -ne 0 ]; then
719								_touched_files="${_touched_files} files/${_patch}"
720							fi
721						done
722
723						# Handle touched files
724						if [ -n "${_touched_files}" ]; then
725							if  [ "${_auto_install}" -eq 1 ]; then
726								warn "${_category}/${_port}:${_touched_files} changed but the port version is still the same (copying port anyway)."
727								_copy_port=1
728							elif [ ${_pretend} -eq 1 ]; then
729								warn "${_category}/${_port}:${_touched_files} changed but the port version is still the same."
730							else
731								_r=""
732								while true; do
733									case ${_r} in
734										# diff
735										d*|D*)
736											for _file in ${_touched_files}; do
737												diff ${DIFF_FLAGS} "${_target}/${_category}/${_port}/${_file}" "${_portsdir}/${_category}/${_port}/${_file}"
738											done | ${PAGER}
739											_r=""
740											;;
741										# install
742										i*|I*)
743											_copy_port=1
744											break
745											;;
746										# continue
747										c*|C*)
748											break
749											;;
750										*)
751											warn "Conflict detected!"
752											echo "The target ports tree (${_target}) already have the version of ${_category}/${_port} provided by ${name}."
753											echo "However, the following file(s) has been touched:${_touched_files}."
754											echo "An update may be required."
755
756                                                                                        if [ "${_fail_on_conflict}" -eq 1 ]; then
757                                                                                                exit 1
758                                                                                        fi
759
760											echo -n "(diff|install|continue) > "
761											read _r
762											;;
763									esac
764								done
765							fi
766						fi
767					fi
768					# Merge port
769					if [ ${_copy_port} -eq 1 -a ${_version_going_backward} -eq 0 ]; then
770						debug "Copying '${_portsdir}/${_category}/${_port}' to '${_target}/${_category}/${_port}'."
771						if [ ${_pretend} -eq 0 ]; then
772							rm -rf ${RM_FLAGS} "${_target}/${_category}/${_port}" || err 1 "Cannot remove outdated '${_target}/${_category}/${_port}' port directory."
773							cp -pr ${CP_FLAGS} "${_portsdir}/${_category}/${_port}" "${_target}/${_category}/${_port}" || err 1 "Cannot merge ${_category}/${_port}."
774							for _special_file in CVS .svn files/CVS files/.svn; do
775								if [ -d "${_target}/${_category}/${_port}/${_special_file}" ]; then
776									rm -rf ${RM_FLAGS} "${_target}/${_category}/${_port}/${_special_file}" || err 1 "Cannot remove '${_target}/${_category}/${_port}/${_special_file}'."
777								fi
778							done
779							# Reference port in tinderbox
780							if [ -n "${_tinderbox_builds}" ]; then
781								for _build in ${_tinderbox_builds}; do
782									(cd "${PB}/scripts" && ${TC} addPort -b "${_build}" -d "${_category}/${_port}")
783								done
784							fi
785							# Keep a trace of merged ports (for speeding up tinderbox in hooks for example)
786							_new_pkg_version=`${MAKE} -C "${_target}/${_category}/${_port}" -V PKGVERSION 2> /dev/null`
787							if [ -z ${_new_pkg_version} ]; then
788								# Second chance: if it was not possible to get the package name from the tagret
789								# ports tree, try to get it from the source ports tree.  This is usefull if the
790								# current port has a master port in the source ports tree that has not already
791								# been merged in the target ports tree.
792								_new_pkg_version=`${MAKE} -C "${_portsdir}/${_category}/${_port}" -V PKGVERSION 2> /dev/null`
793							fi
794							echo "${name}:${_category}/${_port}:${_current_pkg_version}:${_new_pkg_version}" >> "${_target}/.portshaker-merged-ports"
795						fi
796					else
797						debug "Not copying '${_portsdir}/${_category}/${_port}' to '${_target}/${_category}/${_port}'."
798					fi
799				done # _port
800
801				if [ ${_regen_category_makefile} -eq 1 ]; then
802					(cd ${_target}/${_category}
803					debug "Updating ${_category} Makefile."
804					if [ ${_pretend} -eq 0 ]; then
805						sed -e '/SUBDIR/,$d' Makefile > Makefile.new
806						find . -mindepth 1 -maxdepth 1 -type d -print | sort | sed -e 's|^./|    SUBDIR += |' >> Makefile.new
807						sed -e '1,/SUBDIR/d' -e '/SUBDIR/d' Makefile >> Makefile.new
808						mv -f Makefile.new Makefile
809					fi
810					)
811				fi
812				cd ..
813			done # _category
814
815			# Remove obsolete ports
816			if [ -f "${_portsdir}/MOVED" ]; then
817				info "Merging MOVED entries from '${name}'."
818				grep -v '^#' "${_portsdir}/MOVED" | while read _line; do
819					_port=`echo $_line | cut -d'|' -f1`
820					if [ -z "${_port}" ]; then
821						warn "Malformed line found in ${_portsdir}/MOVED: \"${_line}\""
822						continue
823					fi
824					if [ -d "${_target}/${_port}" ]; then
825						info "Removing obsolete port ${_port}."
826						if [ ${_pretend} -eq 0 ]; then
827							rm -rf ${RM_FLAGS} "${_target}/${_port}" || err 1 "Cannot remove obsolete port ${_port}."
828						fi
829					else
830						debug "${_port} referenced in ${_portsdir}/MOVED does not exist."
831					fi
832					if [ ${_pretend} -eq 0 ]; then
833						echo "${_line}" >> "${_target}/MOVED"
834					fi
835				done # while read _line
836			fi
837
838			# Merge UPDATING information
839			if [ -f "${_portsdir}/UPDATING" -a -f "${_target}/UPDATING" -a ${_pretend} -eq 0 ]; then
840				info "Merging UPDATING entries from '${name}'."
841				mv "${_target}/UPDATING" "${_target}/UPDATING.orig"
842				awk -f "@@SHAREDIR@@/portshaker/merge-updating.awk" -v in1="${_target}/UPDATING.orig" -v in2="${_portsdir}/UPDATING" -v out="${_target}/UPDATING"
843				rm ${RM_FLAGS} "${_target}/UPDATING.orig"
844			fi
845
846			# Merge UIDs GIDs
847			if [ -f "${_portsdir}/UIDs" -a ${_pretend} -eq 0 ]; then
848				info "Merging UIDs from ${name}."
849				mv "${_target}/UIDs" "${_target}/UIDs.orig"
850				cat "${_portsdir}/UIDs" "${_target}/UIDs.orig" | sort -nut: -k3 > "${_target}/UIDs"
851				rm "${_target}/UIDs.orig"
852			fi
853			if [ -f "${_portsdir}/GIDs" -a ${_pretend} -eq 0 ]; then
854				info "Merging GIDs from ${name}."
855				mv "${_target}/GIDs" "${_target}/GIDs.orig"
856				cat "${_portsdir}/GIDs" "${_target}/GIDs.orig" | sort -nut: -k3 > "${_target}/GIDs"
857				rm "${_target}/GIDs.orig"
858			fi
859			;;
860		update)
861			info "Updating '${name}' source ports tree with method '${method}'."
862
863			if [ ! -d "${_portsdir}" ] && [ ! "${method}" = "cvs" ]; then
864				debug "Creating the '${_portsdir}' directory."
865				if [ $_use_zfs -eq 1 ]; then
866					_parent=`dirname "${_portsdir}"`
867					_parent_dataset=`zfs list -H | awk "\\\$5 == \"${_parent}\" {print \\\$1}"`
868					if [ -z "${_parent_dataset}" ]; then
869						err 1 "The ${_parent} directory shall be a ZFS filesystem when \$use_zfs=yes."
870					fi
871					_source_dataset="${_parent_dataset}/${name}"
872					zfs create "${_source_dataset}" || err 1 "Cannot create the ${_source_dataset} ZFS filesystem."
873					zfs set compression=${source_zfs_compression} ${_source_dataset} || warn "Cannot set ${source_zfs_compression} compression on the ${_source_dataset} ZFS filesystem."
874				else
875					mkdir -p "${_portsdir}" || err 1 "Cannot create the '${_portsdir}' directory."
876				fi
877			fi
878			if [ ! -w "${_portsdir}" ] && [ ! "${method}" = "cvs" ]; then
879				err 1 "'${_portsdir}' is not writable: cannot update '${name}'."
880			fi
881
882			case "${method}" in
883			csup)
884				if [ -z "${csup_supfile}" ]; then
885					err 1 "\$csup_supfile is not defined."
886				fi
887				if [ "${name}" != "ports" ]; then
888					err 1 "A source ports tree updated using csup requires be named 'ports'."
889				fi
890				debug "Running 'csup ${CSUP_FLAGS} ${csup_supfile}'."
891				csup ${CSUP_FLAGS} ${csup_supfile}
892				if [ $? -ne 0 ]; then
893					err 1 "csup update failed."
894				fi
895				;;
896			cvs)
897				if [ -z "${cvs_root}" ]; then
898					err 1 "\$cvs_root is not defined."
899				fi
900				if [ -z "${cvs_module}" ]; then
901					err 1 "\$cvs_module is not defined."
902				fi
903				if [ ! -d "${_portsdir}/CVS" ]; then
904					cd ${mirror_base_dir}
905					debug "Running '${CVS} ${CVS_FLAGS} -d\"${cvs_root}\" login'."
906					${CVS} ${CVS_FLAGS} -d"${cvs_root}" login
907					if [ $? -ne 0 ]; then
908						err 1 "CVS login failed."
909					fi
910					debug "Running '${CVS} ${CVS_FLAGS} -d\"${cvs_root}\" checkout -d ${name} -P \"${cvs_module}\"'."
911					${CVS} ${CVS_FLAGS} -d"${cvs_root}" checkout -d ${name} -P "${cvs_module}"
912					if [ $? -ne 0 ]; then
913						err 1 "CVS checkout failed."
914					fi
915				else
916					cd "${_portsdir}"
917					debug "Running '${CVS} ${CVS_FLAGS} update -Pd -A'."
918					${CVS} ${CVS_FLAGS} update -Pd -A 2>&1 | sed 's|^cvs update: \(.*\) is no longer in the repository$|D \1|'
919					if [ $? -ne 0 ]; then
920						err 1 "CVS update failed."
921					fi
922				fi
923				;;
924			git)
925				if [ -z "${git_clone_uri}" ]; then
926					err 1 "\$git_clone_uri is not defined."
927				fi
928				if [ ! -d "${_portsdir}/.git" ]; then
929					debug "Running '${GIT} clone ${git_clone_uri} ${_portsdir}'."
930					git_clone_flags="--depth 1 --branch ${git_branch:=master}"
931					${GIT} clone "${git_clone_uri}" ${git_clone_flags} "${_portsdir}"
932					if [ $? -ne 0 ]; then
933						err 1 "git clone failed."
934					fi
935					if [ -n "${git_submodules}" ]; then
936						cd "${_portsdir}"
937						${GIT} submodule init
938						if [ $? -ne 0 ]; then
939							err 1 "git submodule init failed."
940						fi
941						${GIT} submodule update --recursive
942						if [ $? -ne 0 ]; then
943							err 1 "git submodule update failed."
944						fi
945						if [ -n "${git_submodules_branch}" ]; then
946							${GIT} submodule foreach --recursive git checkout ${git_submodules_branch}
947							if [ $? -ne 0 ]; then
948								err 1 "git submodule checkout failed."
949							fi
950						fi
951					fi
952				else
953					cd "${_portsdir}"
954					debug "Running '${GIT} fetch'."
955					${GIT} fetch
956					if [ $? -ne 0 ]; then
957						err 1 "git fetch failed."
958					fi
959					if [ -n "${git_branch}" ]; then
960						${GIT} checkout "${git_branch}"
961					fi
962					if [ -n "${git_submodules}" ]; then
963						${GIT} submodule foreach --recursive git fetch
964						if [ $? -ne 0 ]; then
965							err 1 "git submodule fetch failed."
966						fi
967					fi
968					debug "Running '${GIT} reset --hard origin/${git_branch:=master}'"
969					${GIT} reset --hard origin/${git_branch:=master}
970					if [ $? -ne 0 ]; then
971						err 1 "git reset --hard origin/${git_branch:=master}."
972					fi
973				fi
974				;;
975			hg)
976				if [ -z "${hg_clone_uri}" ]; then
977					err 1 "\$hg_clone_uri is not defined."
978				fi
979				if [ ! -d "${_portsdir}/.hg" ]; then
980					debug "Running '${HG} clone ${hg_clone_uri} ${_portsdir}'."
981					${HG} clone "${hg_clone_uri}" "${_portsdir}"
982					if [ $? -ne 0 ]; then
983						err 1 "hg clone failed."
984					fi
985				else
986					cd "${_portsdir}"
987					debug "Running '${HG} pull'."
988					${HG} pull
989					if [ $? -ne 0 ]; then
990						err 1 "hg pull failed."
991					fi
992					debug "Running '${HG} update'."
993					${HG} update
994					if [ $? -ne 0 ]; then
995						err 1 "hg update failed."
996					fi
997				fi
998				;;
999			portsnap)
1000				PORTSNAP_FLAGS="${PORTSNAP_FLAGS} -p ${_portsdir}"
1001				if [ ! -f "${_portsdir}/.portsnap.INDEX" ]; then
1002					# "portsnap alfred" <=> "portsnap fetch && portsnap update", but we need to "portsnap extract" here.
1003					debug "Running 'portsnap ${PORTSNAP_FLAGS} fetch'."
1004					portsnap ${PORTSNAP_FLAGS} fetch
1005					if [ $? -ne 0 ]; then
1006						err 1 "portsnap fetch failed."
1007					fi
1008					debug "Running 'portsnap ${PORTSNAP_FLAGS} extract'."
1009					portsnap ${PORTSNAP_FLAGS} extract
1010					if [ $? -ne 0 ]; then
1011						err 1 "portsnap extract failed."
1012					fi
1013				else
1014					debug "Running 'portsnap ${PORTSNAP_FLAGS} alfred'."
1015					portsnap ${PORTSNAP_FLAGS} alfred
1016					if [ $? -ne 0 ]; then
1017						err 1 "portsnap alfred failed."
1018					fi
1019				fi
1020				;;
1021			rsync)
1022				if [ -z "${rsync_source_path}" ]; then
1023					err 1 "\$rsync_source_path is not defined."
1024				fi
1025				debug "Running '${RSYNC} ${RSYNC_FLAGS} ${RSYNC_ARGS} ${rsync_extra_args} ${rsync_source_path}/ ${_portsdir}/'."
1026				set -f
1027				${RSYNC} ${RSYNC_FLAGS} ${RSYNC_ARGS} ${rsync_extra_args} "${rsync_source_path}/" "${_portsdir}/"
1028				rsync_res=$?
1029				set +f
1030				if [ $rsync_res -ne 0 ]; then
1031					err 1 "rsync update failed."
1032				fi
1033				;;
1034			svn)
1035				if [ -z "${svn_checkout_path}" ]; then
1036					err 1 "\$svn_checkout_path is not defined."
1037				fi
1038				if [ ! -d "${_portsdir}/.svn" ]; then
1039					debug "Running '${SVN} ${SVN_FLAGS} checkout \"${svn_checkout_path}\" \"${_portsdir}\"'."
1040					${SVN} ${SVN_FLAGS} checkout "${svn_checkout_path}" "${_portsdir}"
1041					if [ $? -ne 0 ]; then
1042						err 1 "Subversion checkout failed."
1043					fi
1044				else
1045					cd "${_portsdir}"
1046					debug "Running '${SVN} ${SVN_FLAGS} update'."
1047					${SVN} ${SVN_FLAGS} update
1048					if [ $? -ne 0 ]; then
1049						err 1 "Subversion update failed."
1050					fi
1051				fi
1052				;;
1053			svn+subtrees)
1054				if [ -z "${svn_checkout_path}" ]; then
1055					err 1 "\$svn_checkout_path is not defined."
1056				fi
1057				cd "${_portsdir}"
1058				if [ ! -d "${_portsdir}/.svn" ]; then
1059					debug "Running '${SVNADMIN} ${SVNADMIN_FLAGS} create \"${_portsdir}/.repos\"'."
1060					${SVNADMIN} ${SVNADMIN_FLAGS} create "${_portsdir}/.repos"
1061					if [ $? -ne 0 ]; then
1062						err 1 "Creating subversion repository failed."
1063					fi
1064					debug "Running '${SVN} ${SVN_FLAGS} checkout \"file://${_portsdir}/.repos\" \"${_portsdir}\"'."
1065					${SVN} ${SVN_FLAGS} checkout "file://${_portsdir}/.repos" "${_portsdir}"
1066					if [ $? -ne 0 ]; then
1067						err 1 "Subversion update failed."
1068					fi
1069					debug "Moving \"${_portsdir}/.repos\" to \"${_portsdir}/.svn/.repos\"."
1070					mv "${_portsdir}/.repos" "${_portsdir}/.svn"
1071					debug "Running '${SVN} ${SVN_FLAGS} relocate \"file://${_portsdir}/.repos\" \"file://${_portsdir}/.svn/.repos\"'."
1072					${SVN} ${SVN_FLAGS} relocate "file://${_portsdir}/.repos" "file://${_portsdir}/.svn/.repos"
1073					if [ $? -ne 0 ]; then
1074						err 1 "Subversion relocate failed."
1075					fi
1076				fi
1077
1078				debug "Running '${SVN} ${SVN_FLAGS} propset svn:externals'."
1079				for _part in ${svn_checkout_subtrees}; do
1080					echo ${svn_checkout_path}/${_part} $(echo ${_part} | grep -oE '^[^@]+')
1081				done | ${SVN} ${SVN_FLAGS} propset svn:externals -F - .
1082				if [ $? -ne 0 ]; then
1083					err 1 "Subversion propset failed."
1084				fi
1085				debug "Running '${SVN} ${SVN_FLAGS} commit'."
1086				${SVN} ${SVN_FLAGS} commit -m "portshaker: change svn:externals"
1087				if [ $? -ne 0 ]; then
1088					err 1 "Subversion commit failed."
1089				fi
1090				debug "Running '${SVN} ${SVN_FLAGS} update'."
1091				${SVN} ${SVN_FLAGS} update
1092				if [ $? -ne 0 ]; then
1093					err 1 "Subversion update failed."
1094				fi
1095				;;
1096			*)
1097				err 1 "Unsupported update method '${method}'."
1098				;;
1099			esac
1100
1101			# If the updated ports tree is a ZFS filesystem, snapshot it.
1102			if [ $_use_zfs -eq 1 -a -d "${_portsdir}/.zfs" ]; then
1103				for p in "${_portsdir}/.zfs/snapshot/portshaker"*; do
1104					# Purge old snapshots
1105					debug "Running 'zfs destroy ${_source_dataset}@`basename ${p}`'."
1106					if [ $_pretend -eq 0 ]; then
1107						zfs destroy ${_source_dataset}@`basename ${p}` >/dev/null 2>&1
1108					fi
1109				done
1110				date=`date +%F:%T`
1111				debug "Running 'zfs snapshot ${_source_dataset}@portshaker-${date}'."
1112				if [ $_pretend -eq 0 ]; then
1113					zfs snapshot "${_source_dataset}@portshaker-${date}" || err 1 "Can't create ZFS snapshot '${_source_dataset}@portshaker-${date}'."
1114				fi
1115			fi
1116			;; # _portshaker_arg = update
1117		esac
1118
1119		# Look for a post-command and execute it
1120		if type ${name}_post${_portshaker_arg} 1>/dev/null 2>&1; then
1121			info "Executing post-command \"${name}_post${_portshaker_arg}\"."
1122			${name}_post${_portshaker_arg} "$@" || exit 1
1123		fi
1124
1125		return ${_return}
1126	done
1127
1128	# Invalid command
1129	echo 1>&2 "$0: unknown command '${_portshaker_arg}'"
1130	portshaker_usage
1131	# not reached
1132}
1133
1134fi
1135