1#============================================================================= 2# Copyright 2015-2016 Kitware, Inc. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15#============================================================================= 16 17######################################################################## 18# Script for updating third party packages. 19# 20# This script should be sourced in a project-specific script which sets 21# the following variables: 22# 23# name 24# The name of the project. 25# ownership 26# A git author name/email for the commits. 27# subtree 28# The location of the thirdparty package within the main source 29# tree. 30# repo 31# The git repository to use as upstream. 32# tag 33# The tag, branch or commit hash to use for upstream. 34# shortlog 35# Optional. Set to 'true' to get a shortlog in the commit message. 36# 37# Additionally, an "extract_source" function must be defined. It will be 38# run within the checkout of the project on the requested tag. It should 39# should place the desired tree into $extractdir/$name-reduced. This 40# directory will be used as the newest commit for the project. 41# 42# For convenience, the function may use the "git_archive" function which 43# does a standard "git archive" extraction using the (optional) "paths" 44# variable to only extract a subset of the source tree. 45######################################################################## 46 47######################################################################## 48# Utility functions 49######################################################################## 50git_archive () { 51 git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \ 52 tar -C "$extractdir" -x 53} 54 55disable_custom_gitattributes() { 56 pushd "${extractdir}/${name}-reduced" 57 # Git does not allow custom attributes in a subdirectory where we 58 # are about to merge the `.gitattributes` file, so disable them. 59 sed -i '/^\[attr\]/ {s/^/#/;}' .gitattributes 60 popd 61} 62 63die () { 64 echo >&2 "$@" 65 exit 1 66} 67 68warn () { 69 echo >&2 "warning: $@" 70} 71 72readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]' 73readonly basehash_regex="$name $regex_date ([0-9a-f]*)" 74readonly toplevel_dir="$( git rev-parse --show-toplevel )" 75 76cd "$toplevel_dir" 77 78######################################################################## 79# Sanity checking 80######################################################################## 81[ -n "$name" ] || \ 82 die "'name' is empty" 83[ -n "$ownership" ] || \ 84 die "'ownership' is empty" 85[ -n "$subtree" ] || \ 86 die "'subtree' is empty" 87[ -n "$repo" ] || \ 88 die "'repo' is empty" 89[ -n "$tag" ] || \ 90 die "'tag' is empty" 91 92# Check for an empty destination directory on disk. By checking on disk and 93# not in the repo it allows a library to be freshly re-inialized in a single 94# commit rather than first deleting the old copy in one commit and adding the 95# new copy in a seperate commit. 96if [ ! -d "$(git rev-parse --show-toplevel)/$subtree" ]; then 97 readonly basehash="" 98else 99 readonly basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )" 100fi 101readonly upstream_old_short="$( git cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p;}' | egrep '^[0-9a-f]+$' )" 102 103[ -n "$basehash" ] || \ 104 warn "'basehash' is empty; performing initial import" 105readonly do_shortlog="${shortlog-false}" 106 107readonly workdir="$PWD/work" 108readonly upstreamdir="$workdir/upstream" 109readonly extractdir="$workdir/extract" 110 111[ -d "$workdir" ] && \ 112 die "error: workdir '$workdir' already exists" 113 114trap "rm -rf '$workdir'" EXIT 115 116# Get upstream 117git clone "$repo" "$upstreamdir" 118 119if [ -n "$basehash" ]; then 120 # Remove old worktrees 121 git worktree prune 122 # Use the existing package's history 123 git worktree add "$extractdir" "$basehash" 124 # Clear out the working tree 125 pushd "$extractdir" 126 git ls-files | xargs rm -v 127 find . -type d -empty -delete 128 popd 129else 130 # Create a repo to hold this package's history 131 mkdir -p "$extractdir" 132 git -C "$extractdir" init 133fi 134 135# Extract the subset of upstream we care about 136pushd "$upstreamdir" 137git checkout "$tag" 138readonly upstream_hash="$( git rev-parse HEAD )" 139readonly upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )" 140readonly upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )" 141readonly upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )" 142if $do_shortlog && [ -n "$basehash" ]; then 143 readonly commit_shortlog=" 144 145Upstream Shortlog 146----------------- 147 148$( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )" 149else 150 readonly commit_shortlog="" 151fi 152extract_source || \ 153 die "failed to extract source" 154popd 155 156[ -d "$extractdir/$name-reduced" ] || \ 157 die "expected directory to extract does not exist" 158readonly commit_summary="$name $upstream_date ($upstream_hash_short)" 159 160# Commit the subset 161pushd "$extractdir" 162mv -v "$name-reduced/"* . 163rmdir "$name-reduced/" 164git add -A . 165git commit -n --author="$ownership" --date="$upstream_datetime" -F - <<-EOF 166$commit_summary 167 168Code extracted from: 169 170 $repo 171 172at commit $upstream_hash ($tag).$commit_shortlog 173EOF 174git branch -f "upstream-$name" 175popd 176 177# Merge the subset into this repository 178if [ -n "$basehash" ]; then 179 git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name" 180else 181 # Note: on Windows 'git merge --help' will open a browser, and the check 182 # will fail, so use the flag by default. 183 unrelated_histories_flag="" 184 if git --version | grep -q windows; then 185 unrelated_histories_flag="--allow-unrelated-histories " 186 elif git merge --help | grep -q -e allow-unrelated-histories; then 187 unrelated_histories_flag="--allow-unrelated-histories " 188 fi 189 readonly unrelated_histories_flag 190 191 git fetch "$extractdir" "+upstream-$name:upstream-$name" 192 git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name" 193 git read-tree -u --prefix="$subtree/" "upstream-$name" 194fi 195git commit --no-edit 196git branch -d "upstream-$name" 197