Path: blob/master/Utilities/Scripts/update-third-party.bash
4998 views
#=============================================================================1# Copyright 2015-2016 Kitware, Inc.2#3# Licensed under the Apache License, Version 2.0 (the "License");4# you may not use this file except in compliance with the License.5# You may obtain a copy of the License at6#7# https://www.apache.org/licenses/LICENSE-2.08#9# Unless required by applicable law or agreed to in writing, software10# distributed under the License is distributed on an "AS IS" BASIS,11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12# See the License for the specific language governing permissions and13# limitations under the License.14#=============================================================================1516set -e1718# Disable noise from `pre-commit` when no configuration is present on the19# imported tree.20export PRE_COMMIT_ALLOW_NO_CONFIG=12122########################################################################23# Script for updating third party packages.24#25# This script should be sourced in a project-specific script which sets26# the following variables:27#28# name29# The name of the project.30# ownership31# A git author name/email for the commits.32# subtree33# The location of the third-party package within the main source34# tree.35# repo36# The git repository to use as upstream.37# tag38# The tag, branch or commit hash to use for upstream.39# shortlog40# Optional. Set to 'true' to get a shortlog in the commit message.41# exact_tree_match42# Optional. Set to 'false' to disable tree-object based matching for43# previous import commit (required for projects that allow modifying44# imported trees). In such cases, log-based searching is performed.45#46# Additionally, an "extract_source" function must be defined. It will be47# run within the checkout of the project on the requested tag. It should48# should place the desired tree into $extractdir/$name-reduced. This49# directory will be used as the newest commit for the project.50#51# For convenience, the function may use the "git_archive" function which52# does a standard "git archive" extraction using the (optional) "paths"53# variable to only extract a subset of the source tree.54#55# Dependencies56#57# To update third party packages from git repositories with submodule,58# you will need to install the "git-archive-all" Python package with59#60# pip install git-archive-all61#62# or install it from https://github.com/Kentzo/git-archive-all.63#64# This package installs a script named "git-archive-all" where pip65# installs executables. If you run pip under your user privileges (i.e.,66# not using "sudo"), this location may be $HOME/.local/bin. Make sure67# that directory is in your path so that git can find the68# "git-archive-all" script.69#70########################################################################7172########################################################################73# Utility functions74########################################################################75git_archive () {76git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \77tar -C "$extractdir" -x78}7980confirm_archive_all_exists () {81which git-archive-all || die "git requires an archive-all command. Please run 'pip install git-archive-all'"82}8384git_archive_all () {85confirm_archive_all_exists86local tmptarball="temp.tar"87git archive-all --prefix="" "$tmptarball"88mkdir -p "$extractdir/$name-reduced"89tar -C "$extractdir/$name-reduced" -xf "$tmptarball" $paths90rm -f "$tmptarball"91}9293disable_custom_gitattributes() {94pushd "${extractdir}/${name}-reduced"95# Git does not allow custom attributes in a subdirectory where we96# are about to merge the `.gitattributes` file, so disable them.97sed -i.bak -e '/^\[attr\]/ {s/^/#/;}' .gitattributes98rm .gitattributes.bak99popd100}101102die () {103echo >&2 "$@"104exit 1105}106107warn () {108echo >&2 "warning:" "$@"109}110111readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'112readonly basehash_regex="$name $regex_date ([0-9a-f]*)"113toplevel_dir="$( git rev-parse --show-toplevel )"114readonly toplevel_dir115116cd "$toplevel_dir"117118########################################################################119# Sanity checking120########################################################################121[ -n "$name" ] || \122die "'name' is empty"123[ -n "$ownership" ] || \124die "'ownership' is empty"125[ -n "$subtree" ] || \126die "'subtree' is empty"127[ -n "$repo" ] || \128die "'repo' is empty"129[ -n "$tag" ] || \130die "'tag' is empty"131[ -n "$exact_tree_match" ] || \132exact_tree_match=true133134# Check for an empty destination directory on disk. By checking on disk and135# not in the repo it allows a library to be freshly re-initialized in a single136# commit rather than first deleting the old copy in one commit and adding the137# new copy in a separate commit.138if [ ! -d "$( git rev-parse --show-toplevel )/$subtree" ]; then139basehash=""140elif $exact_tree_match; then141# Find the tree object for the current subtree.142current_tree="$( git rev-parse "HEAD:$subtree" )"143# Search history for a commit whose subtree matches this tree object.144basehash=""145# Limit candidate commits to those with expected import commit messages for efficiency.146for commit in $( git rev-list --author="$ownership" --grep="$basehash_regex" HEAD ); do147imported_tree="$( git rev-parse "$commit^{tree}" )"148# Verify the imported tree is what is currently imported. If so, we149# have found the desired import commit.150if [ "$imported_tree" = "$current_tree" ] && [ -n "$imported_tree" ]; then151basehash="$commit"152break153fi154done155if [ -z "$basehash" ]; then156die "No previous import commit found with matching tree object for $subtree. (exact_tree_match enabled)"157fi158else159basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )"160fi161readonly basehash162163[ -n "$basehash" ] || \164warn "'basehash' is empty; performing initial import"165readonly do_shortlog="${shortlog-false}"166167readonly workdir="$PWD/work"168readonly upstreamdir="$workdir/upstream"169readonly extractdir="$workdir/extract"170171[ -d "$workdir" ] && \172die "error: workdir '$workdir' already exists"173174trap "rm -rf '$workdir'" EXIT175176# Skip LFS downloading; imports should not need LFS data.177export GIT_LFS_SKIP_SMUDGE=1178179# Get upstream180git clone --recursive "$repo" "$upstreamdir"181182if [ -n "$basehash" ]; then183# Remove old worktrees184git worktree prune185# Use the existing package's history186git worktree add "$extractdir" "$basehash"187# Clear out the working tree188pushd "$extractdir"189git ls-files -z --recurse-submodules | xargs -0 rm -v190find . -type d -empty -delete191popd192else193# Create a repo to hold this package's history194mkdir -p "$extractdir"195git -C "$extractdir" init196fi197198# Extract the subset of upstream we care about199pushd "$upstreamdir"200git checkout "$tag"201git submodule sync --recursive202git submodule update --recursive --init203upstream_hash="$( git rev-parse HEAD )"204readonly upstream_hash205upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )"206readonly upstream_hash_short207upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )"208readonly upstream_datetime209upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )"210readonly upstream_date211if $do_shortlog && [ -n "$basehash" ]; then212upstream_old_short="$( git -C "$toplevel_dir" cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p;}' | grep -E '^[0-9a-f]+$' )"213readonly upstream_old_short214215commit_shortlog="216217Upstream Shortlog218-----------------219220$( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )"221else222commit_shortlog=""223fi224readonly commit_shortlog225extract_source || \226die "failed to extract source"227popd228229[ -d "$extractdir/$name-reduced" ] || \230die "expected directory to extract does not exist"231readonly commit_summary="$name $upstream_date ($upstream_hash_short)"232233# Commit the subset234pushd "$extractdir"235mv -v "$name-reduced/"* .236rmdir "$name-reduced/"237git add -A .238git commit --no-verify --no-post-rewrite --author="$ownership" --date="$upstream_datetime" -F - <<-EOF239$commit_summary240241Code extracted from:242243$repo244245at commit $upstream_hash ($tag).$commit_shortlog246EOF247git branch -f "upstream-$name"248popd249250# Merge the subset into this repository251if [ -n "$basehash" ]; then252git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name"253else254# Note: on Windows 'git merge --help' will open a browser, and the check255# will fail, so use the flag by default. Apple opens an editor instead;256# assume the flag is understood there too.257unrelated_histories_flag=""258if git --version | grep -q -E 'windows|Apple'; then259unrelated_histories_flag="--allow-unrelated-histories"260elif git merge --help | grep -q -e allow-unrelated-histories; then261unrelated_histories_flag="--allow-unrelated-histories"262fi263readonly unrelated_histories_flag264265git fetch "$extractdir" "+upstream-$name:upstream-$name"266git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name"267git read-tree -u --prefix="$subtree/" "upstream-$name"268fi269git commit --no-edit270git branch -d "upstream-$name"271272273