| #============================================================================= | |
| # 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 | |
| } | |
| disable_custom_gitattributes() { | |
| pushd "${extractdir}/${name}-reduced" | |
| # Git does not allow custom attributes in a subdirectory where we | |
| # are about to merge the `.gitattributes` file, so disable them. | |
| sed -i '/^\[attr\]/ {s/^/#/}' .gitattributes | |
| popd | |
| } | |
| 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]*)" | |
| ######################################################################## | |
| # 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" | |
| # Check for an empty destination directory on disk. By checking on disk and | |
| # not in the repo it allows a library to be freshly re-inialized in a single | |
| # commit rather than first deleting the old copy in one commit and adding the | |
| # new copy in a separate commit. | |
| if [ ! -d "$(git rev-parse --show-toplevel)/$subtree" ]; then | |
| readonly basehash="" | |
| else | |
| readonly basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )" | |
| fi | |
| readonly upstream_old_short="$( git cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p}' | egrep '^[0-9a-f]+$' )" | |
| [ -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 | |
| # Remove old worktrees | |
| git worktree prune | |
| # 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 | |
| # Note: on Windows 'git merge --help' will open a browser, and the check | |
| # will fail, so use the flag by default. | |
| unrelated_histories_flag="" | |
| if git --version | grep -q windows; then | |
| unrelated_histories_flag="--allow-unrelated-histories " | |
| elif 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" |