tech-pkg archive

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

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



An introduction to the usage of configuration files tracking and deployment is under review, I attach what was done until today, 9 of August, since I don't plan to introduce further feature or structural changes now.

There's much more to be written in pkgtools/pkgconftrack, it only supports one action at the present time, as described in the commit message below.

FILES keeps track of any file containing the "c" flag, it ignores rc.d scripts, but it does not ignore an "f" among the flags. I don't know if this is desirable. Is there any package that marks configuration files as "cf"?

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, 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).

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.

header files (mk/pkginstall/files) included in packages declare 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. Much more to 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, perse-config.h,
pkg_install.conf.5.in}, pkgtools/pkg_install/files/add/perform.c.
---
 mk/pkginstall/bsd.pkginstall.mk                    |  51 +-
 mk/pkginstall/files                                | 202 ++++-
 mk/pkginstall/header                               |   5 +-
 mk/pkginstall/install                              |  40 +-
 mk/pkginstall/usergroup                            |  38 +-
 mk/pkginstall/usergroupfuncs                       |   4 +-
 mk/pkginstall/usergroupfuncs.DragonFly             |   2 +-
 mk/pkginstall/usergroupfuncs.FreeBSD               |   2 +-
 mk/pkginstall/usergroupfuncs.Linux                 |   2 +-
 mk/pkginstall/versioning                           | 942 +++++++++++++++++++++
 mk/tools/defaults.mk                               |   4 +
 mk/tools/replace.mk                                |  22 +-
 mk/tools/tools.Bitrig.mk                           |   4 +
 mk/tools/tools.Cygwin.mk                           |  12 +
 mk/tools/tools.Darwin.mk                           |   6 +
 mk/tools/tools.DragonFly.mk                        |   6 +
 mk/tools/tools.FreeBSD.mk                          |   2 +
 mk/tools/tools.Haiku.mk                            |   6 +
 mk/tools/tools.Linux.mk                            |  12 +
 mk/tools/tools.NetBSD.mk                           |   4 +
 mk/tools/tools.OpenBSD.mk                          |   9 +
 pkgtools/pkg_install/files/add/perform.c           |  13 +
 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 +
 pkgtools/pkgconftrack/DESCR                        |   2 +
 pkgtools/pkgconftrack/Makefile                     |  21 +
 pkgtools/pkgconftrack/PLIST                        |   2 +
 pkgtools/pkgconftrack/files/pkgconftrack           | 343 ++++++++
 pkgtools/pkgconftrack/files/pkgconftrack.1         | 103 +++
 30 files changed, 1875 insertions(+), 30 deletions(-)
 create mode 100644 mk/pkginstall/versioning
 create mode 100644 pkgtools/pkgconftrack/DESCR
 create mode 100755 pkgtools/pkgconftrack/Makefile
 create mode 100644 pkgtools/pkgconftrack/PLIST
 create mode 100755 pkgtools/pkgconftrack/files/pkgconftrack
 create mode 100644 pkgtools/pkgconftrack/files/pkgconftrack.1

diff --git a/mk/pkginstall/bsd.pkginstall.mk b/mk/pkginstall/bsd.pkginstall.mk
index 93b6230aab53..a382434c31a7 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
 #
@@ -493,6 +496,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,7 +606,11 @@ ${_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
 #	scripts.  MAKE_DIRS is used the same way, but the package admin
@@ -1087,6 +1097,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}
@@ -1119,13 +1131,46 @@ 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}
-
+.if defined(TOOLS_PLATFORM.rcs)
+RCS=${TOOLS_PLATFORM.rcs}
+.else
+USE_TOOLS+=		rcs
+TOOLS_CREATE+=		rcs
+RCS=${TOOLS_PATH.rcs}
+.endif
+FILES_SUBST+=		RCS=${RCS:Q}
+. if defined(TOOLS_PLATFORM.ci)
+CI=${TOOLS_PLATFORM.ci}
+. else
+USE_TOOLS+=		ci
+TOOLS_CREATE+=		ci
+CI=${TOOLS_PATH.ci}
+.endif 
+FILES_SUBST+=		CI=${CI:Q}
+.if defined(TOOLS_PLATFORM.co)
+CO=${TOOLS_PLATFORM.co}
+.else
+USE_TOOLS+=		co
+TOOLS_CREATE+=		co
+CO=${TOOLS_PATH.co}
+.endif
+FILES_SUBST+=		CO=${CO:Q}
+.if defined(TOOLS_PLATFORM.merge)
+MERGE=${TOOLS_PLATFORM.merge}
+.else
+USE_TOOLS+=		merge
+TOOLS_CREATE+=		merge
+MERGE=${TOOLS_PATH.merge}
+.endif
+FILES_SUBST+=		MERGE=${MERGE:Q}
 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..bcfd473fceb2 100644
--- a/mk/pkginstall/files
+++ b/mk/pkginstall/files
@@ -65,6 +65,7 @@ SED="@SED@"
 SORT="@SORT@"
 TEST="@TEST@"
 TRUE="@TRUE@"
+DIFF="@DIFF@"
 
 SELF=$0
 ACTION=$1
@@ -95,15 +96,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="${VCS_CONF_FILES:-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 |
@@ -146,10 +170,171 @@ ADD)
 		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" \
+				&& ! ${ECHO} "${f_flags}" | grep -Fq "r" \
+				&& ${TEST} ! "${VCSCONFPULL}" = "yes"; then
+					if ${TEST} ! -d "${_VCSDIR}"; then
+						${MKDIR} -p "${_VCSDIR}"
+						if ${TEST} ! $? -eq 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} ! $? -eq 0; then
+						${ECHO} "Failed to initialize the repository that should store configuration revisions at ${_VCSDIR}!"
+						${ECHO} "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} "${_VCSDIR} is not writable!"
+					fi	
+				fi
+				if ${TEST} -f "${file}"; then
+					${ECHO} "${PKGNAME}: ${file} already exists"
+					if ${TEST} "${_VCS_ENABLED}" = "yes" \
+					-a ! "${VCSCONFPULL}" = "yes"\
+					&& ! ${ECHO} "${f_flags}" | ${GREP} -Fq "r"; 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} -Fq "${file}" "${_VCSDIR}/automergedfiles"; 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} ! $? -eq 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} -Fq "${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}" -o -c "${f_eg}" \
+				&& ${TEST} "${_VCSCONFPULLFAIL}" = "yes" \
+				-o ! "${VCSCONFPULL}" = "yes" \
+				|| ${ECHO} "${f_flags}" | ${GREP} -Fq "r"; then
+					${ECHO} "${PKGNAME}: copying ${f_eg} to ${file}"
 					${CP} $f_eg $file
 					case $f_user in
 					"")	;;
@@ -168,6 +353,11 @@ ADD)
 			esac
 		fi
 	done
+	if ${TEST} "${_VCS_ENABLED}" = "yes" \
+	&& ${TEST} "${VCSCONFPULL}" != "yes"; then
+		${TEST} ! -x ./+VERSIONING ||
+			./+VERSIONING COMMIT "add ${PKGNAME}"
+	fi 
 	;;
 
 REMOVE)
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..844cc6718e74 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 "${VCS_CONF_FILES}" in
+[Yy][Ee][Ss])
+	VCS_CONF_FILES=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} "$VCS_CONF_FILES" = "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..a63c879debf0 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" -o -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} "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
@@ -371,7 +406,8 @@ CHECK-REMOVE)
 
 *)
 	${ECHO} "Usage: ./+USERGROUP ADD|REMOVE [metadatadir]"
-	${ECHO} "       ./+USERGROUP CHECK-ADD|CHECK-REMOVE [metadatadir]"
+	${ECHO} "       ./+USERGROUP HECK-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..009a81818417
--- /dev/null
+++ b/mk/pkginstall/versioning
@@ -0,0 +1,942 @@
+#
+# 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 VCS_CONF_FILES=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 use can be set via VCS environment variable, and 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 PRAPARE
+#	 ./+VERSIONING PULL PKGPATH PKGVERSION PKGNAME 
+#
+AWK="@AWK@"
+CAT="@CAT@"
+CHMOD="@CHMOD@"
+CHOWN="@CHOWN@"
+DIRNAME="@DIRNAME@"
+ECHO="@ECHO@"
+FIND="@FIND@"
+GREP="@GREP@"
+LS="@LS@"
+MKDIR="@MKDIR@"
+MV="@MV@"
+PWD_CMD="@PWD_CMD@"
+RM="@RM@"
+RMDIR="@RMDIR@"
+SED="@SED@"
+SORT="@SORT@"
+TAIL="@TAIL@"
+TEST="@TEST@"
+TR="@TR@"
+TRUE="@TRUE@"
+RCS="@RCS@"
+CI="@CI@"
+CO="@CO@"
+CP="@CP@"
+CUT="@CUT@"
+MERGE="@MERGE@"
+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" -o "$_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} -Fq "${_VCSDIR}"; then
+		${ECHO} "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} "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" -o -g "$1"; then
+			${ECHO} "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
+					if ! execute "${TEST} -r \"${dir}\" -a -w \"${dir}\""; 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} -eq 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}" -a -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} ! $? -eq 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} -eq 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} -Fq \"origin\""; 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 -a ! "${_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} ! $? -eq 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} "${_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} ! $? -eq 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} ! $? -eq 0; then
+				exitcode=1
+			else
+				cd "${curdir}" 2>/dev/null
+			fi
+		done
+		IFS="${OLDIFS}"
+		;;
+	*)
+		${ECHO} "${_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} "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} "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} ! $? -eq 0; then
+				${ECHO} "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} "Failed to commit conf: $2"
+			exitcode=3
+		fi
+		if ${TEST} "${_REMOTE}" != "no"; then
+			execute "hg --repository \"${_VCSDIR}\" push \"${_REMOTE}\""
+			if ${TEST} ! $? -eq 0; then
+				${ECHO} "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} "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} "${_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\"  '/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 \" '/revision=/ {print $2}')
+			execute "svn export --force --revision ${_firstRev} \"${_REMOTE}/${_PATHSPLIT}\" \"${CFILE}\""
+		fi
+		;;
+	*)
+		${ECHO} "${_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=""
+	_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} -Fq "/branches"; 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} "Configuration deploy: ${_VCS} is not supported"
+			if ${TEST} -z "${VCS}"; then
+				${ECHO} "Set VCS to use configuration deploy in pkgsrc! Supported solutions are git, svn and hg"
+			fi
+			exitcode=2
+			;;
+	esac
+	if ${TEST} ${exitcode} -eq 0 -a -n "${_output}"; then 
+		#sanitize the output first
+		for branch in ${_output}
+			do
+				if ${TEST} $(${ECHO} "${branch}" | ${GREP} -o "_" | ${WC} -l) -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} -Fq '.'; then
+					if ! ${ECHO} ${shortVersion} | ${GREP} -Fq '.'; 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=$((_groupCounter + 1))
+						done
+					if ${TEST} "${_rangeStartLTshortVersion}" = "true" \
+					-a "${_rangeEndGTshortVersion}" = "true"; then
+						if ${TEST} "${_branchRole}" = "any" -o "${_branchRole}" = "ANY"\
+						-o "${_branchRole}" = "${_ROLE}"\
+						-o "${_ROLE}" = "any"; then 
+							compatibleBranches="${branch} ${compatibleBranches}"
+						fi
+					fi
+				else
+					if ${ECHO} ${shortVersion} | ${GREP} -Fq '.'; then
+						${ECHO} "${branch}: branch naming incompatible with package version naming!"
+						continue
+					fi
+					#numerically compare versions
+					if ${TEST} ${_rangeStart} -lt ${shortVersion}\
+					-o ${_rangeStart} -eq ${shortVersion}\
+					&& ${TEST} ${_rangeEnd} -gt ${shortVersion}\
+					-o ${_rangeEnd} -eq ${shortVersion}; then
+
+						if ${TEST} "${_branchRole}" = "any"\
+						-o "${_branchRole}" = "ANY"\
+						-o "${_branchRole}" = "$_ROLE"\
+						-o "${_ROLE}" = "any"; then
+							compatibleBranches="${compatibleBranches} ${branch}"
+						fi
+					fi
+				fi
+			done
+		if ${TEST} -z "${compatibleBranches}"; then
+			${ECHO} "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}"\
+		-a ! "${HOSTNAME}" = "localhost"; then
+			bestHostnameMatchChars=1
+			bestHostnameMatch=""
+			compatibleHostnames=""
+			_charCounter=1
+			for branch in ${compatibleBranches}
+				do
+					if ${TEST} $(${ECHO} "${branch}" | ${GREP} -o "_" | ${WC} -l) -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}" | ${GREP} -o ".") 
+							do
+								if ${TEST} ! "${character}" = $(${ECHO} "${HOSTNAME}" | ${CUT} -c ${_charCounter}); then
+									break
+								else
+									_charCounter=$((_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}"\
+						| ${GREP} -o "_" | ${WC} -l) -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}" | ${GREP} -o "${bestHostnameMatch}" | ${WC} -l) -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} -Fq '.'; then
+				#compare integers
+				versionDistance=$(($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=$(($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}"
+						minimumDistance=$(( $(${ECHO} "${shortVersion}" | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}') - $(${ECHO} "${_branches}" | ${AWK} -F " " '{print $1}' | ${AWK} -F "_" '{print $3}' | ${TR} -dc '[0-9].' | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}') ))
+						miniumDistance=${minimumDistance#-}
+						branchCounter=1
+						for branch in ${_branches}
+							do
+								groupDistance=$(( $(${ECHO} "${shortVersion}" | ${AWK} -F "." -v fieldnum="${groupCounter}" '{print $fieldnum}') - $(${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=${groupDistance#-}
+								if ${TEST} ${groupDistance} -lt ${minimumDistance}; then
+									minimumDistance=${groupDistance}
+									bestBranchGroup=${branch}
+								elif ${TEST} ${groupDistance} -eq ${minimumDistance}; then
+									bestBranchGroup="${bestBranchGroup} ${branch}"
+								fi
+								branchCounter=$((branchCounter + 1))
+							done
+						_branches=${bestBranchGroup}
+						groupCounter=$((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" -a ${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
+		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} -eq 0; then
+						exitcode=5;
+					fi
+				done  
+		fi
+	fi
+	${RM} -fr "${_TEMPDIR}"
+	if ${TEST} ! ${exitcode} -eq 0; then
+		${ECHO} "Error deploying configuration for ${category}/${name}: using package defaults"
+	fi
+	;;
+*)
+	${ECHO} "Usage: ./+VERSIONING REGISTER|CHECKOUT|CHECKOUT-FIRST [examplefile]"
+	${ECHO} "Usage: ./+VERSIONING MERGE [examplefile] [firstrevision]"
+	${ECHO} "Usage: ./+VERSIONING COMMIT [message]"
+	${ECHO} "Usage: ./+VERSIONING PREPARE"
+	${ECHO} "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..873d731b0171 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
@@ -106,6 +108,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 +123,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
diff --git a/mk/tools/replace.mk b/mk/tools/replace.mk
index dbcdd6e12dea..e3ba0be4766a 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)
diff --git a/mk/tools/tools.Bitrig.mk b/mk/tools/tools.Bitrig.mk
index 3ea39e7c2967..2ba513f6d563 100644
--- a/mk/tools/tools.Bitrig.mk
+++ b/mk/tools/tools.Bitrig.mk
@@ -10,7 +10,9 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/sbin/chown
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+TOOLS_PLATFORM.co?=		/usr/bin/co
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -45,6 +47,7 @@ TOOLS_PLATFORM.ls?=		/bin/ls
 TOOLS_PLATFORM.m4?=		/usr/bin/m4
 TOOLS_PLATFORM.mail?=		/usr/bin/mail
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 TOOLS_PLATFORM.mtree?=		/usr/sbin/mtree
@@ -58,6 +61,7 @@ TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
 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..98c1c5e21d16 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
@@ -86,6 +92,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 +128,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..8b97fe817454 100644
--- a/mk/tools/tools.Darwin.mk
+++ b/mk/tools/tools.Darwin.mk
@@ -25,7 +25,9 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+TOOLS_PLATFORM.co?=		/usr/bin/co
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/tcsh
 .if exists(/usr/bin/curl)
@@ -78,6 +80,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 +95,7 @@ TOOLS_PLATFORM.pax?=		/bin/pax
 #TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
 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..c8ba6b68334e 100644
--- a/mk/tools/tools.DragonFly.mk
+++ b/mk/tools/tools.DragonFly.mk
@@ -15,7 +15,9 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+TOOLS_PLATFORM.co?=		/usr/bin/co
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 TOOLS_PLATFORM.cut?=		/usr/bin/cut
@@ -59,6 +61,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 +75,7 @@ TOOLS_PLATFORM.patch?=		/usr/bin/patch
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
 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..c6cf01964a28 100644
--- a/mk/tools/tools.FreeBSD.mk
+++ b/mk/tools/tools.FreeBSD.mk
@@ -15,6 +15,7 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+TOOLS_PLATFORM.ci?=             /usr/bin/ci
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
@@ -67,6 +68,7 @@ TOOLS_PLATFORM.openssl?=	/usr/bin/openssl
 TOOLS_PLATFORM.pax?=		/bin/pax
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 TOOLS_PLATFORM.readlink?=	/usr/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
diff --git a/mk/tools/tools.Haiku.mk b/mk/tools/tools.Haiku.mk
index 0bca51421dc4..19d0605b482a 100644
--- a/mk/tools/tools.Haiku.mk
+++ b/mk/tools/tools.Haiku.mk
@@ -24,7 +24,9 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/bin/chown
+TOOLS_PLATFORM.ci?=		/bin/ci
 TOOLS_PLATFORM.cmp?=		/bin/cmp
+TOOLS_PLATFORM.co?=		/bin/co
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.cut?=		/bin/cut
 .if exists(/bin/curl)
@@ -101,6 +103,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 +125,7 @@ TOOLS_PLATFORM.openssl?=	/boot/common/bin/openssl
 #TOOLS_PLATFORM.patch?=		/bin/patch
 TOOLS_PLATFORM.printf?=		/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/bin/rcs
 TOOLS_PLATFORM.readlink?=	/bin/readlink
 TOOLS_PLATFORM.rm?=		/bin/rm
 TOOLS_PLATFORM.rmdir?=		/bin/rmdir
diff --git a/mk/tools/tools.Linux.mk b/mk/tools/tools.Linux.mk
index 151a919f460b..e68a53a163a9 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
@@ -188,6 +194,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 +230,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.NetBSD.mk b/mk/tools/tools.NetBSD.mk
index 0ec2a1a5a3ef..09eda1240020 100644
--- a/mk/tools/tools.NetBSD.mk
+++ b/mk/tools/tools.NetBSD.mk
@@ -14,7 +14,9 @@ TOOLS_PLATFORM.cat?=		/bin/cat
 TOOLS_PLATFORM.chgrp?=		/usr/bin/chgrp
 TOOLS_PLATFORM.chmod?=		/bin/chmod
 TOOLS_PLATFORM.chown?=		/usr/sbin/chown
+TOOLS_PLATFORM.ci?=		/usr/bin/ci
 TOOLS_PLATFORM.cmp?=		/usr/bin/cmp
+TOOLS_PLATFORM.co?=		/usr/bin/co
 TOOLS_PLATFORM.cp?=		/bin/cp
 TOOLS_PLATFORM.csh?=		/bin/csh
 .if exists(/usr/bin/ctfconvert)
@@ -71,6 +73,7 @@ TOOLS_PLATFORM.mail?=		/usr/bin/mail
 .if exists(/usr/bin/makeinfo)
 TOOLS_PLATFORM.makeinfo?=	/usr/bin/makeinfo
 .endif
+TOOLS_PLATFORM.merge?=		/usr/bin/merge
 TOOLS_PLATFORM.mkdir?=		/bin/mkdir -p
 TOOLS_PLATFORM.mktemp?=		/usr/bin/mktemp
 .if exists(/usr/bin/msgconv)
@@ -94,6 +97,7 @@ TOOLS_PLATFORM.paxctl?=		/usr/sbin/paxctl
 .endif
 TOOLS_PLATFORM.printf?=		/usr/bin/printf
 TOOLS_PLATFORM.pwd?=		/bin/pwd
+TOOLS_PLATFORM.rcs?=		/usr/bin/rcs
 .if empty(USE_CROSS_COMPILE:M[yY][eE][sS])
 TOOLS_PLATFORM.readelf?=	/usr/bin/readelf
 .else
diff --git a/mk/tools/tools.OpenBSD.mk b/mk/tools/tools.OpenBSD.mk
index 516d133270a0..1cea0c06b3f5 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
@@ -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
diff --git a/pkgtools/pkg_install/files/add/perform.c b/pkgtools/pkg_install/files/add/perform.c
index c0111998c23f..f5e30827cbb3 100644
--- a/pkgtools/pkg_install/files/add/perform.c
+++ b/pkgtools/pkg_install/files/add/perform.c
@@ -977,6 +977,19 @@ 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);
+	setenv("VCS_CONF_FILES", 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..2e4ad13b9fb4 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 },
+	{ "VCS_CONF_FILES", &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..f5ae00ca0cd4 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 VCS_CONF_FILES 
+Enable configuration files version tracking by setting VCS_CONF_FILES 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 VCSCONFPULL
+Set to "yes" to have pkgscr 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 ROLE
+The role of this system, used when deploying configuration from a remote repository via VCSCONFPULL
+.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/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 100755
index 000000000000..74a5d03eaf57
--- /dev/null
+++ b/pkgtools/pkgconftrack/Makefile
@@ -0,0 +1,21 @@
+PKGNAME=	pkgconftrack-1.0
+CATEGORIES=	pkgtools
+
+MAINTAINER=	keivan%motavalli.me@localhost		
+HOMEPAGE=	ftp://ftp.NetBSD.org/pub/NetBSD/packages/pkgsrc/doc/pkgsrc.html
+COMMENT=	Script that easily registers in pkgsrc manual configuration changes for a package
+LICENSE=	2-clause-bsd	
+
+.include "../../mk/bsd.prefs.mk"
+
+INSTALLATION_DIRS=	bin ${PKGMANDIR}/man1
+NO_BUILD=	yes
+
+do-extract:
+	@${CP} -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/files/pkgconftrack b/pkgtools/pkgconftrack/files/pkgconftrack
new file mode 100755
index 000000000000..f299e890befb
--- /dev/null
+++ b/pkgtools/pkgconftrack/files/pkgconftrack
@@ -0,0 +1,343 @@
+#!/bin/sh
+usage () {
+	echo "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 -Fq "${_VCSDIR}"; then
+		echo "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 "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" -o -g "$1"; then
+			echo "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
+					if ! execute "test -r \"${dir}\" -a -w \"${dir}\""; 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 "Can't access file $1"
+		return 1
+	fi
+	if test -d "$2" -a -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
+				OLDIFS="$IFS"
+				IFS="/"
+				for curdir in $1
+					do
+						execute "cvs -d \"${_CVSROOT}\" add \"${curdir}\""
+						if [ ! $? -eq 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"
+				IFS="/"
+				_status=0
+				for curdir in $1
+					do
+						execute "svn add --force --depth=empty \"${curdir}\""
+						if [ ! $? -eq 0 ]; then
+							return 1
+						else
+							cd "${curdir}" 2>/dev/null
+						fi
+					done
+				IFS="$OLDIFS"
+				;;
+			*)
+				echo "Unsupported versioning system: $3"
+				return 1
+				;;
+		esac		
+	else
+		echo "Can't use a working directory at VCSDIR: $2"
+		return 1
+	fi
+	
+}
+
+commit() {
+	if test -d "$2" -a -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" -a $_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" -a $_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 "pkgconfrack: commit: ${3}: unsupported VCS"
+				return 1
+				;;
+		esac
+	else
+		echo "Can't access VCSDIR at ${2}!"
+		exit 1
+	fi
+}
+
+_toshift=0
+while getopts "p:m:" flag
+do
+	case $flag in
+	"m"|"M")
+		_MESSAGE=$OPTARG
+		_toshift=$((_toshift + 2))
+		;;
+	"p"|"P")
+		_PREFIX=$OPTARG
+		_toshift=$((_toshift +2))
+		;;	
+	esac
+done
+shift $_toshift
+if [ -z "$_PREFIX" ]; then
+	_PREFIX=/usr/pkg
+fi
+if [ -z "$_MESSAGE" ]; then
+	_MESSAGE=$(date)
+fi
+tstamp=$(date +%s)
+_MESSAGE="${tstamp}:pkgconftrack: ${_MESSAGE}"
+
+_VCSDIR="$VCSDIR"
+if [ -z "$_VCSDIR" ]; then
+	_VCSDIR=$(${_PREFIX}/sbin/pkg_admin config-var VCSDIR)
+fi
+if [ -z "$_VCSDIR" ]; then
+	VARBASE=$(${_PREFIX}/sbin/pkg_info -i $(${_PREFIX}/sbin/pkg_info | head -n 1 | awk '{print $1}') | awk -F "=" '/^VARBASE=/ {gsub("\"","");printf "%s", $2}')
+	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 [ ! $? -eq 0 ]; then
+							_status=1
+						fi
+						if test -f "${_VCSDIR}/automergedfiles"\
+						&& grep -Fq "${file}" "${_VCSDIR}/automergedfiles"; then
+							grep -Fv "${file}" "${_VCSDIR}/automergedfiles" > "${_VCSDIR}/automergedfiles.rm"
+							mv -f "${_VCSDIR}/automergedfiles.rm" "${_VCSDIR}/automergedfiles"
+						fi
+					done
+			else
+				echo "Package not found:" "${_PACKAGE}" "in the pkgdb for" "${_PREFIX}"
+			fi
+		done	
+		if [ $_status -eq 0 ]; then
+			commit "${_MESSAGE}" "${_VCSDIR}" "${_VCS}" "${_REMOTEVCS}"
+			exit $?
+		else
+			echo "Failed to store configuration files"
+		fi 
+	;;
+*)
+	echo "${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