Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Kitware
GitHub Repository: Kitware/CMake
Path: blob/master/Utilities/Scripts/update-third-party.bash
4998 views
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
# https://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
set -e
18
19
# Disable noise from `pre-commit` when no configuration is present on the
20
# imported tree.
21
export PRE_COMMIT_ALLOW_NO_CONFIG=1
22
23
########################################################################
24
# Script for updating third party packages.
25
#
26
# This script should be sourced in a project-specific script which sets
27
# the following variables:
28
#
29
# name
30
# The name of the project.
31
# ownership
32
# A git author name/email for the commits.
33
# subtree
34
# The location of the third-party package within the main source
35
# tree.
36
# repo
37
# The git repository to use as upstream.
38
# tag
39
# The tag, branch or commit hash to use for upstream.
40
# shortlog
41
# Optional. Set to 'true' to get a shortlog in the commit message.
42
# exact_tree_match
43
# Optional. Set to 'false' to disable tree-object based matching for
44
# previous import commit (required for projects that allow modifying
45
# imported trees). In such cases, log-based searching is performed.
46
#
47
# Additionally, an "extract_source" function must be defined. It will be
48
# run within the checkout of the project on the requested tag. It should
49
# should place the desired tree into $extractdir/$name-reduced. This
50
# directory will be used as the newest commit for the project.
51
#
52
# For convenience, the function may use the "git_archive" function which
53
# does a standard "git archive" extraction using the (optional) "paths"
54
# variable to only extract a subset of the source tree.
55
#
56
# Dependencies
57
#
58
# To update third party packages from git repositories with submodule,
59
# you will need to install the "git-archive-all" Python package with
60
#
61
# pip install git-archive-all
62
#
63
# or install it from https://github.com/Kentzo/git-archive-all.
64
#
65
# This package installs a script named "git-archive-all" where pip
66
# installs executables. If you run pip under your user privileges (i.e.,
67
# not using "sudo"), this location may be $HOME/.local/bin. Make sure
68
# that directory is in your path so that git can find the
69
# "git-archive-all" script.
70
#
71
########################################################################
72
73
########################################################################
74
# Utility functions
75
########################################################################
76
git_archive () {
77
git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \
78
tar -C "$extractdir" -x
79
}
80
81
confirm_archive_all_exists () {
82
which git-archive-all || die "git requires an archive-all command. Please run 'pip install git-archive-all'"
83
}
84
85
git_archive_all () {
86
confirm_archive_all_exists
87
local tmptarball="temp.tar"
88
git archive-all --prefix="" "$tmptarball"
89
mkdir -p "$extractdir/$name-reduced"
90
tar -C "$extractdir/$name-reduced" -xf "$tmptarball" $paths
91
rm -f "$tmptarball"
92
}
93
94
disable_custom_gitattributes() {
95
pushd "${extractdir}/${name}-reduced"
96
# Git does not allow custom attributes in a subdirectory where we
97
# are about to merge the `.gitattributes` file, so disable them.
98
sed -i.bak -e '/^\[attr\]/ {s/^/#/;}' .gitattributes
99
rm .gitattributes.bak
100
popd
101
}
102
103
die () {
104
echo >&2 "$@"
105
exit 1
106
}
107
108
warn () {
109
echo >&2 "warning:" "$@"
110
}
111
112
readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
113
readonly basehash_regex="$name $regex_date ([0-9a-f]*)"
114
toplevel_dir="$( git rev-parse --show-toplevel )"
115
readonly toplevel_dir
116
117
cd "$toplevel_dir"
118
119
########################################################################
120
# Sanity checking
121
########################################################################
122
[ -n "$name" ] || \
123
die "'name' is empty"
124
[ -n "$ownership" ] || \
125
die "'ownership' is empty"
126
[ -n "$subtree" ] || \
127
die "'subtree' is empty"
128
[ -n "$repo" ] || \
129
die "'repo' is empty"
130
[ -n "$tag" ] || \
131
die "'tag' is empty"
132
[ -n "$exact_tree_match" ] || \
133
exact_tree_match=true
134
135
# Check for an empty destination directory on disk. By checking on disk and
136
# not in the repo it allows a library to be freshly re-initialized in a single
137
# commit rather than first deleting the old copy in one commit and adding the
138
# new copy in a separate commit.
139
if [ ! -d "$( git rev-parse --show-toplevel )/$subtree" ]; then
140
basehash=""
141
elif $exact_tree_match; then
142
# Find the tree object for the current subtree.
143
current_tree="$( git rev-parse "HEAD:$subtree" )"
144
# Search history for a commit whose subtree matches this tree object.
145
basehash=""
146
# Limit candidate commits to those with expected import commit messages for efficiency.
147
for commit in $( git rev-list --author="$ownership" --grep="$basehash_regex" HEAD ); do
148
imported_tree="$( git rev-parse "$commit^{tree}" )"
149
# Verify the imported tree is what is currently imported. If so, we
150
# have found the desired import commit.
151
if [ "$imported_tree" = "$current_tree" ] && [ -n "$imported_tree" ]; then
152
basehash="$commit"
153
break
154
fi
155
done
156
if [ -z "$basehash" ]; then
157
die "No previous import commit found with matching tree object for $subtree. (exact_tree_match enabled)"
158
fi
159
else
160
basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )"
161
fi
162
readonly basehash
163
164
[ -n "$basehash" ] || \
165
warn "'basehash' is empty; performing initial import"
166
readonly do_shortlog="${shortlog-false}"
167
168
readonly workdir="$PWD/work"
169
readonly upstreamdir="$workdir/upstream"
170
readonly extractdir="$workdir/extract"
171
172
[ -d "$workdir" ] && \
173
die "error: workdir '$workdir' already exists"
174
175
trap "rm -rf '$workdir'" EXIT
176
177
# Skip LFS downloading; imports should not need LFS data.
178
export GIT_LFS_SKIP_SMUDGE=1
179
180
# Get upstream
181
git clone --recursive "$repo" "$upstreamdir"
182
183
if [ -n "$basehash" ]; then
184
# Remove old worktrees
185
git worktree prune
186
# Use the existing package's history
187
git worktree add "$extractdir" "$basehash"
188
# Clear out the working tree
189
pushd "$extractdir"
190
git ls-files -z --recurse-submodules | xargs -0 rm -v
191
find . -type d -empty -delete
192
popd
193
else
194
# Create a repo to hold this package's history
195
mkdir -p "$extractdir"
196
git -C "$extractdir" init
197
fi
198
199
# Extract the subset of upstream we care about
200
pushd "$upstreamdir"
201
git checkout "$tag"
202
git submodule sync --recursive
203
git submodule update --recursive --init
204
upstream_hash="$( git rev-parse HEAD )"
205
readonly upstream_hash
206
upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )"
207
readonly upstream_hash_short
208
upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )"
209
readonly upstream_datetime
210
upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )"
211
readonly upstream_date
212
if $do_shortlog && [ -n "$basehash" ]; then
213
upstream_old_short="$( git -C "$toplevel_dir" cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p;}' | grep -E '^[0-9a-f]+$' )"
214
readonly upstream_old_short
215
216
commit_shortlog="
217
218
Upstream Shortlog
219
-----------------
220
221
$( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )"
222
else
223
commit_shortlog=""
224
fi
225
readonly commit_shortlog
226
extract_source || \
227
die "failed to extract source"
228
popd
229
230
[ -d "$extractdir/$name-reduced" ] || \
231
die "expected directory to extract does not exist"
232
readonly commit_summary="$name $upstream_date ($upstream_hash_short)"
233
234
# Commit the subset
235
pushd "$extractdir"
236
mv -v "$name-reduced/"* .
237
rmdir "$name-reduced/"
238
git add -A .
239
git commit --no-verify --no-post-rewrite --author="$ownership" --date="$upstream_datetime" -F - <<-EOF
240
$commit_summary
241
242
Code extracted from:
243
244
$repo
245
246
at commit $upstream_hash ($tag).$commit_shortlog
247
EOF
248
git branch -f "upstream-$name"
249
popd
250
251
# Merge the subset into this repository
252
if [ -n "$basehash" ]; then
253
git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name"
254
else
255
# Note: on Windows 'git merge --help' will open a browser, and the check
256
# will fail, so use the flag by default. Apple opens an editor instead;
257
# assume the flag is understood there too.
258
unrelated_histories_flag=""
259
if git --version | grep -q -E 'windows|Apple'; then
260
unrelated_histories_flag="--allow-unrelated-histories"
261
elif git merge --help | grep -q -e allow-unrelated-histories; then
262
unrelated_histories_flag="--allow-unrelated-histories"
263
fi
264
readonly unrelated_histories_flag
265
266
git fetch "$extractdir" "+upstream-$name:upstream-$name"
267
git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name"
268
git read-tree -u --prefix="$subtree/" "upstream-$name"
269
fi
270
git commit --no-edit
271
git branch -d "upstream-$name"
272
273