diff --git a/contrib/do_abidiff b/contrib/do_abidiff
new file mode 100755
index 0000000..9adc93a
--- /dev/null
+++ b/contrib/do_abidiff
@@ -0,0 +1,73 @@
+#!/bin/bash -e
+
+# WARNING: This will wipe ~/rpmbuild
+
+range="$*"
+old="${range%%..*}"
+new="${range##*..}"
+
+err()
+{
+	echo "$1"
+	exit 1
+}
+
+build_rpm()
+{
+	local cur=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
+	local ref="$1"
+
+	# setup rpmbuild env
+	rpmdev-wipetree && rpmdev-setuptree
+
+	# prepare ndctl tree
+	git checkout -b rel_${ref} $ref
+	./autogen.sh
+	./configure CFLAGS='-g -O2' --prefix=/usr --sysconfdir=/etc --libdir=/usr/lib64
+	make clean
+	make rhel/ndctl.spec
+	mkdir -p ~/git/
+	[ -d ~/git/ndctl/ ] && err "~/git/ndctl already exists, please remove it" || true
+	cp -r $(readlink -f .) ~/git/
+
+	# build and copy RPMs
+	./rpmbuild.sh
+	mkdir -p release/rel_${ref}/
+	cp ~/rpmbuild/RPMS/x86_64/*.rpm release/rel_${ref}/
+
+	# restore ndctl branch
+	git checkout $cur
+	git branch -D rel_${ref}
+	rm -rf ~/git/ndctl
+}
+
+do_diff()
+{
+	local pkg="$1"
+	local old_base="$(find . -regex "./release/rel_${old}/${pkg}-[0-9]+.*" | head -1)"
+	local new_base="$(find . -regex "./release/rel_${new}/${pkg}-[0-9]+.*" | head -1)"
+	local old_dev="$(find . -regex "./release/rel_${old}/${pkg}-devel-[0-9]+.*" | head -1)"
+	local new_dev="$(find . -regex "./release/rel_${new}/${pkg}-devel-[0-9]+.*" | head -1)"
+	local old_lib="$(find . -regex "./release/rel_${old}/${pkg}-libs-[0-9]+.*" | head -1)"
+	local new_lib="$(find . -regex "./release/rel_${new}/${pkg}-libs-[0-9]+.*" | head -1)"
+
+	[ -n "$pkg" ] || err "specify a package for diff (ndctl, daxctl)"
+
+	abipkgdiff --dso-only --no-added-syms --harmless --drop-private-types \
+		--devel1 "$old_dev" --devel2 "$new_dev" \
+		"$old_base" "$new_base"
+	abipkgdiff  --no-added-syms --harmless --drop-private-types \
+		--devel1 "$old_dev" --devel2 "$new_dev" \
+		"$old_lib" "$new_lib"
+}
+
+[ -e "COPYING" ] || err "Run from the top level of an ndctl tree"
+if ! command -v "abipkgdiff" >/dev/null; then
+	err "missing abipkgdiff. Please install libabigail"
+fi
+rm -rf release/rel*
+
+build_rpm $old > /dev/null 2>&1
+build_rpm $new > /dev/null 2>&1
+do_diff ndctl
+do_diff daxctl
diff --git a/contrib/prepare-release.sh b/contrib/prepare-release.sh
new file mode 100755
index 0000000..2941124
--- /dev/null
+++ b/contrib/prepare-release.sh
@@ -0,0 +1,201 @@
+#!/bin/bash -e
+
+# Arguments:
+#  fix - fixup release instead of a full release
+#  ignore_rev - ignore the check for _REVISION in libtool versioning checks
+
+# Notes:
+#  - Checkout to the appropriate branch beforehand
+#     master - for major release
+#     ndctl-xx.y - for fixup release
+#    This is important for generating the shortlog
+#  - Add a temporary commit that updates the libtool versions as needed.
+#    This will later become the release commit. Use --amend to add in the
+#    git-version update and the message body.
+
+# WARNING: This will wipe ~/rpmbuild
+
+# Pre-reqs:
+#  - libabigail  (for abipkgdiff)
+#  - rpmdevtools (for rpmdev-setuptree etc)
+
+# TODO
+#  - auto generate a release commit/tag message template
+#  - determine the most recent kernel release and add it to the above
+#  - perform documentation update for pmem.io/ndctl
+#    (this needs the ndctl update to be pushed first..)
+
+cleanup()
+{
+	rm -rf release
+	mkdir release/
+}
+
+err()
+{
+	echo "$1"
+	exit 1
+}
+
+parse_args()
+{
+	local args="$*"
+	grep -q "fix" <<< "$args" && rel_fix="1" || rel_fix=""
+	grep -q "ignore_rev" <<< "$args" && ignore_rev="1" || ignore_rev=""
+}
+
+check_branch()
+{
+	local cur=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
+	if [ -n "$rel_fix" ]; then
+		# fixup release, expect ndctl-xx.y branch
+		if ! grep -Eq "^ndctl.[0-9]+\.y$" <<< "$cur"; then
+			err "expected an ndctl-xx.y branch for fixup release"
+		fi
+	else
+		# major release, expect master branch
+		if ! grep -Eq "^master$" <<< "$cur"; then
+			err "expected master branch for a major release"
+		fi
+	fi
+	if ! git diff-index --quiet HEAD --; then
+		err "$cur has uncommitted/unstaged changes"
+	fi
+}
+
+last_maj()
+{
+	git tag  | sort -V | grep -E "v[0-9]+$" | tail -1
+}
+
+last_fix()
+{
+	local base="$1"
+	git tag  | sort -V | grep -E "$base\.?[0-9]*$" | tail -1
+}
+
+next_maj()
+{
+	local last="$1"
+	local num=${last#v}
+
+	newnum="$((num + 1))"
+	echo "v$newnum"
+}
+
+next_fix()
+{
+	local last="$1"
+	local num=${last##*.}
+	local base=${last%%.*}
+
+	newnum=$((num + 1))
+	echo "$base.$newnum"
+}
+
+gen_lists()
+{
+	local range="$1"
+
+	git shortlog "$range" > release/shortlog
+	git log --pretty=format:"%s" "$range" > release/commits
+	c_count=$(git log --pretty=format:"%s" "$range" | wc -l)
+}
+
+# Check libtool versions in Makefile.am.in
+# $1: lib name (currently libndctl or libdaxctl)
+check_libtool_vers()
+{
+	local lib="$1"
+	local lib_u="${lib^^}"
+	local libdir="${lib##lib}"
+	local symfile="${libdir}/lib/${lib}.sym"
+	local last_cur=$(git show $last_ref:Makefile.am.in | grep -E "^${lib_u}_CURRENT" | cut -d'=' -f2)
+	local last_rev=$(git show $last_ref:Makefile.am.in | grep -E "^${lib_u}_REVISION" | cut -d'=' -f2)
+	local last_age=$(git show $last_ref:Makefile.am.in | grep -E "^${lib_u}_AGE" | cut -d'=' -f2)
+	local last_soname=$((last_cur - last_age))
+	local next_cur=$(git show HEAD:Makefile.am.in | grep -E "^${lib_u}_CURRENT" | cut -d'=' -f2)
+	local next_rev=$(git show HEAD:Makefile.am.in | grep -E "^${lib_u}_REVISION" | cut -d'=' -f2)
+	local next_age=$(git show HEAD:Makefile.am.in | grep -E "^${lib_u}_AGE" | cut -d'=' -f2)
+	local next_soname=$((next_cur - next_age))
+	local soname_diff=$((next_soname - last_soname))
+
+	# generally libtool versions either reset to zero or increase only by one
+	# _CURRENT monotonically increases (by one)
+	if [ "$((next_cur - last_cur))" -gt 1 ]; then
+		err "${lib_u}_CURRENT can increase at most by 1"
+	fi
+	if [ "$next_rev" -ne 0 ]; then
+		if [ "$((next_rev - last_rev))" -gt 1 ]; then
+			err "${lib_u}_REVISION can increase at most by 1"
+		fi
+	fi
+	if [ "$next_age" -ne 0 ]; then
+		if [ "$((next_age - last_age))" -gt 1 ]; then
+			err "${lib_u}_AGE can increase at most by 1"
+		fi
+	fi
+
+	# test for soname change
+	if [ "$soname_diff" -ne 0 ]; then
+		err "${lib}: expected soname to stay unchanged"
+	fi
+
+	# tests based on whether symfile changed
+	# compatibility breaking changes are left for libabigail to detect
+	test -s "$symfile" || err "$symfile: not found"
+	if [ -n "$(git diff --name-only $last_ref..HEAD $symfile)" ]; then
+		# symfile has changed, cur and age should increase
+		if [ "$((next_cur - last_cur))" -ne 1 ]; then
+			err "based on $symfile, ${lib_u}_CURRENT should've increased by 1"
+		fi
+		if [ "$((next_age - last_age))" -ne 1 ]; then
+			err "based on $symfile, ${lib_u}_AGE should've increased by 1"
+		fi
+	else
+		# no changes to symfile, revision should've increased if source changed
+		if [ -n "$ignore_rev" ]; then
+			: # skip
+		elif [ -n "$(git diff --name-only $last_ref..HEAD $libdir/)" ]; then
+			if [ "$((next_rev - last_rev))" -ne 1 ]; then
+				err "based on $symfile, ${lib_u}_REVISION should've increased by 1"
+			fi
+		fi
+	fi
+}
+
+
+# main
+cleanup
+parse_args "$*"
+check_branch
+[ -e "COPYING" ] || err "Run from the top level of an ndctl tree"
+
+last_maj=$(last_maj)
+test -n "$last_maj" || err "Unable to determine last release"
+
+last_fix=$(last_fix $last_maj)
+test -n "$last_fix" || err "Unable to determine last fixup tag for $last_maj"
+
+next_maj=$(next_maj "$last_maj")
+next_fix=$(next_fix "$last_fix")
+[ -n "$rel_fix" ] && last_ref="$last_fix" || last_ref="$last_maj"
+[ -n "$rel_fix" ] && next_ref="$next_fix" || next_ref="$next_maj"
+
+check_libtool_vers "libndctl"
+check_libtool_vers "libdaxctl"
+
+gen_lists ${last_ref}..HEAD
+
+# For ABI diff purposes, use the latest fixes tag
+contrib/do_abidiff ${last_fix}..HEAD
+
+# once everything passes, update the git-version
+sed -i -e "s/DEF_VER=[0-9]\+.*/DEF_VER=${next_ref#v}/" git-version
+
+echo "Ready to release ndctl-$next_ref with $c_count new commits."
+echo "Add git-version to the top commit to get the updated version."
+echo "Use release/commits and release/shortlog to compose the release message"
+echo "The release commit typically contains the Makefile.am.in libtool version"
+echo "update, and the git-version update."
+echo "Finally, ensure the release commit as well as the tag are PGP signed."
