update-third-party.bash 7.6 KB

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