update-third-party.bash 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  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 thirdparty 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. ########################################################################
  46. # Utility functions
  47. ########################################################################
  48. git_archive () {
  49. git archive --worktree-attributes --prefix="$name-reduced/" HEAD -- $paths | \
  50. tar -C "$extractdir" -x
  51. }
  52. disable_custom_gitattributes() {
  53. pushd "${extractdir}/${name}-reduced"
  54. # Git does not allow custom attributes in a subdirectory where we
  55. # are about to merge the `.gitattributes` file, so disable them.
  56. sed -i '/^\[attr\]/ {s/^/#/}' .gitattributes
  57. popd
  58. }
  59. die () {
  60. echo >&2 "$@"
  61. exit 1
  62. }
  63. warn () {
  64. echo >&2 "warning: $@"
  65. }
  66. readonly regex_date='20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
  67. readonly basehash_regex="$name $regex_date ([0-9a-f]*)"
  68. ########################################################################
  69. # Sanity checking
  70. ########################################################################
  71. [ -n "$name" ] || \
  72. die "'name' is empty"
  73. [ -n "$ownership" ] || \
  74. die "'ownership' is empty"
  75. [ -n "$subtree" ] || \
  76. die "'subtree' is empty"
  77. [ -n "$repo" ] || \
  78. die "'repo' is empty"
  79. [ -n "$tag" ] || \
  80. die "'tag' is empty"
  81. # Check for an empty destination directory on disk. By checking on disk and
  82. # not in the repo it allows a library to be freshly re-inialized in a single
  83. # commit rather than first deleting the old copy in one commit and adding the
  84. # new copy in a seperate commit.
  85. if [ ! -d "$(git rev-parse --show-toplevel)/$subtree" ]; then
  86. readonly basehash=""
  87. else
  88. readonly basehash="$( git rev-list --author="$ownership" --grep="$basehash_regex" -n 1 HEAD )"
  89. fi
  90. readonly upstream_old_short="$( git cat-file commit "$basehash" | sed -n '/'"$basehash_regex"'/ {s/.*(//;s/)//;p}' | egrep '^[0-9a-f]+$' )"
  91. [ -n "$basehash" ] || \
  92. warn "'basehash' is empty; performing initial import"
  93. readonly do_shortlog="${shortlog-false}"
  94. readonly workdir="$PWD/work"
  95. readonly upstreamdir="$workdir/upstream"
  96. readonly extractdir="$workdir/extract"
  97. [ -d "$workdir" ] && \
  98. die "error: workdir '$workdir' already exists"
  99. trap "rm -rf '$workdir'" EXIT
  100. # Get upstream
  101. git clone "$repo" "$upstreamdir"
  102. if [ -n "$basehash" ]; then
  103. # Remove old worktrees
  104. git worktree prune
  105. # Use the existing package's history
  106. git worktree add "$extractdir" "$basehash"
  107. # Clear out the working tree
  108. pushd "$extractdir"
  109. git ls-files | xargs rm -v
  110. find . -type d -empty -delete
  111. popd
  112. else
  113. # Create a repo to hold this package's history
  114. mkdir -p "$extractdir"
  115. git -C "$extractdir" init
  116. fi
  117. # Extract the subset of upstream we care about
  118. pushd "$upstreamdir"
  119. git checkout "$tag"
  120. readonly upstream_hash="$( git rev-parse HEAD )"
  121. readonly upstream_hash_short="$( git rev-parse --short=8 "$upstream_hash" )"
  122. readonly upstream_datetime="$( git rev-list "$upstream_hash" --format='%ci' -n 1 | grep -e "^$regex_date" )"
  123. readonly upstream_date="$( echo "$upstream_datetime" | grep -o -e "$regex_date" )"
  124. if $do_shortlog && [ -n "$basehash" ]; then
  125. readonly commit_shortlog="
  126. Upstream Shortlog
  127. -----------------
  128. $( git shortlog --no-merges --abbrev=8 --format='%h %s' "$upstream_old_short".."$upstream_hash" )"
  129. else
  130. readonly commit_shortlog=""
  131. fi
  132. extract_source || \
  133. die "failed to extract source"
  134. popd
  135. [ -d "$extractdir/$name-reduced" ] || \
  136. die "expected directory to extract does not exist"
  137. readonly commit_summary="$name $upstream_date ($upstream_hash_short)"
  138. # Commit the subset
  139. pushd "$extractdir"
  140. mv -v "$name-reduced/"* .
  141. rmdir "$name-reduced/"
  142. git add -A .
  143. git commit -n --author="$ownership" --date="$upstream_datetime" -F - <<-EOF
  144. $commit_summary
  145. Code extracted from:
  146. $repo
  147. at commit $upstream_hash ($tag).$commit_shortlog
  148. EOF
  149. git branch -f "upstream-$name"
  150. popd
  151. # Merge the subset into this repository
  152. if [ -n "$basehash" ]; then
  153. git merge --log -s recursive "-Xsubtree=$subtree/" --no-commit "upstream-$name"
  154. else
  155. # Note: on Windows 'git merge --help' will open a browser, and the check
  156. # will fail, so use the flag by default.
  157. unrelated_histories_flag=""
  158. if git --version | grep -q windows; then
  159. unrelated_histories_flag="--allow-unrelated-histories "
  160. elif git merge --help | grep -q -e allow-unrelated-histories; then
  161. unrelated_histories_flag="--allow-unrelated-histories "
  162. fi
  163. readonly unrelated_histories_flag
  164. git fetch "$extractdir" "+upstream-$name:upstream-$name"
  165. git merge --log -s ours --no-commit $unrelated_histories_flag "upstream-$name"
  166. git read-tree -u --prefix="$subtree/" "upstream-$name"
  167. fi
  168. git commit --no-edit
  169. git branch -d "upstream-$name"