tech-pkg archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: [PATCH] Squashed commit of work done on configuration tracking as part of GSOC2018



After having received some feedback about styling and solaris <= 10 compatibility I changed some of the code, leading to the discovery of new corner cases and new bugs (circular dependencies while bootstrapping, no pkgvcsconf getting created when installing a software package that comes with configuration files but doesn't define its own user, no solaris 10 /bin/sh support, missing tools definitions in some platforms!). I Attach the new patch, that should apply over pkgsrc-trunk, hoping it gets tried out on platforms I don't have access to right now.
---------------------------------
Subject: [PATCH] PKGSRC configuration files version tracking

There's a new pkginstall script that wraps around VCSs:
mk/pkginstall/versioning mk/pkginstall/install has changed to check for the
existance of the user "pkgvcsconf" and create it if needed when running as
root (to drop privs). It also calls +VERSIONING to deploy configuration from
a remote VCS if that's enabled in pkg_install.conf, then if this fails it
calls +FILES to use package provided default configuration. +FILES is always
called, in order to install rc.d scripts, but it now gets passed a variable
indicating if CONFPULL failed.

mk/pkginstall/files has been changed to store default configuration files in a
configuration repository, by calling +VERSIONING. If a file already exists, new
logic in mk/pkginstall/files tries to automatically merge it using history from the VCS repository.

mk/pkginstall/usergroupfuncs* have been patched to allow group creation without
passing a groupid (bug in pkgsrc?), while mk/pkginstall/usergroup has a new,
simple "ADDUSER" action that adds a user:group [userid] without parsing more
info from the footers generated at build time (useful for users created for
pkgsrc itself and not for use by packages).
Note that usergroup* script should now be bundled in binary packages even if
them don't require users. This is inefficient, since the scripts will get
duplicated in the pkgdb. Pkgtasks may solve this issue, as may reimplementing
the pkgvcsconf user creation without relying on the usergroup scripts.

mk/pkginstall/bsd.pkginstall.mk is now aware of the new VERSIONING script;
it replaces new vars needed by the new code via FILES_SUBST, installs
devel/rcs as a TOOL if it is not platform-defined and the package
being currently built is not a TOOL itself, to avoid common circular deps issues

Also, the bootstrap/bootstrap script, when running build_package() and
build_package_nopreserve() sets the environment variabile IGNORE_VCS, read by
bsd.pkginstall.mk to avoid pulling in rcs during the bootstrap run.

The header file (mk/pkginstall/files) included in packages declares the current
VARBASE for use by pkgtools/pkgconftrack.

pkgtools/pkgconftrack is a third party tool that facilitates common mantainance
operations on the configuration repository. Now in its infancy, it only allows
for the automatic storage of locally edited configuration files for a set of
packages into the configuration database with an unique commit. More will come.

pkgtools/pkg_install has been lightly modified to parse new VCS-related
options in pkg_install.conf and pass them to the +INSTALL script via setenv.
See pkgtools/pkg_install/files/lib/{lib.h, parse-config.h,
pkg_install.conf.5.in}, pkgtools/pkg_install/files/add/perform.c.

review: try to apply suggestions made by roland.illig%gmx.de@localhost, and target solaris
<= 10
postreview: fixes to pkg_install/files/add/perform.c, bsd.pkginstall.mk,
the bootstrap script, unedited tools.*.mk and obviously versioning and files.
Trying to have it work on solaris made for some new test cases and bug discovery
---
 bootstrap/bootstrap                                |   4 +-
 mk/pkginstall/bsd.pkginstall.mk                    |  79 +-
 mk/pkginstall/files                                | 214 ++++-
 mk/pkginstall/header                               |   5 +-
 mk/pkginstall/install                              |  40 +-
 mk/pkginstall/usergroup                            |  36 +
 mk/pkginstall/usergroupfuncs                       |   4 +-
 mk/pkginstall/usergroupfuncs.DragonFly             |   2 +-
 mk/pkginstall/usergroupfuncs.FreeBSD               |   2 +-
 mk/pkginstall/usergroupfuncs.Linux                 |   2 +-
 mk/pkginstall/versioning                           | 967 +++++++++++++++++++++
 mk/tools/defaults.mk                               |   6 +
 mk/tools/replace.mk                                |  27 +-
 mk/tools/tools.AIX.mk                              |  13 +
 mk/tools/tools.BSDOS.mk                            |  13 +
 mk/tools/tools.Bitrig.mk                           |  13 +
 mk/tools/tools.Cygwin.mk                           |  13 +
 mk/tools/tools.Darwin.mk                           |  13 +
 mk/tools/tools.DragonFly.mk                        |  12 +
 mk/tools/tools.FreeBSD.mk                          |  13 +
 mk/tools/tools.FreeMiNT.mk                         |   1 +
 mk/tools/tools.GNUkFreeBSD.mk                      |   1 +
 mk/tools/tools.HPUX.mk                             |   1 +
 mk/tools/tools.Haiku.mk                            |  13 +
 mk/tools/tools.IRIX.mk                             |   1 +
 mk/tools/tools.Interix.mk                          |   1 +
 mk/tools/tools.Linux.mk                            |  13 +
 mk/tools/tools.Minix.mk                            |   3 +-
 mk/tools/tools.MirBSD.mk                           |   1 +
 mk/tools/tools.NetBSD.mk                           |  13 +
 mk/tools/tools.OSF1.mk                             |   1 +
 mk/tools/tools.OpenBSD.mk                          |  13 +
 mk/tools/tools.QNX.mk                              |   1 +
 mk/tools/tools.SCO_SV.mk                           |   1 +
 mk/tools/tools.SunOS.mk                            |   1 +
 mk/tools/tools.UnixWare.mk                         |   1 +
 pkgtools/pkg_install/files/add/perform.c           |  14 +
 pkgtools/pkg_install/files/lib/lib.h               |   8 +-
 pkgtools/pkg_install/files/lib/parse-config.c      |  14 +
 .../pkg_install/files/lib/pkg_install.conf.5.in    |  24 +
 .../pkg_install/files/lib/pkg_install.conf.cat.in  |  41 +-
 pkgtools/pkgconftrack/DESCR                        |   2 +
 pkgtools/pkgconftrack/Makefile                     |  24 +
 pkgtools/pkgconftrack/PLIST                        |   2 +
 pkgtools/pkgconftrack/distinfo                     |   2 +
 pkgtools/pkgconftrack/files/pkgconftrack           | 352 ++++++++
 pkgtools/pkgconftrack/files/pkgconftrack.1         | 103 +++
 47 files changed, 2089 insertions(+), 41 deletions(-)
 create mode 100644 mk/pkginstall/versioning
 create mode 100644 pkgtools/pkgconftrack/DESCR
 create mode 100644 pkgtools/pkgconftrack/Makefile
 create mode 100644 pkgtools/pkgconftrack/PLIST
 create mode 100644 pkgtools/pkgconftrack/distinfo
 create mode 100755 pkgtools/pkgconftrack/files/pkgconftrack
 create mode 100644 pkgtools/pkgconftrack/files/pkgconftrack.1

diff --git a/bootstrap/bootstrap b/bootstrap/bootstrap
index 8def6e72fc6b..5c10c342923b 100755
--- a/bootstrap/bootstrap
+++ b/bootstrap/bootstrap
@@ -1305,10 +1305,10 @@ echo ".endif			# end pkgsrc settings" >> ${BOOTSTRAP_MKCONF}
 # build and register packages
 # usage: build_package <packagedirectory>
 build_package() {
-	run_cmd "(cd $pkgsrcdir/$1 && $bmake $make_quiet_flags MAKE_JOBS=${make_jobs} PKG_COMPRESSION=none -DPKG_PRESERVE MAKECONF=${BOOTSTRAP_MKCONF} install)"
+	run_cmd "(cd $pkgsrcdir/$1 && IGNORE_VCS=yes $bmake $make_quiet_flags MAKE_JOBS=${make_jobs} PKG_COMPRESSION=none -DPKG_PRESERVE MAKECONF=${BOOTSTRAP_MKCONF} install)"
 }
 build_package_nopreserve() {
-	run_cmd "(cd $pkgsrcdir/$1 && $bmake $make_quiet_flags MAKE_JOBS=${make_jobs} PKG_COMPRESSION=none MAKECONF=${BOOTSTRAP_MKCONF} install)"
+	run_cmd "(cd $pkgsrcdir/$1 && IGNORE_VCS=yes $bmake $make_quiet_flags MAKE_JOBS=${make_jobs} PKG_COMPRESSION=none MAKECONF=${BOOTSTRAP_MKCONF} install)"
 }
 
 #
diff --git a/mk/pkginstall/bsd.pkginstall.mk b/mk/pkginstall/bsd.pkginstall.mk
index 93b6230aab53..4cad625f4c3d 100644
--- a/mk/pkginstall/bsd.pkginstall.mk
+++ b/mk/pkginstall/bsd.pkginstall.mk
@@ -96,6 +96,7 @@ DEINSTALL_TEMPLATES+=	${PKGDIR}/DEINSTALL
 _DEINSTALL_TMPL?=	${.CURDIR}/../../mk/pkginstall/deinstall
 _INSTALL_UNPACK_TMPL?=	# empty
 _INSTALL_TMPL?=		${.CURDIR}/../../mk/pkginstall/install
+_INSTALL_TMPL+=		${.CURDIR}/../../mk/pkginstall/versioning
 INSTALL_TEMPLATES?=	# empty
 .if exists(${PKGDIR}/INSTALL) && \
     empty(INSTALL_TEMPLATES:M${PKGDIR}/INSTALL)
@@ -147,7 +148,9 @@ FILES_SUBST+=		PKG_SYSCONFBASEDIR=${PKG_SYSCONFBASEDIR:Q}
 FILES_SUBST+=		PKG_SYSCONFDIR=${PKG_SYSCONFDIR:Q}
 FILES_SUBST+=		CONF_DEPENDS=${CONF_DEPENDS:C/:.*//:Q}
 FILES_SUBST+=		PKGBASE=${PKGBASE:Q}
-
+FILES_SUBST+=		PKGVERSION=${PKGVERSION:Q}
+FILES_SUBST+=		SIMPLENAME=${PKGNAME_NOREV:Q}
+FILES_SUBST+=		PKGPATH=${PKGPATH:Q}
 # PKG_USERS represents the users to create for the package.  It is a
 #	space-separated list of elements of the form
 #
@@ -297,10 +300,12 @@ ${_INSTALL_USERGROUP_FILE}:						\
 	${SED}	-e "/^# platform-specific adduser\/addgroup functions/r${_INSTALL_USERGROUPFUNCS_FILE}" ../../mk/pkginstall/usergroup |			\
 	${SED} ${FILES_SUBST_SED} > ${.TARGET}
 	${RUN}								\
-	if ${_ZERO_FILESIZE_P} ${_INSTALL_USERGROUP_DATAFILE}; then	\
-		${RM} -f ${.TARGET};					\
-		${TOUCH} ${TOUCH_ARGS} ${.TARGET};			\
-	fi
+#	if ${_ZERO_FILESIZE_P} ${_INSTALL_USERGROUP_DATAFILE}; then	\
+#		${RM} -f ${.TARGET};					\
+#		${TOUCH} ${TOUCH_ARGS} ${.TARGET};			\
+#	fi
+# PKGSRC now creates its own user, pkgvcsconf, when running as root with conf
+# tracking enabled. The functions it uses are part of the usergroup scripts
 
 _INSTALL_USERGROUP_UNPACKER=	${_PKGINSTALL_DIR}/usergroup-unpack
 
@@ -390,7 +395,7 @@ su-create-usergroup: ${_INSTALL_USERGROUP_UNPACKER}
 #
 # GAMEDATA_PERMS and GAMEDIR_PERMS are convenience definitions for files
 # that are meant to be accessed by things that are setgid games. Because
-# such files should normally be under ${VARBASE}, generally these 
+# such files should normally be under ${VARBASE}, generally these
 # definitions should be used roughly as follows:
 #
 #	REQD_DIRS_PERMS+=  /path/to/scoredir ${GAMEDIR_PERMS}
@@ -493,6 +498,9 @@ _INSTALL_FILES_DATAFILE=	${_PKGINSTALL_DIR}/files-data
 _INSTALL_UNPACK_TMPL+=		${_INSTALL_FILES_FILE}
 _INSTALL_DATA_TMPL+=		${_INSTALL_FILES_DATAFILE}
 
+_INSTALL_VERSIONING_FILE=	${_PKGINSTALL_DIR}/versioning
+_INSTALL_UNPACK_TMPL+=		${_INSTALL_VERSIONING_FILE}
+
 # Only generate init scripts if we are using rc.d
 _INSTALL_RCD_SCRIPTS=	# empty
 
@@ -600,6 +608,10 @@ ${_INSTALL_FILES_FILE}: ../../mk/pkginstall/files
 		${RM} -f ${.TARGET};					\
 		${TOUCH} ${TOUCH_ARGS} ${.TARGET};			\
 	fi
+${_INSTALL_VERSIONING_FILE}: ../../mk/pkginstall/versioning
+	${RUN}${MKDIR} ${.TARGET:H}
+	${RUN}	\
+	${SED} ${FILES_SUBST_SED} ../../mk/pkginstall/versioning > ${.TARGET}
 
 # OWN_DIRS contains a list of directories for this package that should be
 #       created and should attempt to be destroyed by the INSTALL/DEINSTALL
@@ -1076,6 +1088,9 @@ FILES_SUBST+=		FONTS_VERBOSE=${FONTS_VERBOSE:Q}
 FILES_SUBST+=		INFO_FILES_VERBOSE=${INFO_FILES_VERBOSE:Q}
 FILES_SUBST+=		OCAML_FINDLIB_REGISTER_VERBOSE=${OCAML_FINDLIB_REGISTER_VERBOSE:Q}
 
+#workaround systems without $RANDOM by using urandom
+USE_TOOLS+=		fold
+
 # Substitute for various programs used in the DEINSTALL/INSTALL scripts and
 # in the rc.d scripts.
 #
@@ -1087,6 +1102,8 @@ FILES_SUBST+=		CHMOD=${CHMOD:Q}
 FILES_SUBST+=		CHOWN=${CHOWN:Q}
 FILES_SUBST+=		CMP=${CMP:Q}
 FILES_SUBST+=		CP=${CP:Q}
+FILES_SUBST+=		CUT=${CUT:Q}
+FILES_SUBST+=		DIFF=${DIFF:Q}
 FILES_SUBST+=		DIRNAME=${DIRNAME:Q}
 FILES_SUBST+=		ECHO=${ECHO:Q}
 FILES_SUBST+=		ECHO_N=${ECHO_N:Q}
@@ -1094,6 +1111,7 @@ FILES_SUBST+=		EGREP=${EGREP:Q}
 FILES_SUBST+=		EXPR=${EXPR:Q}
 FILES_SUBST+=		FALSE=${FALSE:Q}
 FILES_SUBST+=		FIND=${FIND:Q}
+FILES_SUBST+=		FOLD=${FOLD:Q}
 FILES_SUBST+=		GREP=${GREP:Q}
 FILES_SUBST+=		GROUPADD=${GROUPADD:Q}
 FILES_SUBST+=		GTAR=${GTAR:Q}
@@ -1119,13 +1137,62 @@ FILES_SUBST+=		SETENV=${SETENV:Q}
 FILES_SUBST+=		SH=${SH:Q}
 FILES_SUBST+=		SORT=${SORT:Q}
 FILES_SUBST+=		SU=${SU:Q}
+FILES_SUBST+=		TAIL=${TAIL:Q}
 FILES_SUBST+=		TEST=${TEST:Q}
 FILES_SUBST+=		TOUCH=${TOUCH:Q}
 FILES_SUBST+=		TR=${TR:Q}
 FILES_SUBST+=		TRUE=${TRUE:Q}
 FILES_SUBST+=		USERADD=${USERADD:Q}
+FILES_SUBST+=		WC=${WC:Q}
 FILES_SUBST+=		XARGS=${XARGS:Q}
 
+DISABLE_RCSDEP=no
+.for _test in ${USE_TOOLS}
+.	if "${_test}" == "${PKGBASE}"
+DISABLE_RCSDEP=yes
+.	endif
+.	for _ttype_ in "bootstrap" "run" "pkgsrc" "build"
+.		if "${_test}" == "{PKBGASE}:${_ttype_}"
+DISABLE_RCSDEP=yes
+.		endif
+.	endfor
+.endfor
+
+.if "${DISABLE_RCSDEP}" == "no" && !defined(IGNORE_VCS)
+.	if defined(TOOLS_PLATFORM.rcs)
+RCS=${TOOLS_PLATFORM.rcs}
+.	else
+USE_TOOLS+=	rcs
+RCS=${TOOLS_PATH.rcs}
+.	endif
+FILES_SUBST+=		RCS=${RCS:Q}
+
+.	if defined(TOOLS_PLATFORM.ci)
+CI=${TOOLS_PLATFORM.ci}
+.	else
+USE_TOOLS+=	ci
+CI=${TOOLS_PATH.ci}
+.	endif
+FILES_SUBST+=		CI=${CI:Q}
+
+.	if defined(TOOLS_PLATFORM.co)
+CO=${TOOLS_PLATFORM.co}
+.	else
+USE_TOOLS+=	co
+CO=${TOOLS_PATH.co}
+.	endif
+FILES_SUBST+=		CO=${CO:Q}
+
+.	if defined(TOOLS_PLATFORM.merge)
+MERGE=${TOOLS_PLATFORM.merge}
+.	else
+USE_TOOLS+=	merge
+MERGE=${TOOLS_PATH.merge}
+.	endif
+FILES_SUBST+=		MERGE=${MERGE:Q}
+
+.endif
+
 FILES_SUBST_SED=	${FILES_SUBST:S/=/@!/:S/$/!g/:S/^/ -e s!@/}
 
 PKG_REFCOUNT_DBDIR?=	${PKG_DBDIR}.refcount
diff --git a/mk/pkginstall/files b/mk/pkginstall/files
index edbb666f3c32..590e38241959 100644
--- a/mk/pkginstall/files
+++ b/mk/pkginstall/files
@@ -53,6 +53,7 @@ CHGRP="@CHGRP@"
 CHMOD="@CHMOD@"
 CHOWN="@CHOWN@"
 CMP="@CMP@"
+DIFF="@DIFF@"
 ECHO="@ECHO@"
 GREP="@GREP@"
 LS="@LS@"
@@ -68,7 +69,6 @@ TRUE="@TRUE@"
 
 SELF=$0
 ACTION=$1
-
 : ${PKG_PREFIX=@PREFIX@}
 
 case "${PKG_CONFIG:-@PKG_CONFIG@}" in
@@ -95,15 +95,38 @@ case "${PKG_RCD_SCRIPTS:-@PKG_RCD_SCRIPTS@}" in
 	_PKG_RCD_SCRIPTS=no
 	;;
 esac
-
 CURDIR=`${PWD_CMD}`
 PKG_METADATA_DIR="${2-${CURDIR}}"
 : ${PKGNAME=${PKG_METADATA_DIR##*/}}
 : ${PKG_DBDIR=${PKG_METADATA_DIR%/*}}
 : ${PKG_REFCOUNT_DBDIR=${PKG_DBDIR}.refcount}
 PKG_REFCOUNT_FILES_DBDIR="${PKG_REFCOUNT_DBDIR}/files"
-
+_VCSCONFPULLFAIL=$3
+_VCSDIR="${VCSDIR:-@VARBASE@/confrepo}"
+_VCS_ENABLED="${VCSTRACK_CONF:-no}"
+case "${_VCS_ENABLED}" in
+[Nn][Oo])
+	_VCS_ENABLED=no
+	;;
+[Yy][Ee][Ss])
+	_VCS_ENABLED=yes
+	;;
+esac
+case "${VCSCONFPULL}" in
+[Yy][Ee][Ss])
+	VCSCONFPULL=yes
+	;;
+esac
+case "${VCSAUTOMERGE}" in
+[Yy][Ee][Ss])
+	VCSAUTOMERGE=yes
+	;;
+*)
+	VCSAUTOMERGE=no
+	;;
+esac
 exitcode=0
+
 case $ACTION in
 ADD)
 	${SED} -n "/^\# FILE: /{s/^\# FILE: //;p;}" ${SELF} | ${SORT} -u |
@@ -141,15 +164,183 @@ ADD)
 		"")	;;
 	 	*)	${ECHO} "$f_mode $f_user $f_group" > $perms ;;
 		esac
-		if ${TEST} ! -f "$file" -a ! -f "$f_eg" -a ! -c "$f_eg"; then
+
+		if ${TEST} ! -f "$file" \
+		&& ${TEST} ! -f "$f_eg" \
+		&& ${TEST} ! -c "$f_eg"; then
 			:
 		else
 			case "$f_flags:$_PKG_CONFIG:$_PKG_RCD_SCRIPTS" in
 			*f*:*:*|[!r]:yes:*|[!r][!r]:yes:*|[!r][!r][!r]:yes:*|*r*:yes:yes)
-				if ${TEST} -f "$file"; then
-					${ECHO} "${PKGNAME}: $file already exists"
-				elif ${TEST} -f "$f_eg" -o -c "$f_eg"; then
-					${ECHO} "${PKGNAME}: copying $f_eg to $file"
+				if ${TEST} "${_VCS_ENABLED}" = "yes" \
+				&& ${TEST} "${f_flags%r*}" = "${f_flags}" \
+				&& ${TEST} "${VCSCONFPULL}" != "yes"; then
+					if ${TEST} ! -d "${_VCSDIR}"; then
+						${MKDIR} -p "${_VCSDIR}"
+						if ${TEST} $? -ne 0; then
+							_VCS_ENABLED=no
+						fi
+					fi
+					#drop privileges
+					if ${TEST} "${USER}" = "root"; then
+						${CHOWN} pkgvcsconf:pkgvcsconf\
+						"${_VCSDIR}"
+					fi
+					${CHMOD} 0700 "${_VCSDIR}"		
+					for dir in "defaults" "automerged" "user"
+					do	
+						if ${TEST} ! -d "${_VCSDIR}/${dir}"; then
+							${RM} -f "${_VCSDIR}/${dir}"
+							${MKDIR} -p "${_VCSDIR}/${dir}"
+						fi
+					done
+					${TEST} ! -x ./+VERSIONING ||
+						./+VERSIONING PREPARE
+					if ${TEST} $? -ne 0; then
+						${ECHO} 1>&2 "Failed to initialize the repository that should store configuration revisions at ${_VCSDIR}!"
+						${ECHO} 1>&2 "Temporarily disabling configuration files version tracking"
+						_VCS_ENABLED=no
+					fi
+					if ${TEST} -w "${_VCSDIR}/defaults"; then
+						${MKDIR} -p "${_VCSDIR}/defaults/${file}" 2>/dev/null
+						${RMDIR} "${_VCSDIR}/defaults/${file}" 2>/dev/null
+						${CP} -fp "${f_eg}" "${_VCSDIR}/defaults/${file}"
+						${TEST} ! -x ./+VERSIONING ||
+							./+VERSIONING REGISTER "${_VCSDIR}/defaults/${file}" 
+							${ECHO} REGISTER "${_VCSDIR}/defaults/${file}"
+					else
+						${ECHO} 1>&2 "${_VCSDIR} is not writable!"
+					fi	
+				fi
+				if ${TEST} -f "${file}"; then
+					${ECHO} "${PKGNAME}: ${file} already exists"
+					if ${TEST} "${_VCS_ENABLED}" = "yes" \
+					&& ${TEST} ! "${VCSCONFPULL}" = "yes"\
+					&& ${TEST} "${f_flags%r*}" = "${f_flags}"; then
+						if ${TEST} "${VCSAUTOMERGE}" = "yes"; then
+							${ECHO} "${PKGNAME}: attempting to merge ${file} with new defaults!"
+							merge_userinstalled()
+							{
+								${ECHO} "Saving the currently user-installed revision to ${_VCSDIR}/user/${file}"
+								${MKDIR} -p "${_VCSDIR}/user/${file}" 2>/dev/null
+								${RMDIR} "${_VCSDIR}/user/${file}" 2>/dev/null
+								${CP} -fp "${file}" "${_VCSDIR}/user/${file}"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING REGISTER "${_VCSDIR}/user/${file}"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING COMMIT "backup user conf before attempting merge for $PKGNAME"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING CHECKOUT-FIRST "${_VCSDIR}/defaults/${file}"
+								${CP} -fp "${file}" "${_VCSDIR}/defaults/${file}.automerge"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING MERGE "${f_eg}" "${_VCSDIR}/defaults/${file}"
+								mergestatus=$?
+								case ${mergestatus} in
+								0)
+									${ECHO} "Merged with no conflicts. Installing the configuration to ${file}!"
+									${DIFF} -u "${file}" "${_VCSDIR}/defaults/${file}.automerge"
+									${CP} -fp "${_VCSDIR}/defaults/${file}.automerge" "${file}"
+									if ${TEST} $? -eq 0; then
+										${ECHO} "${file}" >> "${_VCSDIR}/automergedfiles"
+										${MKDIR} -p "${_VCSDIR}/automerged/${file}" 2>/dev/null
+										${RMDIR} "${_VCSDIR}/automerged/${file}" 2>/dev/null
+										${CP} -fp "${_VCSDIR}/defaults/${file}.automerge" "${_VCSDIR}/automerged/${file}"
+										${TEST} ! -x ./+VERSIONING ||
+											./+VERSIONING REGISTER "${_VCSDIR}/automerged/${file}"
+									fi
+									${TEST} ! -x ./+VERSIONING ||
+										./+VERSIONING CHECKOUT "${_VCSDIR}/defaults/${file}"
+									${ECHO} "Revert from the last revision of ${_VCSDIR}/user/${file} if needed"
+									;;
+								1)
+									${ECHO} "Some conflicts merging. Manually review them in"
+									${ECHO} "${_VCSDIR}/defaults/${file}.automerge"
+									${ECHO} "then copy the result to ${file}"
+									;;
+								*)
+									${ECHO} "Merge exited with errors. Not changing anything"
+									;;
+								esac
+							}
+
+							merge_automerged()
+								{
+								${ECHO} "Saving the currently installed revision to ${_VCSDIR}/automerged/${file}"
+								${MKDIR} -p "${_VCSDIR}/automerged/${file}" 2>/dev/null
+								${RMDIR} "${_VCSDIR}/automerged/${file}" 2>/dev/null
+								${CP} -fp "${file}" "${_VCSDIR}/automerged/${file}"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING REGISTER "${_VCSDIR}/automerged/${file}"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING COMMIT "backup preexisting conf before attempting merge for ${PKGNAME}"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING CHECKOUT-FIRST "${_VCSDIR}/defaults/${file}"
+								${CP} -fp "${file}" "${_VCSDIR}/defaults/${file}.automerge"
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING MERGE "${f_eg}" "${_VCSDIR}/defaults/${file}"
+								mergestatus=$?
+								case ${mergestatus} in
+								0)
+									${ECHO} "Merged with no conflict. installing it to ${file}!"
+									${DIFF} -u "${file}" "${_VCSDIR}/defaults/${file}.automerge"
+									${CP} -fp "${_VCSDIR}/defaults/${file}.automerge" "${file}"
+									${TEST} -x ./+VERSIONING ||
+										./+VERSIONING CHECKOUT "${_VCSDIR}/defaults/${file}"
+									${CP} -fp "${_VCSDIR}/defaults/${file}.automerge" "${_VCSDIR}/automerged/${file}"
+									${TEST} -x ./+VERSIONING ||
+										./+VERSIONING CHECKOUT "${_VCSDIR}/automerged/${file}"
+									${ECHO} "Revert from the penultimate revision of ${_VCSDIR}/automerged/${file} if needed"
+									;;
+								1)
+									${ECHO} "Some conflicts merging. manually review them in"
+									${ECHO} "${_VCSDIR}/defaults/${file}.automerge"
+									${ECHO} "then copy the result to ${file}"
+									;;
+								*)
+									${ECHO} "Merge exited with errors. not changing anything"
+									;;
+								esac
+							}
+
+							if ${TEST} -f "${_VCSDIR}/automergedfiles" && ${GREP} -F "${file}" "${_VCSDIR}/automergedfiles" > /dev/null; then
+								#check if the automergedfile was manually edited after being automatically registered as merged
+								${TEST} ! -x ./+VERSIONING ||
+									./+VERSIONING CHECKOUT "${_VCSDIR}/automerged/${file}"
+								if ${TEST} -f "${_VCSDIR}/automerged/${file}"; then
+									${DIFF} -q "${file}" "${_VCSDIR}/automerged/${file}" 2>/dev/null
+									if ${TEST} $? -ne 0; then
+										#the installed file and the last automerged version differ! must have been manually edited after being committed
+										${GREP} -Fv "${file}" "${_VCSDIR}/automergedfiles" > "${_VCSDIR}/automergedfiles.rm"
+										${MV} -f "${_VCSDIR}/automergedfiles.rm" "${_VCSDIR}/automergedfiles"
+										merge_userinstalled
+									else
+										merge_automerged
+									fi
+								else
+									merge_automerged
+								fi
+							else
+								merge_userinstalled
+							fi
+						else
+						#always track installed conf files
+							if ${GREP} -Fv "${file}" "${_VCSDIR}/automergedfiles" 2>/dev/null; then
+								${MKDIR} -p "${_VCSDIR}/user/${file}" 2>/dev/null;
+								${RMDIR} "${_VCSDIR}/user/${file}" 2>/dev/null;
+								${CP} -fp "${file}" "${_VCSDIR}/user/${file}";
+								if ${TEST} $? -eq 0; then
+									${ECHO} "Attempting to track the existing configuration file"
+									${TEST} ! -x ./+VERSIONING ||
+										./+VERSIONING REGISTER "${_VCSDIR}/user/${file}"
+								fi
+							fi
+						fi
+					fi
+				elif ( ${TEST} -f "${f_eg}" || ${TEST} -c "${f_eg}" ) \
+				&& ( ${TEST} "${_VCSCONFPULLFAIL}" = "yes" \
+				|| ${TEST} ! "${VCSCONFPULL}" = "yes" ) \
+				|| ${ECHO} "${f_flags}" | ${GREP} -F "r"; then
+					${ECHO} "${PKGNAME}: copying ${f_eg} to ${file}"
 					${CP} $f_eg $file
 					case $f_user in
 					"")	;;
@@ -168,6 +359,11 @@ ADD)
 			esac
 		fi
 	done
+	if ${TEST} "${_VCS_ENABLED}" = "yes" \
+	&& ${TEST} "${VCSCONFPULL}" != "yes"; then
+		${TEST} ! -x ./+VERSIONING ||
+			./+VERSIONING COMMIT "add ${PKGNAME}"
+	fi 
 	;;
 
 REMOVE)
@@ -198,7 +394,7 @@ REMOVE)
 			"")
 				if ${TEST} -f "$preexist"; then
 					:
-				elif ${TEST} -f "$file" -a \( -f "$f_eg" -o -c "$f_eg" \) && \
+				elif ${TEST} -f "${file}" && ( ${TEST} -f "${f_eg}" || ${TEST} -c "${f_eg}" ) && \
 				     ${CMP} -s "$file" "$f_eg"; then
 					case "$f_flags:$_PKG_CONFIG:$_PKG_RCD_SCRIPTS" in
 					*f*:*:*|[!r]:yes:*|[!r][!r]:yes:*|[!r][!r][!r]:yes:*|*r*:yes:yes)
diff --git a/mk/pkginstall/header b/mk/pkginstall/header
index 1a270cd0ea92..226b7ee1abc5 100644
--- a/mk/pkginstall/header
+++ b/mk/pkginstall/header
@@ -50,9 +50,12 @@ XARGS="@XARGS@"
 
 CURDIR=`${PWD_CMD}`
 : ${PKG_METADATA_DIR=${CURDIR}}
-PKGBASE="@PKGBASE@"
+PKGPATH="@PKGPATH@"
+PKGVERSION="@PKGVERSION@"
+SIMPLENAME="@SIMPLENAME@"
 
 LOCALBASE="@LOCALBASE@"
+VARBASE="@VARBASE@"
 X11BASE="@X11BASE@"
 PREFIX="@PREFIX@"
 
diff --git a/mk/pkginstall/install b/mk/pkginstall/install
index 2edaf97032ae..4ff9fe0db0b0 100644
--- a/mk/pkginstall/install
+++ b/mk/pkginstall/install
@@ -1,5 +1,20 @@
 # $NetBSD: install,v 1.5 2017/06/14 16:23:09 prlw1 Exp $
 
+case "${VCSTRACK_CONF}" in
+[Yy][Ee][Ss])
+	VCSTRACK_CONF=yes
+	;;
+esac
+case "${VCSCONFPULL}" in
+[Yy][Ee][Ss])
+	VCSCONFPULL=yes
+	;;
+esac
+case "${REMOTEVCS}" in
+[Nn][Oo])
+	REMOTEVCS=no
+	;;
+esac
 case ${STAGE} in
 PRE-INSTALL)
 	#
@@ -21,6 +36,11 @@ PRE-INSTALL)
 		./+DIRS ADD ${PKG_METADATA_DIR}
 	${TEST} ! -x ./+DIRS ||
 		./+DIRS PERMS ${PKG_METADATA_DIR}
+	if ${TEST} "${VCSTRACK_CONF}" = "yes" && ${TEST} "${USER}" = "root"; then
+		#create an unprivileged user for vcs conf tracking
+		${TEST} ! -x ./+USERGROUP ||
+			./+USERGROUP ADDUSER pkgvcsconf pkgvcsconf
+	fi
         ;;
 
 POST-INSTALL)
@@ -32,10 +52,22 @@ POST-INSTALL)
 	#
 	# Copy configuration/support files into place.
 	#
-        ${TEST} ! -x ./+FILES ||
-		./+FILES ADD ${PKG_METADATA_DIR}
-        ${TEST} ! -x ./+FILES ||
-		./+FILES PERMS ${PKG_METADATA_DIR}
+	_VCSCONFPULLFAIL=no
+	if ${TEST} "${VCSCONFPULL}" = "yes" \
+		&& ${TEST} -n "${REMOTEVCS}" && ${TEST} "${REMOTEVCS}" != "no"; then
+		${TEST} ! -x ./+VERSIONING ||
+			./+VERSIONING PULL ${PKGPATH} ${PKGVERSION} ${PKGNAME}
+			#if it fails, run the normal files routine
+		if ${TEST} ! $? -eq 0; then 
+			#_VCSCONFPULLFAIL=yes makes files install example config
+			# from the package; rc.d scripts are always installed
+			_VCSCONFPULLFAIL=yes
+		fi
+	fi
+	${TEST} ! -x ./+FILES ||
+		./+FILES ADD ${PKG_METADATA_DIR} "$_VCSCONFPULLFAIL"
+	${TEST} ! -x ./+FILES ||
+		./+FILES PERMS ${PKG_METADATA_DIR} "$_VCSCONFPULLFAIL"
 	#
 	# Set special permissions on any files/directories that need them.
 	#
diff --git a/mk/pkginstall/usergroup b/mk/pkginstall/usergroup
index 1b15dc321e38..9d92e858c05e 100644
--- a/mk/pkginstall/usergroup
+++ b/mk/pkginstall/usergroup
@@ -12,6 +12,7 @@ UNPACK,|UNPACK,+USERGROUP)
 #
 # Usage: ./+USERGROUP ADD|REMOVE [metadatadir]
 #        ./+USERGROUP CHECK-ADD|CHECK-REMOVE [metadatadir]
+#        ./+USERGROUP ADDUSER username group [userid]
 #
 # This script supports two actions, ADD and REMOVE, that will add or
 # remove the users and groups needed by the package associated with
@@ -22,6 +23,9 @@ UNPACK,|UNPACK,+USERGROUP)
 # and print an informative message noting those users and groups.  The
 # CHECK-ADD and CHECK-REMOVE actions return non-zero if they detect
 # either missing or existing users/groups, respectively.
+# An additional action, ADDUSER, exist to check for and
+# add specific users not defined by the package 
+# (for pkgsrc/pkginstall own use).
 #
 # Lines starting with "# USER: " or "# GROUP: " are data read by this
 # script that name the users and groups that this package requires to
@@ -111,6 +115,37 @@ listwrap()
 
 exitcode=0
 case $ACTION in
+ADDUSER)
+	user="$2"; group="$3"; userid="$4"
+	if ${TEST} -z "$user" || ${TEST} -z "$group"; then
+		exit 1
+	fi
+	USERADD="@USERADD@"
+	group_exists $group
+	if ${TEST} $? -gt 0; then
+		addgroup "$group"
+	fi
+	user_exists $user $userid
+	case "$?" in
+	0)
+		#user exists
+		;;
+	2)
+		#user exists, userid doesn't match it
+		${ECHO} 1>&2 "The user $user exists, but it does not match userid $userid. Fix this manually!"
+		;;
+	*)
+		case $userid in
+		"")
+			${USERADD} -m -s "@SH@" -g $group $user
+			;;
+		*)
+			${USERADD} -m -s "@SH@" -g $group -u $userid $user
+			;;
+		esac
+		;;
+	esac
+	;;
 ADD)
 	${SED} -n "/^\# GROUP: /{s/^\# GROUP: //;p;}" ${SELF} | ${SORT} -u |
 	{ while read line; do
@@ -372,6 +407,7 @@ CHECK-REMOVE)
 *)
 	${ECHO} "Usage: ./+USERGROUP ADD|REMOVE [metadatadir]"
 	${ECHO} "       ./+USERGROUP CHECK-ADD|CHECK-REMOVE [metadatadir]"
+	${ECHO} "	./+USERGROUP ADDUSER USERNAME GROUP [USERID]"
 	;;
 esac
 exit $exitcode
diff --git a/mk/pkginstall/usergroupfuncs b/mk/pkginstall/usergroupfuncs
index 7b0cad29e33f..25edcd50b3d2 100644
--- a/mk/pkginstall/usergroupfuncs
+++ b/mk/pkginstall/usergroupfuncs
@@ -96,7 +96,7 @@ adduser()
 {
 	user="$1"; group="$2"; userid="$3"
 	descr="$4"; home="$5" shell="$6"
-	${TEST} $# -eq 6 || return 1
+	${TEST} $# -gt 1 || return 1
 	${TEST} -n "$user" || return 2
 	${TEST} -n "$group" || return 2
 
@@ -132,7 +132,7 @@ adduser()
 addgroup()
 {
 	group="$1"; groupid="$2"
-	${TEST} $# -eq 2 || return 1
+	${TEST} $# -gt 0 || return 1
 	${TEST} -n "$group" || return 2
 
 	GROUPADD="@GROUPADD@"
diff --git a/mk/pkginstall/usergroupfuncs.DragonFly b/mk/pkginstall/usergroupfuncs.DragonFly
index 78aaa3f24ee3..d8901b947b08 100644
--- a/mk/pkginstall/usergroupfuncs.DragonFly
+++ b/mk/pkginstall/usergroupfuncs.DragonFly
@@ -126,7 +126,7 @@ adduser()
 addgroup()
 {
 	group="$1"; groupid="$2"
-	${TEST} $# -eq 2 || return 1
+	${TEST} $# -gt 1 || return 1
 	${TEST} -n "$group" || return 2
 
 	PW="@PW@"
diff --git a/mk/pkginstall/usergroupfuncs.FreeBSD b/mk/pkginstall/usergroupfuncs.FreeBSD
index 9454bc37a958..2984b4a24213 100644
--- a/mk/pkginstall/usergroupfuncs.FreeBSD
+++ b/mk/pkginstall/usergroupfuncs.FreeBSD
@@ -126,7 +126,7 @@ adduser()
 addgroup()
 {
 	group="$1"; groupid="$2"
-	${TEST} $# -eq 2 || return 1
+	${TEST} $# -gt 1 || return 1
 	${TEST} -n "$group" || return 2
 
 	PW="@PW@"
diff --git a/mk/pkginstall/usergroupfuncs.Linux b/mk/pkginstall/usergroupfuncs.Linux
index c2791ce7cd3b..abe2e3c86150 100644
--- a/mk/pkginstall/usergroupfuncs.Linux
+++ b/mk/pkginstall/usergroupfuncs.Linux
@@ -141,7 +141,7 @@ adduser()
 addgroup()
 {
 	group="$1"; groupid="$2"
-	${TEST} $# -eq 2 || return 1
+	${TEST} $# -gt 1 || return 1
 	${TEST} -n "$group" || return 2
 
 	GROUPADD="@GROUPADD@"
diff --git a/mk/pkginstall/versioning b/mk/pkginstall/versioning
new file mode 100644
index 000000000000..b9e2fa6cabd6
--- /dev/null
+++ b/mk/pkginstall/versioning
@@ -0,0 +1,967 @@
+#
+# Generate a +VERSIONING script used by FILES and INSTALL to perform common
+# operations on version control software used to store, retrieve and merge
+# package configuration files during installation and upgrade operations. 
+#
+case "${STAGE},$1" in
+UNPACK,|UNPACK,+VERSIONING)
+	${CAT} > ./+VERSIONING << 'EOF'
+#!@SH@
+#
+# +VERSIONING - operate on version control systems
+# set VCSTRACK_CONF=yes in order to enable configuration file version tracking!
+#
+# This script calls VCS software, "rcs" by default, in order to store revisions
+# of configuration files. This is done by the "REGISTER" action, which takes 
+# the path to an example configuration file as argument.
+#
+# Said file should be placed in the VCS working directory by the "files" script
+# VERSIONING only handles the storage of revisions inside a vcs.
+# Once all configuration files for a package are registered, files should call
+# the COMMIT action, which will commit changes on backends that support
+# atomic transactions (if using rcs, commit won't execute further operations).
+#
+# MERGE calls RCS merge to attempt a 3-way merge between the copy of an  
+# installed configuration file, its original revision and the last example file
+# as provided by the package. This is a non-interactive merge, if successful
+# "files" will install the output in place of the existing configuration.
+#
+# CHECKOUT and CHECKOUT-FIRST exist to assist "files" in retrieving revisions
+# before attempting an automatic merge.
+#
+# Files only attempts to automatically merge changes if the environment
+# variable VCSAUTOMERGE is set to "yes". A backup of the installed configuration
+# is taken first, and user modified configuration files are stored separately,
+# in order to enable for a quick restoration of the last known working file.
+#
+# PREPARE is called before registering files: under some VCSs it checks
+# that the working directory and/or the remote repository is correctly
+# initialized, and initializes it otherwise. 
+#
+# The VCSDIR environment variable is read to set the working directory,
+# under which the repository will also reside, if a local vcs is being used.
+# It defaults to VARBASE/confrepo.
+#
+# The VCS backend to be used can be set via the VCS environment variable;
+# it defaults to RCS (Revision Control System).
+#
+# REMOTEVCS, if set, must contain a string that the chosen VCS understands as
+# an URI to a remote repository, including login credentials if not specified
+# through other means. This is non standard across different backends, and
+# additional environment variables and cryptographic material 
+# may need to be provided. 
+# Remember, ssh keys must be placed under $HOME/.ssh, except when you are root.
+# Because we drop privileges to the user "pkgvcsconf", place keys at pkgvcsconf
+# own HOME directory, under .ssh/, or run an accessible ssh-agent socket and
+# unlock the required key via ssh-add.
+# 
+# PULL tries to deploy configuration from a remote repository. It needs
+# REMOTEVCS to be set, and gets called from +INSTALL if VCSCONFPULL=yes
+#
+# The remote configuration repository should contain branches named
+# according to the following convention:
+# category_pkgName_pkgVersion_compatRangeStart_compatRangeEnd_systemRole
+# an optional field may exist that explicitates part of the system hostname
+# category_pkgName_pkgVersion_compatRangeStart_compatRangeEnd_systemRole_hostname
+#
+# the branch should contain needed configuration files. Their path relative
+# to the repository is then prepended with a "/" and files force copied
+# to the system and chmod 0600 executed on them.
+# Permission handling and removal upon package uninstallation are not supported.
+#
+# The branch to be used, among the available ones, is chosen this way:
+# branches named according to the convention that provide configuration
+# for category/packageName are filtered from the VCS output;
+# then, all branches whose ranges are compatible with the version of the
+# package being installed are selected. The upper bound of the range is
+# excluded as a compatible release if using sequence based identifiers.
+# If system role is set through the ROLE environment variable, 
+# and it's different from "any",
+# and branches exists whose role is different from "any", then their
+# role gets compared with the one defined on the system or in pkg_add config.
+# The last part of the branch name is optional and, if present, is compared
+# character by character with the system hostname, 
+# finally selecting the branches that best match it.
+# As an example, a branch named mail_postfix_3.3.0_3.0.0_3.3.20_mailrelay_ams
+# will match with system hostname amsterdam09.
+# A system with its ROLE unspecified or set to ANY will select branches
+# independently of the role they are created for, scoring and using the one
+# with the best matching optional hostname and/or nearest to the target release
+# as explained below:
+#
+# The checks now further refine the candidates: if a branch pkgVersion exactly
+# corresponds with the version of the package being installed,
+# that branch gets selected, otherwise the procedure uses the one
+# which is closest to the package version being installed.
+#
+# Non numerical values in package versions are accounted for
+# when checking for an exact match, and are otherwise ignored.
+# Only integer versions and dot-separated sequence based identifiers are
+# understood when checking for compatible software ranges and for the closest
+# branch, if no branch exactly matches with the package version being installed.#
+# Dates are handled provided they follow the ISO 8601 scheme: YYYY-MM-DD, YYYYMMDD 
+#
+# Usage: ./+VERSIONING REGISTER|CHECKOUT|CHECKOUT-FIRST [examplefile]
+#        ./+VERSIONING MERGE [examplefile] [firstrevision]
+#        ./+VERSIONING COMMIT [message]
+#        ./+VERSIONING PREPARE
+#        ./+VERSIONING PULL PKGPATH PKGVERSION PKGNAME 
+#
+AWK="@AWK@"
+CAT="@CAT@"
+CHMOD="@CHMOD@"
+CHOWN="@CHOWN@"
+CI="@CI@"
+CO="@CO@"
+CP="@CP@"
+CUT="@CUT@"
+DIRNAME="@DIRNAME@"
+ECHO="@ECHO@"
+EXPR="@EXPR@"
+FIND="@FIND@"
+FOLD="@FOLD@"
+GREP="@GREP@"
+HEAD="@HEAD@"
+LS="@LS@"
+MERGE="@MERGE@"
+MKDIR="@MKDIR@"
+MV="@MV@"
+PWD_CMD="@PWD_CMD@"
+RCS="@RCS@"
+RM="@RM@"
+RMDIR="@RMDIR@"
+SED="@SED@"
+SORT="@SORT@"
+TAIL="@TAIL@"
+TEST="@TEST@"
+TR="@TR@"
+TRUE="@TRUE@"
+WC="@WC@"
+
+SELF=$0
+ACTION=$1
+CFILE=$2
+FIRSTFILE=$3
+exitcode=0
+
+#VCSDIR: user set environment variable, the working directory under which a local repository may also be kept
+_VCSDIR="${VCSDIR:-@VARBASE@/confrepo}"
+
+#VCS: the versioning system to be used. Defaults to rcs, other solutions are searched in $PATH
+_VCS="${VCS:-rcs}"
+case "${_VCS}" in
+[Rr][Cc][Ss])
+	_VCS=rcs
+	;;
+[Gg][Ii][Tt])
+	_VCS=git
+	;;
+[Cc][Vv][Ss])
+	_VCS=cvs
+	;;
+[Hh][Gg])
+	_VCS=mercurial
+	;;
+[Ss][Vv][Nn])
+	_VCS=subversion
+	;;
+[Mm][Ee][Rr][Cc][Uu][Rr][Ii][Aa][Ll])
+	_VCS=mercurial
+	;;
+[Ss][Uu][Bb][Vv][Ee][Rr][Ss][Ii][Oo][Nn])
+	_VCS=subversion
+	;;
+esac
+
+#REMOTEVCS: set the URI to the remote repository, leave unset or set to no in order to use a local repository 
+#the URI, while required for pull/configuration deployment mode, does not enable it. by default the remote is only used to store the system configuration 
+_REMOTE="${REMOTEVCS:-no}"
+case "${_REMOTE}" in
+[Nn][Oo])
+	_REMOTE=no
+	;;
+esac
+if ${TEST} "$_VCS" = "cvs" || ${TEST} "$_VCS" = "CVS"; then
+	if ${TEST} "$_REMOTE" != "no"; then
+		_CVSROOT="$_REMOTE"
+	else
+		_CVSROOT="${_VCSDIR}/CVSROOT"
+	fi
+fi
+#ROLE: the system role, used in configuration deploy mode. Defaults to any if unset
+_ROLE="${ROLE:-any}"
+case "${_ROLE}" in
+[Aa][Nn][Yy])
+	_ROLE=any
+	;;
+esac
+execute()
+{
+	if ${TEST} "${USER}" = "root"; then
+		su -m pkgvcsconf -c "${1}"
+		return $?
+	else
+		eval "${1}"
+		return $?
+	fi
+}
+
+pkgchown()
+{
+	_restoreDir="`${PWD_CMD}`"
+	if ${ECHO} "${1}" | ${GREP} -Fv "${_VCSDIR}" > /dev/null; then
+		${ECHO} 1>&2 "Path \"${1}\" not under VCSDIR: \"${_VCSDIR}\""
+		return 1
+	fi
+	if ${TEST} -d "${_VCSDIR}"; then
+		${CHMOD} 700 "${_VCSDIR}"
+		${CHOWN} pkgvcsconf:pkgvcsconf "${_VCSDIR}"
+	fi
+	if ${TEST} -d "${1}"; then
+		if ${TEST} -n "`${FIND} "${1}" -perm -2000 -or -perm -4000 -print -quit 2>/dev/null `"; then
+			${ECHO} 1>&2 "SUID/SGID files under ${1}, refusing to run CHOWN -R"
+		else
+			${CHMOD} 700 "${1}"
+			${CHOWN} -R pkgvcsconf:pkgvcsconf "${1}"
+		fi
+		if ${TEST} "${1}" = "${_VCSDIR}"; then
+			return 0
+		fi
+	fi
+	if ${TEST} -f "${1}"; then
+		if ${TEST} -u "${1}" || ${TEST} -g "${1}"; then
+			${ECHO} 1>&2 "SUID/SGID file at ${1}, refusing to chown it"
+		else
+			${CHMOD} 600 "${1}"
+			${CHOWN} pkgvcsconf:pkgvcsconf "${1}"
+		fi
+		_dirs="`${DIRNAME} "${1}"`"
+	else
+		_dirs="${1}"
+	fi
+	_dirsplit="`${ECHO} "${_dirs}" | ${AWK} -F "${_VCSDIR}" '{print $2}'| ${TR} "/" " "`" 
+	cd "${_VCSDIR}"
+	for dir in ${_dirsplit}
+		do
+			if ${TEST} -d "${dir}"; then
+					execute "${TEST} -r \"${dir}\""
+					_canR=$?
+					execute "${TEST} -w \"${dir}\""
+					_canW=$?
+					if ${TEST} "${_canR}" -ne 0 || ${TEST} "${_canW}" -ne 0; then
+						${CHMOD} 700 "${dir}"
+						${CHOWN} pkgvcsconf:pkgvcsconf "${dir}"
+					fi
+					cd "${dir}"
+			fi
+		done
+	cd "${_restoreDir}"
+}
+
+if ${TEST} "${USER}" = "root"; then
+	drop=${TRUE}
+else
+	drop=${FALSE}
+fi
+
+case $ACTION in
+PREPARE)
+		cd "${_VCSDIR}"
+		case "${_VCS}" in
+		"cvs")
+			execute "cvs -d \"${_CVSROOT}\" rlog defaults 1>/dev/null"
+			repostatus=$?
+			if ${TEST} ${repostatus} -ne 0; then
+				if ${TEST} "${_REMOTE}" = "no"; then	
+					execute "cvs -d \"${_CVSROOT}\" init"
+					exitcode=$?
+				fi
+				for module in "automerged" "defaults" "user"
+					do
+						if ${TEST} -d "${_VCSDIR}/${module}" && ${TEST} -r "${_VCSDIR}/${module}"; then
+							if ${drop}; then
+								pkgchown "${_VCSDIR}/${module}"
+							fi
+							cd "${_VCSDIR}/${module}"
+							execute "cvs -d \"${_CVSROOT}\" import -m \"auto import preexisting ${module} files\" \"${module}\" auto start 1>/dev/null"
+						fi
+					
+					done
+				cd "${_VCSDIR}"
+				for module in "automerged" "defaults" "user"
+					do
+						execute "cvs -d \"${_CVSROOT}\" checkout -R \"${module}\""
+					done	
+			fi
+			cd "${_VCSDIR}"
+			for module in "automerged" "defaults" "user"
+				do
+					if ${drop}; then
+						pkgchown "${_VCSDIR}/${module}"
+					fi
+					execute "cvs -d \"${_CVSROOT}\" update -A -R \"${module}\""
+				done
+			;;
+		"subversion")
+			if ${drop}; then
+				pkgchown "${_VCSDIR}/defaults"
+			fi
+			execute "svn info \"${_VCSDIR}/defaults\" > /dev/null"
+			if ${TEST} $? -ne 0; then
+				if ${TEST} "$_REMOTE" = "no"; then
+					${RM} -fr "${_VCSDIR}/localsvn" 2>/dev/null
+					execute "svnadmin create \"${_VCSDIR}/localsvn\""
+					for subdir in "automerged" "defaults" "user"
+						do
+							execute "svn mkdir file:\/\/\"${_VCSDIR}/localsvn/${subdir}\" -m \"create ${subdir} conf dir\""
+							if ${drop}; then
+								pkgchown "${_VCSDIR}/${subdir}"
+							fi
+							execute "svn co file:\/\/\"${_VCSDIR}/localsvn/${subdir}\" \"${_VCSDIR}/${subdir}\""
+						done
+				else
+					#manually migrate to a remote repository, if needed
+					for subdir in "automerged" "defaults" "user"
+						do
+							if ${TEST} -d "${_VCSDIR}/${subdir}"; then
+								if ${drop}; then
+									pkgchown "${_VCSDIR}/${subdir}"
+								fi
+								execute "svn import -m \"initial import\" \"${_VCSDIR}/${subdir}\" \"${_REMOTE}/${subdir}\""
+							else
+							       execute "svn mkdir \"${_REMOTE}/${subdir}\""
+							fi
+							execute "svn co \"${_REMOTE}/${subdir}\" \"${_VCSDIR}/${subdir}\""
+						done
+				fi
+			fi	
+				execute "svn info \"${_VCSDIR}/defaults\" > /dev/null"
+				exitcode=$?
+				cd "${_VCSDIR}"
+				execute "svn update \"${_VCSDIR}/defaults\" \"${_VCSDIR}/user\" \"${_VCSDIR}/automerged\" > /dev/null"
+			;;
+		"git")
+			if ${drop} && ${TEST} -d "${_VCSDIR}/.git"; then
+				pkgchown "${_VCSDIR}/.git"
+			fi
+			execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" status > /dev/null"
+			gitstatus=$?
+			#this script won't clone a remote repository if a local git repo already exists in the VCSDIR.
+			#just setting $REMOTE won't suffice, manually move over data and clone the repo first or use git remote add
+			if ${TEST} ${gitstatus} -ne 0; then
+				execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" init"
+				exitcode=$?
+				if ${TEST} "${_REMOTE}" != "no"\
+				&& ${TEST} $exitcode -eq 0; then
+					execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" remote add origin \"${_REMOTE}\""
+					exitcode=$?
+				fi
+			else
+				if ${TEST} "$_REMOTE" != "no"; then
+					if execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" remote | ${GREP} -F \"origin\" >/dev/null"; then
+						execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" remote remove origin"
+						execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" remote add origin \"${_REMOTE}\""
+						#populate the remote repo if empty
+						#prefer losing remote status over local status
+						execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" push origin master"
+						exitcode=$?
+				 	else	
+						execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" remote add origin \"${_REMOTE}\""
+						exitcode=$?
+					fi
+				fi
+				 
+			fi
+			if ${TEST} $exitcode -eq 0 && ${TEST} ! "${_REMOTE}" = "no"; then
+				execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" pull origin master"
+			fi 
+			;;
+		"mercurial")
+			if ${drop} && ${TEST} -d "${_VCSDIR}/.hg"; then
+				pkgchown "${_VCSDIR}/.hg"
+			fi
+			execute "hg --repository \"${_VCSDIR}\" summary > /dev/null"
+			if ${TEST} $? -ne 0; then
+				execute "hg init \"${_VCSDIR}\""
+				exitcode=$?
+				if ${TEST} "${_REMOTE}" != "no"; then
+					execute "hg --repository \"${_VCSDIR}\" pull \"${_REMOTE}\""
+					execute "hg update -C -R \"${_VCSDIR}\""
+					execute "hg --repository \"${_VCSDIR}\" summary > /dev/null"
+					exitcode=$?
+				fi
+			else
+				if ${TEST} "$_REMOTE" != "no"; then
+					execute "hg clone . \"${_REMOTE}\""
+					execute "hg --repository \"${_VCSDIR}\" push \"${_REMOTE}\""
+					execute "hg --repository \"${_VCSDIR}\" pull \"${_REMOTE}\""
+				fi
+			fi
+			;;	
+		"rcs")
+			if ${drop}; then
+				pkgchown "${_VCSDIR}"
+			fi
+			;;
+		*)
+			${ECHO} 1>&2 "${_VCS}: unsupported versioning system"
+			exitcode=2
+			;;
+		esac
+	;;
+REGISTER)
+	if ${drop}; then
+		pkgchown "${CFILE}"
+	fi
+	case "${_VCS}" in
+	"rcs")
+		execute "${RCS} -U \"${CFILE}\" > /dev/null"
+		execute "${CI} -u \"${CFILE}\""
+		exitcode=$?
+		;;
+	"cvs")
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "//" '{print $2}'`"
+		cd "`${ECHO} "${CFILE}" | ${AWK} -F "//" '{print $1}'`"
+		OLDIFS="${IFS}"
+		IFS="/"
+		_cvsstatus=0
+		for curdir in ${_PATHSPLIT}
+			do
+				execute "cvs -d \"${_CVSROOT}\" add \"${curdir}\"" 
+				if ${TEST} $? -ne 0; then
+					exitcode=1
+				else
+					cd "$curdir" 2>/dev/null
+				fi
+			done
+		IFS="${OLDIFS}"
+		;;
+	"git")
+		cd "${_VCSDIR}"
+		execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" add -f \"${CFILE}\""
+		exitcode=$?
+		;;
+	"mercurial")
+		cd "${_VCSDIR}"
+		execute "hg --repository \"${_VCSDIR}\" add \"${CFILE}\""
+		exitcode=$?
+		;;
+	"subversion")
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "//" '{print $2}'`"
+		cd "`${ECHO} "${CFILE}" | ${AWK} -F "//" '{print $1}'`"
+		_svnstatus=0
+		OLDIFS="${IFS}"
+		IFS="/"
+		for curdir in ${_PATHSPLIT}
+		do
+			execute	"svn add --force --depth=empty \"${curdir}\""
+			if ${TEST} $? -ne 0; then
+				exitcode=1
+			else
+				cd "${curdir}" 2>/dev/null
+			fi
+		done
+		IFS="${OLDIFS}"
+		;;
+	*)
+		${ECHO} 1>&2 "${_VCS}: unsupported versioning system. I shouldn't be there!"
+		exitcode=2
+		;;
+	esac
+	;;
+COMMIT)
+	cd "${_VCSDIR}"
+	case "${_VCS}" in
+	"cvs")
+		execute "cvs -Q -d \"${_CVSROOT}\" commit -R -m \"pkgsrc: $2\""
+		if ${TEST} $? -eq 0; then
+			${ECHO} "Conf commit: pkgsrc: $2"
+		else
+			${ECHO} 1>&2 "Failed to commit conf: $2"
+			exitcode=3
+		fi
+		;;
+	"git")
+		execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" commit -m \"pkgsrc: $2\""
+		gitcommitstatus=$?
+		if ${TEST} ${gitcommitstatus} -eq 0; then
+			${ECHO} "Conf commit: pkgsrc: $2"
+		else
+			${ECHO} 1>&2 "Failed to commit conf: $2"
+			exitcode=3
+		fi
+		if ${TEST} "$_REMOTE" != "no"; then
+			execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" push origin master"
+			#even if it fails, a local copy exists
+			if ${TEST} $? -ne 0; then
+				${ECHO} 1>&2 "git: failed to push changes to the remote repository ${_REMOTE}"
+			fi
+		fi
+		;;
+	"mercurial")
+		execute "hg --repository \"${_VCSDIR}\" commit -m \"pkgsrc: $2\" --user pkgsrc > /dev/null"
+		hgcommitstatus=$?
+		if ${TEST} ${hgcommitstatus} -eq 0; then
+			${ECHO} "Conf commit: pkgsrc: $2"
+		else
+			${ECHO} 1>&2 "Failed to commit conf: $2"
+			exitcode=3
+		fi
+		if ${TEST} "${_REMOTE}" != "no"; then
+			execute "hg --repository \"${_VCSDIR}\" push \"${_REMOTE}\""
+			if ${TEST} $? -ne 0; then
+				${ECHO} 1>&2 "hg: failed to push changes to the remote repository ${_REMOTE}"
+			fi
+		fi
+		;;
+	"subversion")
+		_svnexitstatus=1
+		for dir in "defaults" "user" "automerged"
+			do
+				if ${drop} && ! execute "${TEST} -r \"${_VCSDIR}/${dir}\""; then
+					pkgchown "${_VCSDIR}/${dir}"
+				fi
+
+				cd "${_VCSDIR}/${dir}"
+				execute "svn commit -m \"pkgsrc: $2\" 2>/dev/null"
+				if ${TEST}  $? -eq 0; then
+					_svnexitstatus=0
+					#at least one worked
+				fi
+			done
+		if ${TEST} ${_svnexitstatus} -eq 0; then
+			${ECHO} "Conf commit: pkgsrc: $2"
+		else
+			${ECHO} 1>&2 "Failed to commit conf: $2"
+			exitcode=3
+		fi
+		;;
+	*)
+		;;
+	esac
+	;;
+CHECKOUT)
+	if ${drop}; then
+		pkgchown "${CFILE}"
+	fi
+	case "${_VCS}" in
+	"rcs")
+		execute "${CO} -f \"${CFILE}\""
+		exitcode=$?
+		;;
+	"cvs")
+		cd "${_VCSDIR}"
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "${_VCSDIR}" '{print $2}' | ${SED} 's@//@/@' | ${SED} 's@/@@'`"
+		execute "cvs -d \"${_CVSROOT}\" co \"${_PATHSPLIT}\""
+		;;
+	"git")
+		if ${drop}; then
+			pkgchown "${_VCSDIR}/.git"
+		fi
+		cd "${_VCSDIR}"
+		execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" checkout -- \"${CFILE}\""
+		;;
+	"subversion")
+		if ${drop}; then
+			pkgchown "${_VCSDIR}/localsvn"
+		fi
+		cd "${_VCSDIR}"
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "${_VCSDIR}" '{print $2}'`"
+		if ${TEST} "${_REMOTE}" = "no"; then
+			execute "svn export --force file:\/\/\"${_VCSDIR}/localsvn/${_PATHSPLIT}\" \"${CFILE}\""
+		else
+			execute "svn export --force \"${_REMOTE}/${_PATHSPLIT}\" \"${CFILE}\""
+		fi
+		;;
+	"mercurial")
+		if ${drop}; then
+			pkgchown "${_VCSDIR}/.hg"
+		fi
+		cd "${_VCSDIR}"
+		execute "hg --repository \"${_VCSDIR}\" cat -r tip \"${CFILE}\" > \"${CFILE}\""
+		;;
+	*)
+		${ECHO} 1>&2 "${_VCS}: unsupported versioning system"
+		exitcode=2
+		;;
+	esac
+	;;
+#checkout the initial revision of the file, used in the 3-way merge
+CHECKOUT-FIRST)
+	if ${drop}; then
+		pkgchown "${CFILE}"
+	fi
+	case ${_VCS} in
+	"rcs")
+		execute "${CO} -f1.1 \"${CFILE}\""
+		exitcode=$?
+		;;
+	"cvs")
+		cd "${_VCSDIR}"
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "${_VCSDIR}" '{print $2}' | ${SED} 's@//@/@' | ${SED} 's@/@@'`"
+		execute "cvs -d \"${_CVSROOT}\" co -r1.1 \"${_PATHSPLIT}\""
+		;;
+	"git")
+		if ${drop}; then
+			pkgchown "${_VCSDIR}/.git"
+		fi
+		cd "${_VCSDIR}"
+ 		_firstRev="`execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" rev-list HEAD \"${CFILE}\"" | ${TAIL} -n 1 `" 
+		execute "git --git-dir=\"${_VCSDIR}/.git\" --work-tree=\"${_VCSDIR}\" checkout ${_firstRev} -- \"${CFILE}\""
+		;;
+	"mercurial")
+		if ${drop}; then
+			pkgchown "${_VCSDIR}/.hg"
+		fi
+		cd "${_VCSDIR}"
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "${_VCSDIR}" '{print $2}' | ${CUT} -c 2- `"
+		execute "hg --repository \"${_VCSDIR}\" cat -r \"first(file(\"${_PATHSPLIT}\"),1)\" \"${CFILE}\" > \"${CFILE}\""
+		;;
+	"subversion")
+		cd "${_VCSDIR}"
+		_PATHSPLIT="`${ECHO} "${CFILE}" | ${AWK} -F "${_VCSDIR}" '{print $2}'`"
+		if ${TEST} "${_REMOTE}" = "no"; then
+			_firstRev="`execute "svn log --xml -r 1:HEAD --limit 1 file:\/\/\"${_VCSDIR}/localsvn/${_PATHSPLIT}\"" | ${AWK} -F'\042'  '/revision=/ {print $2}'`"
+			execute "svn export --force --revision ${_firstRev} file:\/\/\"${_VCSDIR}/localsvn/${_PATHSPLIT}\" \"${CFILE}\""
+		else
+			_firstRev="`execute "svn log --xml -r 1:HEAD --limit 1 \"${_REMOTE}/${_PATHSPLIT}\"" | ${AWK} -F '\042' '/revision=/ {print $2}'`"
+			execute "svn export --force --revision ${_firstRev} \"${_REMOTE}/${_PATHSPLIT}\" \"${CFILE}\""
+		fi
+		;;
+	*)
+		${ECHO} 1>&2 "${_VCS}: unsupported versioning system"
+		exitcode=2
+		;;
+	esac
+	;;
+MERGE)
+	${MERGE} "${FIRSTFILE}.automerge" "${FIRSTFILE}" "${CFILE}"
+	exitcode=$?
+	;;
+PULL)
+	#change in spec: branch name contains package name
+	#category_baseName_pkgVersion_rangeStart_rangeEnd_systemRole_optionalHostname
+	#remember to set the env variables $ROLE and $HOSTNAME
+	${ECHO} "Trying to deploy configuration from ${_REMOTE} via ${_VCS}"
+	exitcode=0
+	branchFound=no
+	compatibleBranches=""
+	branchName=""
+	category="`${ECHO} "${2}" | ${AWK} -F "/" '{print $1}'`"
+	name="`${ECHO} "${2}" | ${AWK} -F "/" '{print $2}'`"
+	exactVersion=${3}
+	shortVersion="`${ECHO} ${4} | ${AWK} -F "${name}" '{print $2}' | ${TR} -dc '[0-9].'`"
+	branchesOutput=""
+	_RANDINT="`${CAT} /dev/urandom | ${TR} -dc '[0-9]' | ${FOLD} -w 4 | ${HEAD} -n 1  `"
+	_TEMPDIR="/tmp/pkgsrcdeploy-${RANDOM}"
+	while ${TEST} -d "${_TEMPDIR}"; do
+		#this should never happen!
+		${RM} -fr "${_TEMPDIR}"
+		_TEMPDIR="/tmp/pkgsrcdeploy-${RANDOM}"
+	done
+	execute "${MKDIR} -p \"${_TEMPDIR}\""
+	execute "${CHMOD} 0700 \"${_TEMPDIR}\""
+	cd "${_TEMPDIR}"
+	case "${_VCS}" in
+		"git")
+			_output="`execute "git ls-remote --refs \"${_REMOTE}\" \"${category}\"_\"${name}\"\"_*\""`"
+			exitcode=$?
+			_output="`${ECHO} "${_output}" | ${AWK} -F "refs/heads/" '{print $2}'`"
+			;;
+		"subversion")
+			if ${ECHO} "${_REMOTE}" | ${GREP} -Fv "/branches" > /dev/null; then
+				_REMOTE="${_REMOTE}/branches/"
+			fi
+			_output="`execute "svn ls \"${_REMOTE}\""`"
+			exitcode=$?
+			_output="`${ECHO} "${_output}" | ${SED} 's@/@@g' | ${GREP} "${category}_${name}_"`"
+			#it would be more accurate to iterate over single branch names, split them and check for category_name
+			;;
+		#Mercurial, CVS require to checkout the remote repository locally before listing branches...
+		"mercurial")
+			execute "hg clone \"${_REMOTE}\" \"${_TEMPDIR}/work\""
+			exitcode=$?
+			if ${TEST} ${exitcode} -eq 0; then
+				_output="`execute "hg branches -R \"${_TEMPDIR}/work\"" | ${AWK} -v search="${category}_${name}_" '$0 ~search {print $1}'`"
+				exitcode=$?
+			fi	
+			;;
+		"rcs"|*)
+			${ECHO} 1>&2 "Configuration deploy: ${_VCS} is not supported"
+			if ${TEST} -z "${VCS}"; then
+				${ECHO} 1>&2 "Set VCS to use configuration deploy in pkgsrc! Supported solutions are git, svn and hg"
+			fi
+			exitcode=2
+			;;
+	esac
+	if ${TEST} ${exitcode} -eq 0 && ${TEST} -n "${_output}"; then 
+		#sanitize the output first
+		for branch in ${_output}
+			do
+				if ${TEST} "`${ECHO} "${branch}" | ${AWK} '{while (match($0, /_/)) {++c; $0=substr($0, RSTART+RLENGTH)}} END {print c}'`" -gt 4 && \
+					${TEST} "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $1}'`" = "${category}" && \
+
+					${TEST} "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $2}'`" = "${name}" && \
+					${TEST} -n "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $3}' | ${TR} -dc '[0-9]'`" && \
+					${TEST} -n "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $4}' | ${TR} -dc '[0-9]'`" && \
+					${TEST} -n "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $5}' | ${TR} -dc '[0-9]'`"; then
+						compatibleBranches=""${branch}" "${compatibleBranches}""
+				else
+					${ECHO} "Warning: ignoring branch with invalid naming: "${branch}""
+					${ECHO} "category_packageName_packageVersion_compatRangeStart_compatRangeEnd_systemRole_optionalHostname"
+				fi
+			done
+		_output="${compatibleBranches}"
+		compatibleBranches=""
+		for branch in ${_output}
+			do
+				_rangeStart="`${ECHO} "${branch}" | ${AWK} -F "_" '{print $4}'`"
+				_rangeEnd="`${ECHO} "${branch}" | ${AWK} -F "_" '{print $5}'`"
+				_branchRole="`${ECHO} "${branch}" | ${AWK} -F "_" '{print $6}'`"
+				if ${ECHO} "${branch}" | ${GREP} -F '.' > /dev/null; then
+					if ${ECHO} "${shortVersion}" | ${GREP} -Fv '.' > /dev/null; then
+						${ECHO} ""${branch}": branch naming incompatible with package version naming!"
+						continue
+					fi
+					#compare versions
+					_rangeStartLTshortVersion=true
+					_rangeEndGTshortVersion=false
+					_groupCounter=1
+					for intGroup in `${ECHO} "${shortVersion}" | ${SED} 's/\./ /g'`
+						do
+							_rangeStartGroup="`${ECHO} "${_rangeStart}" \
+							| ${AWK} -F "." -v fieldnum="${_groupCounter}" '{print $fieldnum}'`"
+							_rangeEndGroup="`${ECHO} "${_rangeEnd}" \
+							| ${AWK} -F "." -v fieldnum="$_groupCounter" '{print $fieldnum}'`"
+							if ${TEST} -n "${_rangeStartGroup}" && ${TEST} "${_rangeStartGroup}" -gt "${intGroup}"; then
+								_rangeStartLTshortVersion=false
+								break
+							fi
+							if ${TEST} -n "${_rangeEndGroup}" \
+							&& ${TEST} ! "${_rangeEndGTshortVersion}" = "true" \
+							&& ${TEST} "${_rangeEndGroup}" -gt "${intGroup}"; then
+								_rangeEndGTshortVersion=true
+							else
+								break
+							fi
+							_groupCounter="`${EXPR} "${_groupCounter}" + 1 `"
+						done
+					if ${TEST} "${_rangeStartLTshortVersion}" = "true" \
+					&& ${TEST} "${_rangeEndGTshortVersion}" = "true"; then
+						if ${TEST} "${_branchRole}" = "any" || ${TEST} "${_branchRole}" = "ANY"\
+						|| ${TEST} "${_branchRole}" = "${_ROLE}"\
+						|| ${TEST} "${_ROLE}" = "any"; then 
+							compatibleBranches="${branch} ${compatibleBranches}"
+						fi
+					fi
+				else
+					if ${ECHO} "${shortVersion}" | ${GREP} -F '.' > /dev/null; then
+						${ECHO} 1>&2 ""${branch}": branch naming incompatible with package version naming!"
+						continue
+					fi
+					#numerically compare versions
+					if ( ${TEST} "${_rangeStart}" -lt "${shortVersion}"\
+					|| ${TEST} "${_rangeStart}" -eq "${shortVersion}" ) \
+					&& ( ${TEST} "${_rangeEnd}" -gt "${shortVersion}" \
+					|| ${TEST} "${_rangeEnd}" -eq "${shortVersion}" ) ; then
+
+						if ${TEST} "${_branchRole}" = "any" \
+						|| ${TEST} "${_branchRole}" = "ANY" \
+						|| ${TEST} "${_branchRole}" = "$_ROLE"\
+						|| ${TEST} "${_ROLE}" = "any"; then
+							compatibleBranches=""${compatibleBranches}" "${branch}""
+						fi
+					fi
+				fi
+			done
+		if ${TEST} -z "${compatibleBranches}"; then
+			${ECHO} 1>&2 "Error: no compatible branches found for "${category}"/"${name}"-"${shortVersion}"! Using package defaults"
+			${RM} -fr "${_TEMPDIR}"
+			exit 6
+		fi
+		#check for hostnames, best effort
+		if ${TEST} -n "${HOSTNAME}"\
+		&& ${TEST} ! "${HOSTNAME}" = "localhost"; then
+			bestHostnameMatchChars=1
+			bestHostnameMatch=""
+			compatibleHostnames=""
+			_charCounter=1
+			for branch in ${compatibleBranches}
+				do
+					if ${TEST} "`${ECHO} "${branch}" | ${AWK} '{while (match($0, /_/)) {++c; $0=substr($0, RSTART+RLENGTH)}} END {print c}'`" -gt 5; then
+						#optional hostname exists in branch name
+						_branchHostname="`${ECHO} "${branch}" | ${AWK} -F "_" '{for(i=7;i<=NF;++i)print $i}'`"
+						_charCounter=1
+						for character in `${ECHO} "${_branchHostname}" | ${FOLD} -w 1 `
+							do
+								if ${TEST} ! "${character}" = "`${ECHO} "${HOSTNAME}" | ${CUT} -c "${_charCounter}"`"; then
+									break
+								else
+									_charCounter="`${EXPR} "${_charCounter}" + 1 `"
+								fi
+							done
+					fi
+					if ${TEST} "${_charCounter}" -gt "${bestHostnameMatchChars}"; then
+						bestHostnameMatchChars="${_charCounter}"
+						bestHostnameMatch="${_branchHostname}"
+					fi
+				done
+			compatibleBranchesWithHostnames=""
+			if ${TEST} -n "${bestHostnameMatch}"; then
+				for branch in ${compatibleBranches}
+					do
+						if ${TEST} "`${ECHO} "${branch}"\
+						| ${AWK} '{while (match($0, /set/)) {++c; $0=substr($0, RSTART+RLENGTH)}} END {print c}'`" -gt 5 \
+						&& ${TEST} \
+						"`${ECHO} "${branch}" | ${AWK} -F "_" '{for(i=7;i<=NF;++i)print $i}'`" = "${bestHostnameMatch}"; then
+							compatibleBranchesWithHostnames=""${branch}" "${compatibleBranchesWithHostnames}""
+						fi
+					done
+				compatibleBranches="${compatibleBranchesWithHostnames}"
+				if ${TEST} "`${ECHO} "${compatibleBranches}" | ${AWK} -v pattern="${bestHostnameMatch}" '{while (match($0, pattern)) {++c; $0=substr($0, RSTART+RLENGTH)}} END {print c}'`" -eq 1; then
+					branchFound=yes
+					branchName="${compatibleBranches}"
+				fi
+					
+			fi
+		fi
+		if ${TEST} ! "${branchFound}" = "yes"; then
+			#then check for an exact match! first with exactVersion, else with shortVersion
+			for branch in ${compatibleBranches}
+				do
+					
+					if ${TEST} "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $3}'`" = "${exactVersion}"; then
+						branchFound=yes
+						branchName="${branch}"
+						break
+					elif ${TEST} "`${ECHO} "${branch}" | ${AWK} -F "_" '{print $3}'`" = "${shortVersion}"; then
+						branchFound=yes
+						branchName="${branch}"
+						break
+					fi
+				done
+			#if the branch is still not found, select the best compatible branch in range.
+			branch="`${ECHO} "${compatibleBranches}" | ${AWK} -F " " '{print $1}'`"
+			branchVersion="`${ECHO} "${branch}" | ${AWK} -F "_" '{print $3}'| ${TR} -dc '[0-9].' `"
+			if ${ECHO} "${shortVersion}" | ${GREP} -Fv '.' > /dev/null; then
+				#compare integers
+				versionDistance="`${EXPR} "${shortVersion}" - "${branchVersion}"`"
+				versionDistance="${versionDistance#-}"
+				minimumDistance="${versionDistance}"
+				bestBranch="${branch}"
+				for branch in ${compatibleBranches}
+					do
+						branchVersion="`${ECHO} "${branch}" | ${AWK} -F "_" '{print $3}' | ${TR} -dc '[0-9].' `"
+						versionDistance="`${EXPR} "${shortVersion}" - "${branchVersion}"`"
+						versionDistance=${versionDistance#-}
+						if ${TEST} "${versionDistance}" -lt "${minimumDistance}"; then
+							minimumDistance=${versionDistance}
+							bestBranch="${branch}"
+						fi
+							
+					done
+				branchFound=yes
+				branchName="${bestBranch}"
+			else
+				#dots in version numbers!
+				bestBranch="${branch}"
+				_branches="${compatibleBranches}"
+				groupCounter=1
+				for intGroup in `${ECHO} "${shortVersion}" | ${AWK} -F "." '{print $0}'`
+					do
+						bestBranchGroup="${_branches}"
+						_pkgVer="`${ECHO} "${shortVersion}" | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}'`"
+						_braVer="`${ECHO} "${_branches}" | ${AWK} -F " " '{print $1}' | ${AWK} -F "_" '{print $3}' | ${TR} -dc '[0-9].' | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}'`"
+						minimumDistance="`${EXPR} "${_pkgVer}" - "${_braVer}"`" 
+						miniumDistance="${minimumDistance#-}"
+						branchCounter=1
+						for branch in ${_branches}
+							do
+								_pkgVer="`${ECHO} "${shortVersion}" | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}'`"
+								_braVer="`${ECHO} "${_branches}" | ${AWK} -F " " -v fieldnum="${branchCounter}" '{print $fieldnum}' | ${AWK} -F "_" '{print $3}' | ${TR} -dc '[0-9].' | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}'`"
+								groupDistance="`${EXPR} "${_pkgVer}" - "${_braVer}"`"
+								groupDistance="${groupDistance#-}"
+								if ${TEST} "${groupDistance}" -lt "${minimumDistance}"; then
+									minimumDistance="${groupDistance}"
+									bestBranchGroup="${branch}"
+								elif ${TEST} "${groupDistance}" -eq "${minimumDistance}"; then
+									bestBranchGroup=""${bestBranchGroup}" "${branch}""
+								fi
+								branchCounter="`${EXPR} "${branchCounter}" + 1 `"
+							done
+						_branches="${bestBranchGroup}"
+						groupCounter="` ${EXPR} "${groupCounter}" + 1 `"
+					done
+					if ${TEST} -n "${_branches}"; then
+					#I should always have one and only one branch in _branches at the end...
+						bestBranch="`${ECHO} "${_branches}" | ${AWK} -F " " '{print $1}'`"
+						branchFound=yes
+					fi
+				branchName="${bestBranch}"
+			fi		
+		fi
+	fi
+	if ${TEST} "${branchFound}" = "yes" && ${TEST} "${exitcode}" -eq 0; then
+		branchName="`${ECHO} "${branchName}" | ${SED} 's/ //g'`"
+		${ECHO} "About to use remote branch "${branchName}""
+		case "${_VCS}" in
+			"git")
+				execute "git --git-dir=\"${_TEMPDIR}/.git\" --work-tree=\"${_TEMPDIR}/work\" clone -b \"${branchName}\" \"${_REMOTE}\""
+				exitcode=$?
+				;;
+			"subversion")
+				execute "svn co \"${_REMOTE}/${branchName}\" \"${_TEMPDIR}/work\""
+				exitcode=$?
+				if ${TEST} "${exitcode}" -eq 0; then
+					${RM} -fr "${_TEMPDIR}/work/.svn"
+					exitcode=$?
+				fi
+				;;
+			"mercurial")
+					execute "hg -R \"${_TEMPDIR}/work\" update -C \"${branchName}\""
+					exitcode=$?
+					if ${TEST} "${exitcode}" -eq 0; then
+						${RM} -fr "${_TEMPDIR}/work/.hg"
+						exitcode=$?
+					fi
+				;;
+		esac
+		set -x
+		if ${TEST} "${exitcode}" -eq 0; then
+			${FIND} "${_TEMPDIR}/work" -type f -print \
+			| while read line
+				do
+					relativePath="`${ECHO} "${line}" \
+					| ${AWK} -F "${_TEMPDIR}/work" '{print $2}'`"
+					${MKDIR} -p "${relativePath}" 2>/dev/null
+					${RMDIR} "${relativePath}" 2>/dev/null
+					${CP} -v -f "${line}" "${relativePath}"
+					cpexitcode=$?
+					${CHMOD} 0600 "${relativePath}"
+					if ${TEST} "${cpexitcode}" -ne 0; then
+						exitcode=5;
+					fi
+				done  
+		fi
+	fi
+	${RM} -fr "${_TEMPDIR}"
+	if ${TEST} ${exitcode} -ne 0; then
+		${ECHO} 1>&2 "Error deploying configuration for "${category}"/"${name}": using package defaults"
+	fi
+	;;
+*)
+	${ECHO} 1>&2 "Usage: ./+VERSIONING REGISTER|CHECKOUT|CHECKOUT-FIRST [examplefile]"
+	${ECHO} 1>&2 "Usage: ./+VERSIONING MERGE [examplefile] [firstrevision]"
+	${ECHO} 1>&2 "Usage: ./+VERSIONING COMMIT [message]"
+	${ECHO} 1>&2 "Usage: ./+VERSIONING PREPARE"
+	${ECHO} 1>&2 "Usage: ./+VERSIONING PULL PKGPATH PKGVERSION PKGNAME"
+	exitcode=3
+	;;
+esac
+exit $exitcode
+
+EOF
+	${SED} -n "/^\# VERSIONING: /p" "${SELF}" >> ./+VERSIONING
+	${CHMOD} +x ./+VERSIONING
+	;;
+esac
diff --git a/mk/tools/defaults.mk b/mk/tools/defaults.mk
index 161e0a3ce197..52fefa0c586d 100644
--- a/mk/tools/defaults.mk
+++ b/mk/tools/defaults.mk
@@ -62,8 +62,10 @@ _TOOLS_VARNAME.cat=		CAT
 _TOOLS_VARNAME.chgrp=		CHGRP
 _TOOLS_VARNAME.chmod=		CHMOD
 _TOOLS_VARNAME.chown=		CHOWN
+_TOOLS_VARNAME.ci=		CI
 _TOOLS_VARNAME.cmake=		CMAKE
 _TOOLS_VARNAME.cmp=		CMP
+_TOOLS_VARNAME.co=		CO
 _TOOLS_VARNAME.cp=		CP
 _TOOLS_VARNAME.cpack=		CPACK
 _TOOLS_VARNAME.csh=		CSH
@@ -80,6 +82,7 @@ _TOOLS_VARNAME.false=		FALSE
 _TOOLS_VARNAME.fgrep=		FGREP
 _TOOLS_VARNAME.file=		FILE_CMD
 _TOOLS_VARNAME.find=		FIND
+_TOOLS_VARNAME.fold=		FOLD
 _TOOLS_VARNAME.gawk=		AWK
 _TOOLS_VARNAME.gem=		GEM
 _TOOLS_VARNAME.gm4=		M4
@@ -106,6 +109,7 @@ _TOOLS_VARNAME.lzcat=		LZCAT
 _TOOLS_VARNAME.m4=		M4
 _TOOLS_VARNAME.mail=		MAIL_CMD
 _TOOLS_VARNAME.makeinfo=	MAKEINFO
+_TOOLS_VARNAME.merge=		MERGE
 _TOOLS_VARNAME.mkdir=		MKDIR
 _TOOLS_VARNAME.mktemp=		MKTEMP
 _TOOLS_VARNAME.mtree=		MTREE
@@ -120,6 +124,7 @@ _TOOLS_VARNAME.perl=		PERL5
 _TOOLS_VARNAME.pod2man=		POD2MAN
 _TOOLS_VARNAME.printf=		PRINTF
 _TOOLS_VARNAME.pwd=		PWD_CMD
+_TOOLS_VARNAME.rcs=		RCS
 _TOOLS_VARNAME.rm=		RM
 _TOOLS_VARNAME.rmdir=		RMDIR
 _TOOLS_VARNAME.rpm2pkg=		RPM2PKG
@@ -169,6 +174,7 @@ _TOOLS_VARNAME_GNU.egrep=	EGREP ac_cv_path_EGREP
 _TOOLS_VARNAME_GNU.env=		SETENV ENV_PROG ac_cv_path_ENV
 _TOOLS_VARNAME_GNU.false=	FALSE ac_cv_path_FALSE
 _TOOLS_VARNAME_GNU.find=	FIND
+_TOOLS_VARNAME_GNU.fold=	FOLD
 _TOOLS_VARNAME_GNU.gawk=	AWK
 _TOOLS_VARNAME_GNU.gm4=		M4
 _TOOLS_VARNAME_GNU.grep=	GREP ac_cv_path_GREP
diff --git a/mk/tools/replace.mk b/mk/tools/replace.mk
index dbcdd6e12dea..4cfd313d0dff 100644
--- a/mk/tools/replace.mk
+++ b/mk/tools/replace.mk
@@ -492,15 +492,19 @@ TOOLS_ARGS.gzip=		-nf ${GZIP}
 .  endif
 .endif
 
-.if !defined(TOOLS_IGNORE.ident) && !empty(_USE_TOOLS:Mident)
-.  if !empty(PKGPATH:Mdevel/rcs)
-MAKEFLAGS+=			TOOLS_IGNORE.ident=
-.  elif !empty(_TOOLS_USE_PKGSRC.ident:M[yY][eE][sS])
-TOOLS_DEPENDS.ident?=		rcs-[0-9]*:../../devel/rcs
-TOOLS_CREATE+=			ident
-TOOLS_PATH.ident=		${LOCALBASE}/bin/ident
-.  endif
-.endif
+_TOOLS.rcsutils=	ident merge ci co rcs
+.for _t_ in ${_TOOLS.rcsutils}
+.	if !defined(TOOLS_IGNORE.${_t_}) && !empty(_USE_TOOLS:M${_t_})
+.		if !empty(PKGPATH:Mdevel/rcs)
+MAKEFLAGS+=             TOOLS_IGNORE.${_t_}=
+.	elif !empty(_TOOLS_USE_PKGSRC.${_t_}:M[yY][eE][sS])
+TOOLS_DEPENDS.${_t_}?=		rcs-[0-9]*:../../devel/rcs
+TOOLS_CREATE+=			${_t_}
+TOOLS_PATH.${_t_}=		${LOCALBASE}/bin/${_t_}
+.		endif
+.	endif
+.endfor
+
 
 .if !defined(TOOLS_IGNORE.install-info) && !empty(_USE_TOOLS:Minstall-info)
 .  if !empty(PKGPATH:Mpkgtools/pkg_install-info)
@@ -889,7 +893,7 @@ _TOOLS_VERSION.perl!=							\
 	eval $$(${TOOLS_PLATFORM.perl} -V:version) && echo $$version
 _TOOLS_PKG.perl=		perl-${_TOOLS_VERSION.perl}
 .  if !empty(_TOOLS_USE_PKGSRC.perl:M[nN][oO])
-.    for _dep_ in perl>=${PERL5_REQD} 
+.    for _dep_ in perl>=${PERL5_REQD}
 .      if !empty(_TOOLS_USE_PKGSRC.perl:M[nN][oO])
 _TOOLS_USE_PKGSRC.perl!=						\
 	if ${PKG_ADMIN} pmatch ${_dep_:Q} ${_TOOLS_PKG.perl:Q}; then \
@@ -947,7 +951,8 @@ TOOLS_PATH.${_t_}=		${LOCALBASE}/bin/${_t_}
 _TOOLS.coreutils=	basename cat chgrp chmod chown cp cut date	\
 		dirname echo env expr false head hostname id install	\
 		ln ls mkdir mv nice numfmt printf pwd readlink realpath \
-		rm rmdir sleep sort tail tee test touch tr true tsort wc
+		rm rmdir sleep sort tail tee test touch tr true tsort wc\
+		fold
 
 .for _t_ in ${_TOOLS.coreutils}
 .  if !defined(TOOLS_IGNORE.${_t_}) && !empty(_USE_TOOLS:M${_t_})
diff --git a/mk/tools/tools.AIX.mk b/mk/tools/tools.AIX.mk
index d358456f02aa..22d391c498a1 100644
--- a/mk/tools/tools.AIX.mk
+++ b/mk/tools/tools.AIX.mk
@@ -15,7 +15,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/bin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -30,6 +36,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/grep -F
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold=?		/usr/bin/fold
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.head?=		/usr/bin/head
 TOOLS_PLATFORM.hostname?=	/bin/hostname
@@ -39,12 +46,18 @@ TOOLS_PLATFORM.ln?=		/bin/ln
 TOOLS_PLATFORM.ls?=		/bin/ls
 TOOLS_PLATFORM.m4?=		/usr/bin/m4
 TOOLS_PLATFORM.mail?=		/usr/bin/mailx
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mv?=		/bin/mv
 TOOLS_PLATFORM.nice?=		/usr/bin/nice
 TOOLS_PLATFORM.nroff?=		/usr/bin/nroff
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
 TOOLS_PLATFORM.sed?=		/usr/bin/sed
diff --git a/mk/tools/tools.BSDOS.mk b/mk/tools/tools.BSDOS.mk
index 8bad48758ea2..c60aa4432bbb 100644
--- a/mk/tools/tools.BSDOS.mk
+++ b/mk/tools/tools.BSDOS.mk
@@ -9,7 +9,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -24,6 +30,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.grep?=		/bin/grep
 .if exists(/usr/bin/gunzip)
 TOOLS_PLATFORM.gunzip?=		/usr/bin/gunzip -f
@@ -49,6 +56,9 @@ TOOLS_PLATFORM.ln?=		/bin/ln
 TOOLS_PLATFORM.ls?=		/bin/ls
 TOOLS_PLATFORM.m4?=		/usr/bin/m4
 TOOLS_PLATFORM.mail?=		/usr/bin/mail
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mv?=		/bin/mv
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -63,6 +73,9 @@ TOOLS_PLATFORM.patch?=		/usr/contrib/bin/patch
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 .endif
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
 TOOLS_PLATFORM.sed?=		/usr/bin/sed
diff --git a/mk/tools/tools.Bitrig.mk b/mk/tools/tools.Bitrig.mk
index 3ea39e7c2967..943f52e02a98 100644
--- a/mk/tools/tools.Bitrig.mk
+++ b/mk/tools/tools.Bitrig.mk
@@ -10,7 +10,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -26,6 +32,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/flex
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.gunzip?=		/usr/bin/gunzip -f
@@ -45,6 +52,9 @@ TOOLS_PLATFORM.ls?=		/bin/ls
 TOOLS_PLATFORM.m4?=		/usr/bin/m4
 TOOLS_PLATFORM.mail?=		/usr/bin/mail
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -58,6 +68,9 @@ TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
diff --git a/mk/tools/tools.Cygwin.mk b/mk/tools/tools.Cygwin.mk
index 5991699eb8c0..96dfca9f6746 100644
--- a/mk/tools/tools.Cygwin.mk
+++ b/mk/tools/tools.Cygwin.mk
@@ -25,7 +25,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/bin/chown
+.if exists(/bin/ci)
+TOOLS_PLATFORM.ci?=		/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/bin/cmp
+.if exists(/bin/co)
+TOOLS_PLATFORM.co?=		/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 .if exists(/bin/tcsh)
 TOOLS_PLATFORM.csh?=		/bin/tcsh
@@ -46,6 +52,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.file?=		/bin/file
 TOOLS_PLATFORM.find?=		/bin/find
+TOOLS_PLATFORM.fold?=		/bin/fold
 .if exists(/bin/flex)
 TOOLS_PLATFORM.flex?=		/bin/flex
 .endif
@@ -86,6 +93,9 @@ TOOLS_PLATFORM.m4?=		/bin/m4
 TOOLS_PLATFORM.gmake?=		/bin/make
 .endif
 TOOLS_PLATFORM.makeinfo?=	/bin/makeinfo
+.if exists(/bin/merge)
+TOOLS_PLATFORM.merge?=		/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/bin/mktemp
 .if exists(/bin/msgconv)
@@ -119,6 +129,9 @@ TOOLS_PLATFORM.pkg-config?=	/bin/pkg-config
 .endif
 TOOLS_PLATFORM.printf?=		/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/bin/rcs
+.endif
 TOOLS_PLATFORM.readlink?=	/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
diff --git a/mk/tools/tools.Darwin.mk b/mk/tools/tools.Darwin.mk
index 2464ea7b2bf1..e283852ca236 100644
--- a/mk/tools/tools.Darwin.mk
+++ b/mk/tools/tools.Darwin.mk
@@ -25,7 +25,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/tcsh
 .if exists(/usr/bin/curl)
@@ -43,6 +49,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/flex
 TOOLS_PLATFORM.ftp?=		/usr/bin/ftp
 .if empty(MACHINE_PLATFORM:MDarwin-[0-8].*-*)
@@ -78,6 +85,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -90,6 +100,9 @@ TOOLS_PLATFORM.pax?=		/bin/pax
 #TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
diff --git a/mk/tools/tools.DragonFly.mk b/mk/tools/tools.DragonFly.mk
index d0533aff27cf..e67db338c9b9 100644
--- a/mk/tools/tools.DragonFly.mk
+++ b/mk/tools/tools.DragonFly.mk
@@ -15,7 +15,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -59,6 +65,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -70,6 +79,9 @@ TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
diff --git a/mk/tools/tools.FreeBSD.mk b/mk/tools/tools.FreeBSD.mk
index 7841315e47d8..b889c310e13c 100644
--- a/mk/tools/tools.FreeBSD.mk
+++ b/mk/tools/tools.FreeBSD.mk
@@ -15,6 +15,12 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=             /usr/bin/ci
+.endif
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM=			/usr/bin/co
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
@@ -32,6 +38,7 @@ TOOLS_PLATFORM.fetch?=		/usr/bin/fetch
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/lex
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.groff?=		/usr/bin/groff
@@ -55,6 +62,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -67,6 +77,9 @@ TOOLS_PLATFORM.openssl?=	/usr/bin/openssl
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
diff --git a/mk/tools/tools.FreeMiNT.mk b/mk/tools/tools.FreeMiNT.mk
index 2a93943ae380..34b9f9a2b96b 100644
--- a/mk/tools/tools.FreeMiNT.mk
+++ b/mk/tools/tools.FreeMiNT.mk
@@ -26,6 +26,7 @@ TOOLS_PLATFORM.expr?=		/usr/bin/expr
 TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/lex
 TOOLS_PLATFORM.grep?=		/bin/grep
 TOOLS_PLATFORM.groff?=		/usr/bin/groff
diff --git a/mk/tools/tools.GNUkFreeBSD.mk b/mk/tools/tools.GNUkFreeBSD.mk
index 2972c83dc1f9..11f1266130a9 100644
--- a/mk/tools/tools.GNUkFreeBSD.mk
+++ b/mk/tools/tools.GNUkFreeBSD.mk
@@ -31,6 +31,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.gm4?=		/usr/bin/m4
 .if exists(/usr/bin/make)
 TOOLS_PLATFORM.gmake?=		/usr/bin/make
diff --git a/mk/tools/tools.HPUX.mk b/mk/tools/tools.HPUX.mk
index 03a885896759..a43460501fa4 100644
--- a/mk/tools/tools.HPUX.mk
+++ b/mk/tools/tools.HPUX.mk
@@ -24,6 +24,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/grep -F
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.gunzip?=		/usr/contrib/bin/gunzip -f
 TOOLS_PLATFORM.gzcat?=		/usr/contrib/bin/gzcat
diff --git a/mk/tools/tools.Haiku.mk b/mk/tools/tools.Haiku.mk
index 0bca51421dc4..7543a243bafe 100644
--- a/mk/tools/tools.Haiku.mk
+++ b/mk/tools/tools.Haiku.mk
@@ -24,7 +24,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/bin/chown
+.if exists(/bin/ci)
+TOOLS_PLATFORM.ci?=		/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/bin/cmp
+.if exists(/bin/co)
+TOOLS_PLATFORM.co?=		/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.cut?=		/bin/cut
 .if exists(/bin/curl)
@@ -43,6 +49,7 @@ TOOLS_PLATFORM.expr?=		/bin/expr
 TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.find?=		/bin/find
+TOOLS_PLATFORM.fold?=		/bin/fold
 TOOLS_PLATFORM.ftp?=		/bin/ftp
 TOOLS_PLATFORM.gawk?=		/bin/gawk
 .if exists(/bin/m4)
@@ -101,6 +108,9 @@ TOOLS_PLATFORM.makeinfo?=	/bin/makeinfo
 .elif exists(/boot/common/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/boot/common/bin/makeinfo
 .endif
+.if exists(/bin/merge)
+TOOLS_PLATFORM.merge?=		/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 .if exists(/bin/mktemp)
 TOOLS_PLATFORM.mktemp?=		/bin/mktemp
@@ -120,6 +130,9 @@ TOOLS_PLATFORM.openssl?=	/boot/common/bin/openssl
 #TOOLS_PLATFORM.patch?=		/bin/patch
 TOOLS_PLATFORM.printf?=		/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/bin/rcs
+.endif
 TOOLS_PLATFORM.readlink?=	/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
diff --git a/mk/tools/tools.IRIX.mk b/mk/tools/tools.IRIX.mk
index f761a7e65238..cfee8d17748c 100644
--- a/mk/tools/tools.IRIX.mk
+++ b/mk/tools/tools.IRIX.mk
@@ -24,6 +24,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/sbin/find
+TOOLS_PLATFORM.fold?=		/sbin/fold
 TOOLS_PLATFORM.grep?=		/sbin/grep
 .if exists(/usr/sbin/gunzip)
 TOOLS_PLATFORM.gunzip?=		/usr/sbin/gunzip -f
diff --git a/mk/tools/tools.Interix.mk b/mk/tools/tools.Interix.mk
index 4c30c5baccc3..2e91bbba5aa6 100644
--- a/mk/tools/tools.Interix.mk
+++ b/mk/tools/tools.Interix.mk
@@ -28,6 +28,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.file?=		/bin/file
 TOOLS_PLATFORM.find?=		/bin/find
+TOOLS_PLATFORM.fold?=		/bin/fold
 .if empty(MACHINE_PLATFORM:MInterix-[0-5].*-*)
 TOOOLS_PLATFORM.gawk?=		/usr/contrib/bin/gawk
 .endif
diff --git a/mk/tools/tools.Linux.mk b/mk/tools/tools.Linux.mk
index 151a919f460b..b86b7a07e7fa 100644
--- a/mk/tools/tools.Linux.mk
+++ b/mk/tools/tools.Linux.mk
@@ -45,11 +45,17 @@ TOOLS_PLATFORM.chown?=		/bin/chown
 .elif exists(/usr/sbin/chown)
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
 .endif
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 .if exists(/bin/cmp)
 TOOLS_PLATFORM.cmp?=		/bin/cmp
 .elif exists(/usr/bin/cmp)
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
 .endif
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 .if exists(/bin/tcsh)
 TOOLS_PLATFORM.csh?=		/bin/tcsh
@@ -94,6 +100,7 @@ TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 .endif
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 .if exists(/etc/debian_version)
 .  if exists(/usr/bin/gawk)
 TOOLS_PLATFORM.gawk?=		/usr/bin/gawk
@@ -188,6 +195,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail	# Debian, Slackware, SuSE
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 .if exists(/usr/bin/mktemp)
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
@@ -221,6 +231,9 @@ TOOLS_PLATFORM.openssl?=	/usr/bin/openssl
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 .endif
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.readlink?=	/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
diff --git a/mk/tools/tools.Minix.mk b/mk/tools/tools.Minix.mk
index 6a00c5c0bbc6..b880ff638ea9 100644
--- a/mk/tools/tools.Minix.mk
+++ b/mk/tools/tools.Minix.mk
@@ -14,7 +14,7 @@ TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/bin/chown
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
-TOOLS_PLATFORM.cp?=		/bin/cp 
+TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
 TOOLS_PLATFORM.date?=		/bin/date
 TOOLS_PLATFORM.diff?=		/usr/bin/diff
@@ -28,6 +28,7 @@ TOOLS_PLATFORM.fetch?=		/usr/bin/fetch
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/flex
 TOOLS_PLATFORM.ftp?=		/usr/bin/ftp
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
diff --git a/mk/tools/tools.MirBSD.mk b/mk/tools/tools.MirBSD.mk
index a19cc3e4307e..e59c8c78c0fe 100644
--- a/mk/tools/tools.MirBSD.mk
+++ b/mk/tools/tools.MirBSD.mk
@@ -25,6 +25,7 @@ TOOLS_PLATFORM.false?=		/bin/sh -c false
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/lex
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.gunzip?=		/usr/bin/gunzip -f
diff --git a/mk/tools/tools.NetBSD.mk b/mk/tools/tools.NetBSD.mk
index 0ec2a1a5a3ef..eebdfd54cfeb 100644
--- a/mk/tools/tools.NetBSD.mk
+++ b/mk/tools/tools.NetBSD.mk
@@ -14,7 +14,13 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM.co?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 .if exists(/usr/bin/ctfconvert)
@@ -36,6 +42,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/lex
 TOOLS_PLATFORM.ftp?=		/usr/bin/ftp
 .if exists(/usr/bin/gettext)
@@ -71,6 +78,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 .if exists(/usr/bin/msgconv)
@@ -94,6 +104,9 @@ TOOLS_PLATFORM.paxctl?=		/usr/sbin/paxctl
 .endif
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 .if empty(USE_CROSS_COMPILE:M[yY][eE][sS])
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 .else
diff --git a/mk/tools/tools.OSF1.mk b/mk/tools/tools.OSF1.mk
index 84aed7ce5daf..b6a15d758ac4 100644
--- a/mk/tools/tools.OSF1.mk
+++ b/mk/tools/tools.OSF1.mk
@@ -26,6 +26,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.gunzip?=		/usr/bin/gunzip -f
 TOOLS_PLATFORM.gzcat?=		/usr/bin/gzcat
diff --git a/mk/tools/tools.OpenBSD.mk b/mk/tools/tools.OpenBSD.mk
index 516d133270a0..72d88f622049 100644
--- a/mk/tools/tools.OpenBSD.mk
+++ b/mk/tools/tools.OpenBSD.mk
@@ -20,7 +20,13 @@ TOOLS_PLATFORM.chown?=		/sbin/chown
 .elif exists(/usr/sbin/chown)
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
 .endif
+.if exists(/usr/bin/ci)
+TOOLS_PLATFORM?=		/usr/bin/ci
+.endif
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+.if exists(/usr/bin/co)
+TOOLS_PLATFORM?=		/usr/bin/co
+.endif
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -36,6 +42,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		/usr/bin/lex
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.gunzip?=		/usr/bin/gunzip -f
@@ -59,6 +66,9 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+.if exists(/usr/bin/merge)
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
+.endif
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -78,6 +88,9 @@ TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
+.if exists(/usr/bin/rcs)
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
+.endif
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
 TOOLS_PLATFORM.sdiff?=		/usr/bin/sdiff
diff --git a/mk/tools/tools.QNX.mk b/mk/tools/tools.QNX.mk
index 1a2d288e3ac2..9938b796f60d 100644
--- a/mk/tools/tools.QNX.mk
+++ b/mk/tools/tools.QNX.mk
@@ -29,6 +29,7 @@ TOOLS_PLATFORM.false?=		false			# shell builtin
 TOOLS_PLATFORM.fgrep?=		/usr/bin/fgrep
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.flex?=		${QNX_HOST}/usr/bin/flex
 TOOLS_PLATFORM.ftp?=		/usr/bin/ftp
 TOOLS_PLATFORM.gawk?=		/usr/bin/gawk
diff --git a/mk/tools/tools.SCO_SV.mk b/mk/tools/tools.SCO_SV.mk
index f81c13d5e37d..351304ce130a 100644
--- a/mk/tools/tools.SCO_SV.mk
+++ b/mk/tools/tools.SCO_SV.mk
@@ -27,6 +27,7 @@ TOOLS_PLATFORM.false?=		/bin/false
 TOOLS_PLATFORM.fgrep?=		/bin/fgrep
 TOOLS_PLATFORM.file?=		/bin/file
 TOOLS_PLATFORM.find?=		/bin/find
+TOOLS_PLATFORM.fold?=		/bin/fold
 TOOLS_PLATFORM.grep?=		/bin/grep
 TOOLS_PLATFORM.gunzip?=		/bin/gunzip -f
 TOOLS_PLATFORM.gzcat?=		/bin/gzcat
diff --git a/mk/tools/tools.SunOS.mk b/mk/tools/tools.SunOS.mk
index 079ea307b513..cff31a8d40b9 100644
--- a/mk/tools/tools.SunOS.mk
+++ b/mk/tools/tools.SunOS.mk
@@ -81,6 +81,7 @@ TOOLS_PLATFORM.find?=		/usr/gnu/bin/find
 .else
 TOOLS_PLATFORM.find?=		/usr/bin/find
 .endif
+TOOLS_PLATFORM.fold?=   /usr/bin/fold
 .if exists(/usr/bin/flex)
 TOOLS_PLATFORM.flex?=		/usr/bin/flex
 TOOLS_PLATFORM.lex?=		/usr/bin/flex
diff --git a/mk/tools/tools.UnixWare.mk b/mk/tools/tools.UnixWare.mk
index d59bd916823e..d90344815470 100644
--- a/mk/tools/tools.UnixWare.mk
+++ b/mk/tools/tools.UnixWare.mk
@@ -24,6 +24,7 @@ TOOLS_PLATFORM.false?=		/usr/bin/false
 TOOLS_PLATFORM.fgrep?=		/usr/bin/grep -F
 TOOLS_PLATFORM.file?=		/usr/bin/file
 TOOLS_PLATFORM.find?=		/usr/bin/find
+TOOLS_PLATFORM.fold?=		/usr/bin/fold
 TOOLS_PLATFORM.grep?=		/usr/bin/grep
 TOOLS_PLATFORM.head?=		/usr/bin/head
 TOOLS_PLATFORM.hostname?=	/usr/bin/hostname
diff --git a/pkgtools/pkg_install/files/add/perform.c b/pkgtools/pkg_install/files/add/perform.c
index c0111998c23f..7f6647514091 100644
--- a/pkgtools/pkg_install/files/add/perform.c
+++ b/pkgtools/pkg_install/files/add/perform.c
@@ -977,6 +977,20 @@ run_install_script(struct pkg_task *pkg, const char *argument)
 	setenv(PKG_PREFIX_VNAME, pkg->prefix, 1);
 	setenv(PKG_METADATA_DIR_VNAME, pkg->logdir, 1);
 	setenv(PKG_REFCOUNT_DBDIR_VNAME, config_pkg_refcount_dbdir, 1);
+	if (vcs_enabled != NULL)
+		setenv("VCSTRACK_CONF", vcs_enabled, 0);
+	if (vcs_vcs != NULL)
+		setenv("VCS", vcs_vcs, 1);
+	if (vcs_vcsdir != NULL)
+		setenv("VCSDIR", vcs_vcsdir, 1);
+	if (vcs_vcsautomerge != NULL)
+		setenv("VCSAUTOMERGE", vcs_vcsautomerge, 0); //prefer temporary setting
+	if (vcs_vcsconfpull != NULL)
+		setenv("VCSCONFPULL", vcs_vcsconfpull, 1);
+	if (vcs_remotevcs != NULL)
+		setenv("REMOTEVCS", vcs_remotevcs, 1);
+	if (vcs_role != NULL)
+		setenv("ROLE", vcs_role, 1);
 
 	if (Verbose)
 		printf("Running install with PRE-INSTALL for %s.\n", pkg->pkgname);
diff --git a/pkgtools/pkg_install/files/lib/lib.h b/pkgtools/pkg_install/files/lib/lib.h
index 17fe9231c595..2d5e787d34f7 100644
--- a/pkgtools/pkg_install/files/lib/lib.h
+++ b/pkgtools/pkg_install/files/lib/lib.h
@@ -450,5 +450,11 @@ extern const char tnf_vulnerability_base[];
 
 extern const char *acceptable_licenses;
 extern const char *default_acceptable_licenses;
-
+extern const char *vcs_enabled;
+extern const char *vcs_vcs;
+extern const char *vcs_vcsdir;
+extern const char *vcs_vcsautomerge;
+extern const char *vcs_vcsconfpull;
+extern const char *vcs_remotevcs;
+extern const char *vcs_role;
 #endif				/* _INST_LIB_LIB_H_ */
diff --git a/pkgtools/pkg_install/files/lib/parse-config.c b/pkgtools/pkg_install/files/lib/parse-config.c
index bdeba73756cf..d794617d953d 100644
--- a/pkgtools/pkg_install/files/lib/parse-config.c
+++ b/pkgtools/pkg_install/files/lib/parse-config.c
@@ -85,6 +85,13 @@ const char *pkg_vulnerabilities_url;
 const char *ignore_advisories = NULL;
 const char tnf_vulnerability_base[] = "http://ftp.NetBSD.org/pub/NetBSD/packages/vulns";;
 const char *acceptable_licenses = NULL;
+const char *vcs_enabled;
+const char *vcs_vcs;
+const char *vcs_vcsdir;
+const char *vcs_vcsautomerge;
+const char *vcs_vcsconfpull;
+const char *vcs_remotevcs;
+const char *vcs_role;
 
 static struct config_variable {
 	const char *name;
@@ -116,6 +123,13 @@ static struct config_variable {
 	{ "PKGVULNURL", &pkg_vulnerabilities_url },
 	{ "VERBOSE_NETIO", &verbose_netio },
 	{ "VERIFIED_INSTALLATION", &verified_installation },
+	{ "VCSTRACK_CONF", &vcs_enabled },
+	{ "VCS", &vcs_vcs },
+	{ "VCSDIR", &vcs_vcsdir },
+	{ "VCSAUTOMERGE", &vcs_vcsautomerge },
+	{ "VCSCONFPULL", &vcs_vcsconfpull },
+	{ "REMOTEVCS", &vcs_remotevcs },
+	{ "ROLE", &vcs_role },
 	{ NULL, NULL }, /* For use by pkg_install_show_variable */
 	{ NULL, NULL }
 };
diff --git a/pkgtools/pkg_install/files/lib/pkg_install.conf.5.in b/pkgtools/pkg_install/files/lib/pkg_install.conf.5.in
index d5ad8b449485..baf325865e5f 100644
--- a/pkgtools/pkg_install/files/lib/pkg_install.conf.5.in
+++ b/pkgtools/pkg_install/files/lib/pkg_install.conf.5.in
@@ -188,6 +188,30 @@ Currently supported are uncompressed files and files compressed by
 or
 .Xr gzip 1
 .Pq Pa .gz .
+.It Dv VCSTRACK_CONF 
+Enable configuration files version tracking by setting VCSTRACK_CONF to "yes".
+It defaults to being disabled ("no"). This option needs to be enabled even if attempting to deploy remote configuration to the system via VCSCONFPULL
+.It Dv VCSDIR
+Path where the working directory for configuration files handling should be.
+It also double as a vcs repository, if REMOTEVCS is unset.
+.It Dv REMOTEVCS
+URI describing how to access a remote repository, according to the format required by the chosen VCS.
+Remote repositories are disabled if this variable is unset, or if it is set to "no".
+.It Dv VCSAUTOMERGE
+Set to "yes" in order to automatically merge and install configuration changes.
+.It Dv ROLE
+The role of this system, used when deploying configuration from a remote repository via VCSCONFPULL.
+.It Dv VCSCONFPULL
+Set to "yes" to have pkgsrc attempt to deploy configuration files from a remote repository.
+It works with git, svn and mercurial. The remote repository should have branches named
+"category_packageName_packageVersion_compatibilityRangeStart_compatibilityRangeEnd_systemRole"
+with an optional, additional field "_hostname" that gets best-matched with the system hostname.
+See pkgsrc/mk/pkginstall/versioning for a more detailed description.
+Configuration files in each branch are prepended "/" and copied to the system unconditionally.
+.It Dv VCS
+The Version Control solution to be used. PKGSRC defaults to using rcs if this is unset.
+Other software, such as git, svn, cvs and hg, if desired, must be manually installed by the user in the system PATH.
+
 .It Dv VERBOSE_NETIO
 Log details of network IO to stderr.
 .It Dv VERIFIED_INSTALLATION
diff --git a/pkgtools/pkg_install/files/lib/pkg_install.conf.cat.in b/pkgtools/pkg_install/files/lib/pkg_install.conf.cat.in
index 01b891f3c711..9860178cd6ac 100644
--- a/pkgtools/pkg_install/files/lib/pkg_install.conf.cat.in
+++ b/pkgtools/pkg_install/files/lib/pkg_install.conf.cat.in
@@ -149,6 +149,45 @@ DDEESSCCRRIIPPTTIIOONN
              supported are uncompressed files and files compressed by bzip2(1)
              (_._b_z_2) or gzip(1) (_._g_z).
 
+     VCSTRACK_CONF
+             Enable configuration files version tracking by setting
+             VCSTRACK_CONF to "yes".  It defaults to being disabled ("no").
+             This option needs to be enabled even if attempting to deploy
+             remote configuration to the system via VCSCONFPULL
+
+     VCSDIR  Path where the working directory for configuration files handling
+             should be.  It also double as a vcs repository, if REMOTEVCS is
+             unset.
+
+     REMOTEVCS
+             URI describing how to access a remote repository, according to
+             the format required by the chosen VCS.  Remote repositories are
+             disabled if this variable is unset, or if it is set to "no".
+
+     VCSAUTOMERGE
+             Set to "yes" in order to automatically merge and install
+             configuration changes.
+
+     ROLE    The role of this system, used when deploying configuration from a
+             remote repository via VCSCONFPULL.
+
+     VCSCONFPULL
+             Set to "yes" to have pkgsrc attempt to deploy configuration files
+             from a remote repository.  It works with git, svn and mercurial.
+             The remote repository should have branches named
+             "category_packageName_packageVersion_compatibilityRangeStart_compatibilityRangeEnd_systemRole"
+             with an optional, additional field "_hostname" that gets best-
+             matched with the system hostname.  See
+             pkgsrc/mk/pkginstall/versioning for a more detailed description.
+             Configuration files in each branch are prepended "/" and copied
+             to the system unconditionally.
+
+     VCS     The Version Control solution to be used. PKGSRC defaults to using
+             rcs if this is unset.  Other software, such as git, svn, cvs and
+             hg, if desired, must be manually installed by the user in the
+             system PATH.
+
+
      VERBOSE_NETIO
              Log details of network IO to stderr.
 
@@ -175,4 +214,4 @@ FFIILLEESS
 SSEEEE AALLSSOO
      pkg_add(1), pkg_admin(1), pkg_create(1), pkg_delete(1), pkg_info(1)
 
-pkgsrc                         October 28, 2014                         pkgsrc
+NetBSD 8.0                     October 28, 2014                     NetBSD 8.0
diff --git a/pkgtools/pkgconftrack/DESCR b/pkgtools/pkgconftrack/DESCR
new file mode 100644
index 000000000000..3ba6268fe05d
--- /dev/null
+++ b/pkgtools/pkgconftrack/DESCR
@@ -0,0 +1,2 @@
+Manually handle configuration files revisions in pkgsrc VCS backends
+outside of package upgrades.
diff --git a/pkgtools/pkgconftrack/Makefile b/pkgtools/pkgconftrack/Makefile
new file mode 100644
index 000000000000..7bbe502454c0
--- /dev/null
+++ b/pkgtools/pkgconftrack/Makefile
@@ -0,0 +1,24 @@
+PKGNAME=	pkgconftrack-1.0
+CATEGORIES=	pkgtools
+
+MAINTAINER=	keivan%motavalli.me@localhost
+HOMEPAGE=	https://pkgsrc.org/
+COMMENT=	Script that aids in managing manual configuration changes
+LICENSE=	2-clause-bsd
+
+.include "../../mk/bsd.prefs.mk"
+
+INSTALLATION_DIRS=	bin ${PKGMANDIR}/man1
+NO_BUILD=		yes
+
+USE_TOOLS+=		sh
+REPLACE_SH=		pkgconftrack
+
+do-extract:
+	${RUN} ${CP:Q} -R ${FILESDIR} ${WRKSRC}
+
+do-install:
+	${INSTALL_SCRIPT} ${WRKSRC}/pkgconftrack ${DESTDIR}${PREFIX}/bin/pkgconftrack
+	${INSTALL_MAN} ${WRKSRC}/pkgconftrack.1 ${DESTDIR}${PREFIX}/${PKGMANDIR}/man1
+
+.include "../../mk/bsd.pkg.mk"
diff --git a/pkgtools/pkgconftrack/PLIST b/pkgtools/pkgconftrack/PLIST
new file mode 100644
index 000000000000..68c4fef251cc
--- /dev/null
+++ b/pkgtools/pkgconftrack/PLIST
@@ -0,0 +1,2 @@
+bin/pkgconftrack
+man/man1/pkgconftrack.1
diff --git a/pkgtools/pkgconftrack/distinfo b/pkgtools/pkgconftrack/distinfo
new file mode 100644
index 000000000000..afc76512f47f
--- /dev/null
+++ b/pkgtools/pkgconftrack/distinfo
@@ -0,0 +1,2 @@
+$NetBSD$
+
diff --git a/pkgtools/pkgconftrack/files/pkgconftrack b/pkgtools/pkgconftrack/files/pkgconftrack
new file mode 100755
index 000000000000..106bbac71ed0
--- /dev/null
+++ b/pkgtools/pkgconftrack/files/pkgconftrack
@@ -0,0 +1,352 @@
+#!/bin/sh
+usage() {
+	echo 1>&2 "Usage: pkgconftrack [-p PREFIX] [-m commit message]"\
+	"store packagename [... packagenames]"
+}
+
+extractAdditionalFiles() {
+#TODO: parse files for includes
+}
+
+execute()
+{
+	if test "${USER}" = "root"; then
+		su -m pkgvcsconf -c "$1"
+		return $?
+	else
+		eval "$1"
+		return $?
+	fi
+}
+
+pkgchown()
+{
+	_restoreDir="`pwd`"
+	if echo "${1}" | grep -Fv "${_VCSDIR}"; then
+		echo 1>&2 "path \"${1}\" not under VCSDIR: \"${_VCSDIR}\""
+		return 1
+	fi
+	if test -d "${_VCSDIR}"; then
+		chmod 700 "${_VCSDIR}"
+		chown pkgvcsconf:pkgvcsconf "${_VCSDIR}"
+	fi
+	if test -d "${1}"; then
+		if test -n "`find "${1}" -perm -2000 -or -perm -4000 -print \
+			-quit 2>/dev/null`"; then
+			echo 1>&2 "SUID/SGID files under ${1},"\ 
+			"refusing to run CHOWN -R"
+		else
+			chmod 700 "${1}"
+			chown -R pkgvcsconf:pkgvcsconf "${1}"
+		fi
+		if test "${1}" = "${_VCSDIR}"; then
+			return 0
+		fi
+	fi
+	if test -f "${1}"; then
+		if test -u "${1}" || test -g "${1}"; then
+			echo 1>&2 "SUID/SGID file at ${1}, refusing to chown it"
+		else
+			chmod 600 "${1}"
+			chown pkgvcsconf:pkgvcsconf "${1}"
+		fi
+		_dirs="`dirname "${1}"`"
+	else
+		_dirs="${1}"
+	fi
+	_dirsplit="`echo "${_dirs}" | awk -F "${_VCSDIR}" '{print $2}'| tr "/" " "`" 
+	cd "${_VCSDIR}"
+	for dir in ${_dirsplit}
+		do
+			if test -d "${dir}"; then
+					execute "test -r \"${dir}\""
+					_canR=$?
+					execute "test -w \"${dir}\""
+					_canW=$?
+					if test "${_canR}" -ne 0 || test "${_canW}" -ne 0; then
+						chmod 700 "${dir}"
+						chown pkgvcsconf:pkgvcsconf\
+						"${dir}"
+					fi
+					cd "${dir}"
+			fi
+		done
+	cd "${_restoreDir}"
+}
+
+if [ "${USER}" = "root" ]; then
+	drop=`which true`
+else
+	drop=`which false`
+fi
+
+store() {
+	if test ! -f ${1}; then
+		echo 1>&2 "Can't access file ${1}"
+		return 1
+	fi
+	if test -d "${2}" && test -w "${2}"; then
+		if ${drop}; then
+			pkgchown "${2}"
+		fi
+		_dir="`dirname ${1}`"
+		if [ ! -d "${2}/user/${_dir}" ]; then
+			execute "mkdir -p \"${2}/user/${_dir}\"";
+		fi
+		cp -f "${1}" "${2}/user/${1}"
+		if ${drop}; then
+			pkgchown "${2}/user/${1}"
+		fi
+		case ${3} in
+			"rcs")
+				execute "rcs -U \"${2}/user/${1}\" > /dev/null"
+				execute "echo . | ci -m\"${_MESSAGE}\" -u \"${2}/user/${1}\"" 
+				return $?
+				;;
+			"cvs")
+				if [ "${4}" = "no" ]; then
+					_CVSROOT="${2}/CVSROOT"
+				else
+					_CVSROOT="${4}"
+				fi
+				cd "${2}/user"
+				_status=0
+				_relPath="`echo "${1}" | sed 's@^/@@'`"
+				OLDIFS="$IFS"
+				IFS="/"
+				for curdir in ${_relPath} 
+					do
+						execute "cvs -d \"${_CVSROOT}\" add \"${curdir}\""
+						if [ $? -ne 0 ]; then
+							return 1
+						else
+							cd "${curdir}" 2>/dev/null
+						fi
+					done
+				IFS="$OLDIFS"
+				;;
+			"git")
+				cd "${2}"
+				execute "git --git-dir=\"${2}/.git\" --work-tree=\"${2}\" add -f \"${2}/user/${1}\""
+				return $?
+				;;
+			"mercurial")
+				cd "${2}"
+				execute "hg --repository \"${2}\" add \"${2}/user/${1}\""
+				return $?
+				;;
+			"subversion")
+				cd "${2}/user"
+				OLDIFS="$IFS"
+				_relPath="`echo "${1}" | sed 's@^/@@'`"
+				IFS="/"
+				_status=0
+				for curdir in ${_relPath}
+					do
+						execute "svn add --force --depth=empty \"${curdir}\""
+						if [ $? -ne 0 ]; then
+							return 1
+						else
+							cd "${curdir}" 2>/dev/null
+						fi
+					done
+				IFS="$OLDIFS"
+				;;
+			*)
+				echo 1>&2 "Unsupported versioning system: ${3}"
+				return 1
+				;;
+		esac		
+	else
+		echo 1>&2 "Can't use a working directory at VCSDIR: ${2}"
+		return 1
+	fi
+	
+}
+
+commit() {
+	if test -d "${2}" && test -w "${2}"; then
+		cd "${2}"
+		if ${drop}; then
+			pkgchown "${2}"
+		fi
+		_COMMITMESSAGE="${1}"
+		case ${3} in
+			"rcs")
+				#do nothing
+				return 0
+				;;
+			"cvs")
+				if [ "${4}" = "no" ]; then
+					_CVSROOT="${2}/CVSROOT"
+				else
+					_CVSROOT="${4}"
+				fi
+				execute "cvs -Q -d \"${_CVSROOT}\" commit -R -m \"${_COMMITMESSAGE}\""
+				return $?
+				;;
+			"git")
+				execute "git --git-dir=\"${2}/.git\" --work-tree=\"${2}\" commit -m \"${_COMMITMESSAGE}\""
+				_gitStatus=$?
+				if [ "${4}" != "no" ] && [ $_gitStatus -eq 0 ]; then
+					execute "git --git-dir=\"${2}/.git\" --work-tree=\"${2}\" push origin master"
+					_gitStatus=$?
+				fi
+				return $_gitStatus
+				;;
+			"mercurial")
+				execute "hg --repository \"${2}\" commit -m \"${_COMMITMESSAGE}\" --user pkgconftrack"
+				_hgStatus=$?
+				if [ "${4}" != "no" ] && [ $_hgStatus -eq 0 ]; then
+					execute "hg --repository \"${2}\" push \"${4}\""
+					_hgStatus=$?
+				fi
+				return $_hgStatus
+				;;
+			"subversion")
+				cd "${2}/user"
+				execute "svn commit -m \"${_COMMITMESSAGE}\""
+				return $?
+				;;
+			*)
+				echo 1>&2 "pkgconfrack: commit: ${3}: unsupported VCS"
+				return 1
+				;;
+		esac
+	else
+		echo 1>&2 "Can't access VCSDIR at ${2}!"
+		exit 1
+	fi
+}
+
+while getopts "p:m:" flag
+do
+	case ${flag} in
+	"m"|"M")
+		_MESSAGE=${OPTARG}
+		;;
+	"p"|"P")
+		_PREFIX=${OPTARG}
+		;;	
+	esac
+done
+shift "`expr "${OPTIND}" - 1`"
+
+if [ -z "${_PREFIX}" ]; then
+	_PREFIX=/usr/pkg
+fi
+if [ -z "${_MESSAGE}" ]; then
+	_MESSAGE="`date`"
+fi
+_randId="`cat /dev/urandom | tr -dc '[0-9]' | fold -w 6 | head -n 1`"
+time="`date "+%Y%m%d%H%S"`"
+_MESSAGE="${tstamp}-${_randId}:pkgconftrack: ${_MESSAGE}"
+
+_VCSDIR="${VCSDIR}"
+if [ -z "${_VCSDIR}" ]; then
+	_VCSDIR="`${_PREFIX}/sbin/pkg_admin config-var VCSDIR`"
+fi
+if [ -z "${_VCSDIR}" ]; then
+	_firstPackage="`${_PREFIX}/sbin/pkg_info | head -n 1 | awk '{print $1}' | awk -F "=" '/^VARBASE=/ {gsub("\"","");printf "%s", $2}'`"
+	VARBASE="`${_PREFIX}/sbin/pkg_info -i "${_firstPackage}"`"
+	if [ -n "${VARBASE}" ]; then
+		_VCSDIR="${VARBASE}/confrepo"
+	else
+		_VCSDIR="/var/confrepo"
+	fi
+fi
+
+_REMOTEVCS="$REMOTEVCS"
+if [ -z "$_REMOTEVCS" ]; then
+	_REMOTEVCS="`${_PREFIX}/sbin/pkg_admin config-var REMOTEVCS`"
+fi
+_REMOTEVCS="${_REMOTEVCS:-no}"
+case "${_REMOTEVCS}" in
+[Nn][Oo])
+	_REMOTEVCS=no
+	;;
+esac
+
+_VCS="$VCS"
+if [ -z "$_VCS" ]; then
+	_VCS="`${_PREFIX}/sbin/pkg_admin config-var VCS`"
+fi
+_VCS="${_VCS:-rcs}"
+case "${_VCS}" in
+[Rr][Cc][Ss])
+	_VCS=rcs
+	;;
+[Gg][Ii][Tt])
+	_VCS=git
+	;;
+[Cc][Vv][Ss])
+	_VCS=cvs
+	;;
+[Hh][Gg])
+	_VCS=mercurial
+	;;
+[Ss][Vv][Nn])
+	_VCS=subversion
+	;;
+[Ss][Uu][Bb][Vv][Ee][Rr][Ss][Ii][Oo][Nn])
+	_VCS=subversion
+	;;
+[Mm][Ee][Rr][Cc][Uu][Rr][Ii][Aa][Ll])
+	_VCS=mercurial
+	;;
+esac
+
+echo "prefix: ${_PREFIX}, VCSDIR: ${_VCSDIR}, VCS: ${_VCS}, REMOTEVCS: ${_REMOTEVCS}"
+
+ACTION=$1
+PACKAGES=""
+while [ $# -gt 1 ] 
+	do
+		PACKAGES="${2} ${PACKAGES}"
+		shift 1
+	done
+
+case $ACTION in
+store|STORE)
+	_status=0
+	for _PACKAGE in ${PACKAGES}
+		do
+			_pkgdbpath="`${_PREFIX}/sbin/pkg_info -E "${_PACKAGE}"`"
+			if [ $? -eq 0 ]; then
+				echo "Storing configuration files for ${_PACKAGE}"
+				_LOCALBASE="`${_PREFIX}/sbin/pkg_info -Q LOCALBASE "${_PACKAGE}"`"
+				_filesList="`${_PREFIX}/sbin/pkg_info -i "${_PACKAGE}" | awk -v pre="${_LOCALBASE}" '/^# FILE: / && $4 ~ /c/ && $4 !~ /r/ {printf "%s/%s\n", pre, $3}'`"
+				for file in ${_filesList}
+					do
+						extractAdditionalFiles "${file}"
+					done
+				_filesList=""${_filesList}" "${_includedFilesList}""
+				for file in ${_filesList}
+					do
+						store "${file}" "${_VCSDIR}" "${_VCS}" "${_REMOTEVCS}"
+						if [ $? -ne 0 ]; then
+							_status=1
+						fi
+						if test -f "${_VCSDIR}/automergedfiles"\
+						&& grep -F "${file}" "${_VCSDIR}/automergedfiles" > /dev/null; then
+							grep -Fv "${file}" "${_VCSDIR}/automergedfiles" > "${_VCSDIR}/automergedfiles.rm"
+							mv -f "${_VCSDIR}/automergedfiles.rm" "${_VCSDIR}/automergedfiles"
+						fi
+					done
+			else
+				echo 1>&2 "Package not found:" "${_PACKAGE}" "in the pkgdb for" "${_PREFIX}"
+			fi
+		done	
+		if [ $_status -eq 0 ]; then
+			commit "${_MESSAGE}" "${_VCSDIR}" "${_VCS}" "${_REMOTEVCS}"
+			exit $?
+		else
+			echo 1>&2 "Failed to store configuration files"
+		fi 
+	;;
+*)
+	echo 1>&2 "${ACTION}: unknown action"
+	usage
+	exit 2
+	;;
+esac
diff --git a/pkgtools/pkgconftrack/files/pkgconftrack.1 b/pkgtools/pkgconftrack/files/pkgconftrack.1
new file mode 100644
index 000000000000..9caa730afd01
--- /dev/null
+++ b/pkgtools/pkgconftrack/files/pkgconftrack.1
@@ -0,0 +1,103 @@
+.ie \n(.g .ds Aq \(aq
+.el       .ds Aq '
+.nh
+.ad l
+.TH "PKGCONFTRACK" "1" "2018-08-01"
+.P
+.SH NAME 
+.RS 4
+\fBpkgconftrack\fR - track packages configuration files after manual edits
+.P
+.RE
+.SH SYNOPSIS
+.RS 4
+\fBpkgconftrack\fR [\fB-m\fR "commit message"] [\fB-p\fR PREFIX] store packageName [packageName_1 packageName_2 ... packageName_n]
+.P
+.RE
+.SH DESCRIPTION
+.P
+pkgsrc is capable of storing changes in package provided example configuration files in a version control system chosen by the user. Locally installed files, if they differ from the package provided ones, are also tracked across package upgrades and automatic merging of changes may be attemped if VCSAUTOMERGE is set to yes.
+.P
+\fBpkgconftrack\fR exists to easily store a group of changed configuration files in the version control solution configured with pkgsrc, even if the package(s) is not going to be upgraded (if the user made manual changes to installed configuration and cares storing a revision of the affected configuration files).
+.P
+The following command line options are available:
+.P
+.RS 4
+\fB-p\fR	is used to look in a specific prefix. If unspecified, \fI/usr/pkg\fR is looked into for VCS related options in \fIetc/pkg_install.conf\fR (see pkg_install.conf(5)). Installed configuration files for each package are extracted from the pkgdb found in the PREFIX.
+.P
+\fB-m\fR	allows to specify a custom commit message. By default, changes are stored with "pkgconftrack" and the output of the date(1) utility.
+.P
+.RE
+The options are to be followed by an action performed on one or more packages.
+Only \fIstore\fR is currenly supported as an action.
+.P
+Environment variables can be used to ovverride identically named options set in \fIpkg_install.conf\fR, these are:
+.RS 4
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.IP \(bu 4
+.\}
+VCS	used to set a version control system, by default RCS is assumed
+
+.RE
+.P
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.IP \(bu 4
+.\}
+VCSDIR	the working directory used by pkgsrc to track configuration files. \fIVARBASE/confrepo\fR is the default working directory. If the packages in your prefix have not been built with a vcs-aware pkginstall release, and no VCSDIR is otherwise defined, pkgconftrack falls back to using \fI/var/confrepo\fR instead.
+
+.RE
+.P
+.RS 4
+.ie n \{\
+\h'-04'\(bu\h'+03'\c
+.\}
+.el \{\
+.IP \(bu 4
+.\}
+REMOTEVCS	an URI defining, in a format understood by the chosen VCS, how to access a remote repository. If unset, no is assumed and a local repository located in VCSDIR is used. Remember to set cryptographic material and additional daemons and their environment variables (such as ssh-agent) as needed before invoking pkgconftrack. Also note that, when running as root, pkgsrc and pkgconftrack
+drop privileges to "pkgvcsconf:pkgvcsconf", so keys will get searched in pkgvcsconf home directory. SSH_AUTH_SOCK, if ssh-agent is in use, must be accessible to that user. 
+
+.RE
+.P
+.RE
+.SH EXIT STATUS
+.RS 4
+pkgconftrack exits 0 if one or more package configuration files got successfully committed to the repository, and >0 if errors occurred. Non existant package names are ignored and are not reflected in the exit status. 
+.P
+.RE
+.SH EXAMPLES
+.RS 4
+Store local modifications to default nginx configuration files in the repository, reading VCS options from /usr/pkg/etc/pkg_install.conf:
+.RS 4
+'''
+# pkgconftrack store nginx 
+'''
+.P
+.RE
+Manually set VCS options on the command line, store nginx and php-fpm configuration with a custom commit message:
+.RS 4
+'''
+# export VCSDIR=/usr/local/configstore; export VCS=git; pkgconftrack -m "changed php-fpm socket path" store nginx php-fpm
+'''
+.P
+.RE
+Store configuration for nginx, php-fpm and mysql reading VCS options and the pkgdb for \fI/usr/packagesource/etc/pkg_install.conf\fR, set a custom message:
+.RS 4
+'''
+# pkgconftrack -m "changed php-fpm socket path" -p /usr/packagesource store nginx php-fpm mysql-server
+'''
+.P
+.RE
+.RE
+.SH SEE ALSO
+.RS 4
+pkg_install.conf(5) pkg_info(1) pkg_admin(1)
+.RE
-- 
2.16.3



Home | Main Index | Thread Index | Old Index