diff --git a/update-third-party.bash b/update-third-party.bash
new file mode 100644
index 0000000000000000000000000000000000000000..3b8358e0114b9afc48c104d9afbbd7ba7de48a90
--- /dev/null
+++ b/update-third-party.bash
@@ -0,0 +1,169 @@
+#=============================================================================
+# Copyright 2015-2016 Kitware, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#=============================================================================
+
+########################################################################
+# Script for updating third party packages.
+#
+# This script should be sourced in a project-specific script which sets
+# the following variables:
+#
+#   name
+#       The name of the project.
+#   ownership
+#       A git author name/email for the commits.
+#   subtree
+#       The location of the thirdparty package within the main source
+#       tree.
+#   repo
+#       The git repository to use as upstream.
+#   tag
+#       The tag, branch or commit hash to use for upstream.
+#   shortlog
+#       Optional.  Set to 'true' to get a shortlog in the commit message.
+#
+# Additionally, an "extract_source" function must be defined. It will be
+# run within the checkout of the project on the requested tag. It should
+# should place the desired tree into $extractdir/$name-reduced. This
+# directory will be used as the newest commit for the project.
+#
+# For convenience, the function may use the "git_archive" function which
+# does a standard "git archive" extraction using the (optional) "paths"
+# variable to only extract a subset of the source tree.
+########################################################################
+
+########################################################################
+# Utility functions
+########################################################################
+git_archive () {
+    git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \
+        tar -C "$extractdir" -x
+}
+
+die () {
+    echo >&2 "$@"
+    exit 1
+}
+
+warn () {
+    echo >&2 "warning: $@"
+}
+
+readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
+readonly basehash_regex="$name $regex_date ([0-9a-f]*)"
+readonly basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )"
+readonly upstream_old_short="$( git cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p}' | egrep '^[0-9a-f]+$' )"
+
+########################################################################
+# Sanity checking
+########################################################################
+[ -n "$name" ] || \
+    die "'name' is empty"
+[ -n "$ownership" ] || \
+    die "'ownership' is empty"
+[ -n "$subtree" ] || \
+    die "'subtree' is empty"
+[ -n "$repo" ] || \
+    die "'repo' is empty"
+[ -n "$tag" ] || \
+    die "'tag' is empty"
+[ -n "$basehash" ] || \
+    warn "'basehash' is empty; performing initial import"
+readonly do_shortlog="${shortlog-false}"
+
+readonly workdir="$PWD/work"
+readonly upstreamdir="$workdir/upstream"
+readonly extractdir="$workdir/extract"
+
+[ -d "$workdir" ] && \
+    die "error: workdir '$workdir' already exists"
+
+trap "rm -rf '$workdir'" EXIT
+
+# Get upstream
+git clone "$repo" "$upstreamdir"
+
+if [ -n "$basehash" ]; then
+    # Use the existing package's history
+    git worktree add "$extractdir" "$basehash"
+    # Clear out the working tree
+    pushd "$extractdir"
+    git ls-files | xargs rm -v
+    find . -type d -empty -delete
+    popd
+else
+    # Create a repo to hold this package's history
+    mkdir -p "$extractdir"
+    git -C "$extractdir" init
+fi
+
+# Extract the subset of upstream we care about
+pushd "$upstreamdir"
+git checkout "$tag"
+readonly upstream_hash="$( git rev-parse HEAD )"
+readonly upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )"
+readonly upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )"
+readonly upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )"
+if $do_shortlog && [ -n "$basehash" ]; then
+    readonly commit_shortlog="
+
+Upstream Shortlog
+-----------------
+
+$( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )"
+else
+    readonly commit_shortlog=""
+fi
+extract_source || \
+    die "failed to extract source"
+popd
+
+[ -d "$extractdir/$name-reduced" ] || \
+    die "expected directory to extract does not exist"
+readonly commit_summary="$name $upstream_date ($upstream_hash_short)"
+
+# Commit the subset
+pushd "$extractdir"
+mv -v "$name-reduced/"* .
+rmdir "$name-reduced/"
+git add -A .
+git commit -n --author="$ownership" --date="$upstream_datetime" -F - <<-EOF
+$commit_summary
+
+Code extracted from:
+
+    $repo
+
+at commit $upstream_hash ($tag).$commit_shortlog
+EOF
+git branch -f "upstream-$name"
+popd
+
+# Merge the subset into this repository
+if [ -n "$basehash" ]; then
+    git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name"
+else
+    unrelated_histories_flag=""
+    if git merge --help | grep -q -e allow-unrelated-histories; then
+        unrelated_histories_flag="--allow-unrelated-histories "
+    fi
+    readonly unrelated_histories_flag
+
+    git fetch "$extractdir" "upstream-$name:upstream-$name"
+    git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name"
+    git read-tree -u --prefix="$subtree/" "upstream-$name"
+fi
+git commit --no-edit
+git branch -d "upstream-$name"