git 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #!/usr/bin/env bash
  2. # NOTE: if the output of this backend has to change (e.g. we change what gets
  3. # included in the archive (e.g. LFS), or we change the format of the archive
  4. # (e.g. tar options, compression ratio or method)), we MUST update the format
  5. # version in the variable BR_FMT_VERSION_git, in package/pkg-download.mk.
  6. # We want to catch any unexpected failure
  7. set -e
  8. # Download helper for git, to be called from the download wrapper script
  9. #
  10. # Options:
  11. # -q Be quiet.
  12. # -r Clone and archive sub-modules.
  13. # -o FILE Generate archive in FILE.
  14. # -u URI Clone from repository at URI.
  15. # -c CSET Use changeset CSET.
  16. # -n NAME Use basename NAME.
  17. #
  18. # Environment:
  19. # GIT : the git command to call
  20. # shellcheck disable=SC1090 # Only provides mk_tar_gz()
  21. # shellcheck disable=SC1091
  22. . "${0%/*}/helpers"
  23. # Save our path and options in case we need to call ourselves again
  24. myname="${0}"
  25. declare -a OPTS=("${@}")
  26. # This function is called when an error occurs. Its job is to attempt a
  27. # clone from scratch (only once!) in case the git tree is borked, or in
  28. # case an unexpected and unsupported situation arises with submodules
  29. # or uncommitted stuff (e.g. if the user manually mucked around in the
  30. # git cache).
  31. _on_error() {
  32. local ret=${?}
  33. printf "Detected a corrupted git cache.\n" >&2
  34. if ${BR_GIT_BACKEND_FIRST_FAULT:-false}; then
  35. printf "This is the second time in a row; bailing out\n" >&2
  36. exit ${ret}
  37. fi
  38. export BR_GIT_BACKEND_FIRST_FAULT=true
  39. printf "Removing it and starting afresh.\n" >&2
  40. popd >/dev/null
  41. rm -rf "${git_cache}"
  42. exec "${myname}" "${OPTS[@]}" || exit ${ret}
  43. }
  44. quiet=
  45. large_file=0
  46. recurse=0
  47. while getopts "${BR_BACKEND_DL_GETOPTS}" OPT; do
  48. case "${OPT}" in
  49. q) quiet=-q; exec >/dev/null;;
  50. l) large_file=1;;
  51. r) recurse=1;;
  52. o) output="${OPTARG}";;
  53. u) uri="${OPTARG}";;
  54. c) cset="${OPTARG}";;
  55. d) dl_dir="${OPTARG}";;
  56. n) basename="${OPTARG}";;
  57. :) printf "option '%s' expects a mandatory argument\n" "${OPTARG}"; exit 1;;
  58. \?) printf "unknown option '%s'\n" "${OPTARG}" >&2; exit 1;;
  59. esac
  60. done
  61. shift $((OPTIND-1)) # Get rid of our options
  62. # Create and cd into the directory that will contain the local git cache
  63. git_cache="${dl_dir}/git"
  64. mkdir -p "${git_cache}"
  65. pushd "${git_cache}" >/dev/null
  66. # Any error now should try to recover
  67. trap _on_error ERR
  68. set -E
  69. # Caller needs to single-quote its arguments to prevent them from
  70. # being expanded a second time (in case there are spaces in them)
  71. _git() {
  72. if [ -z "${quiet}" ]; then
  73. printf '%s ' GIT_DIR="${git_cache}/.git" "${GIT}" "${@}"; printf '\n'
  74. fi
  75. _plain_git "$@"
  76. }
  77. # Note: please keep command below aligned with what is printed above
  78. _plain_git() {
  79. # shellcheck disable=SC2086 # We want word-splitting for GIT
  80. # shellcheck disable=SC2294
  81. eval GIT_DIR="${git_cache}/.git" ${GIT} "${@}"
  82. }
  83. # Create a warning file, that the user should not use the git cache.
  84. # It's ours. Our precious.
  85. cat <<-_EOF_ >"${dl_dir}/git.readme"
  86. IMPORTANT NOTE!
  87. The git tree located in this directory is for the exclusive use
  88. by Buildroot, which uses it as a local cache to reduce bandwidth
  89. usage.
  90. Buildroot *will* trash any changes in that tree whenever it needs
  91. to use it. Buildroot may even remove it in case it detects the
  92. repository may have been damaged or corrupted.
  93. Do *not* work in that directory; your changes will eventually get
  94. lost. Do *not* even use it as a remote, or as the source for new
  95. worktrees; your commits will eventually get lost.
  96. _EOF_
  97. # Initialise a repository in the git cache. If the repository already
  98. # existed, this is a noop, unless the repository was broken, in which
  99. # case this magically restores it to working conditions. In the latter
  100. # case, we might be missing blobs, but that's not a problem: we'll
  101. # fetch what we need later anyway.
  102. #
  103. # We can still go through the wrapper, because 'init' does not use the
  104. # path pointed to by GIT_DIR, but really uses the directory passed as
  105. # argument.
  106. _git init .
  107. # Ensure the repo has an origin (in case a previous run was killed).
  108. if ! _plain_git remote |grep -q -E '^origin$'; then
  109. _git remote add origin "'${uri}'"
  110. fi
  111. _git remote set-url origin "'${uri}'"
  112. printf "Fetching all references\n"
  113. _git fetch "${@}" origin
  114. _git fetch "${@}" origin -t -f
  115. # Try to get the special refs exposed by some forges (pull-requests for
  116. # github, changes for gerrit...). There is no easy way to know whether
  117. # the cset the user passed us is such a special ref or a tag or a sha1
  118. # or whatever else. We'll eventually fail at checking out that cset,
  119. # below, if there is an issue anyway. Since most of the cset we're gonna
  120. # have to clone are not such special refs, consign the output to oblivion
  121. # so as not to alarm unsuspecting users, but still trace it as a warning.
  122. if ! _git fetch "${@}" origin "'${cset}:${cset}'" >/dev/null 2>&1; then
  123. printf "Could not fetch special ref '%s'; assuming it is not special.\n" "${cset}"
  124. fi
  125. # Check that the changeset does exist. If it does not, re-cloning from
  126. # scratch won't help, so we don't want to trash the repository for a
  127. # missing commit. We just exit without going through the ERR trap.
  128. if ! _git rev-parse --quiet --verify "'${cset}^{commit}'" >/dev/null 2>&1; then
  129. printf "Commit '%s' does not exist in this repository.\n" "${cset}"
  130. exit 1
  131. fi
  132. # The new cset we want to checkout might have different submodules, or
  133. # have sub-dirs converted to/from a submodule. So we would need to
  134. # deregister _current_ submodules before we checkout.
  135. #
  136. # Using "git submodule deinit --all" would remove all the files for
  137. # all submodules, including the corresponding .git files or directories.
  138. # However, it was only introduced with git-1.8.3, which is too recent
  139. # for some enterprise-grade distros.
  140. #
  141. # So, we fall-back to just removing all submodules directories. We do
  142. # not need to be recursive, as removing a submodule will de-facto remove
  143. # its own submodules.
  144. #
  145. # For recent git versions, the repository for submodules is stored
  146. # inside the repository of the super repository, so the following will
  147. # only remove the working copies of submodules, effectively caching the
  148. # submodules.
  149. #
  150. # For older versions however, the repository is stored in the .git/ of
  151. # the submodule directory, so the following will effectively remove the
  152. # the working copy as well as the repository, which means submodules
  153. # will not be cached for older versions.
  154. #
  155. # shellcheck disable=SC2016 # Will be expanded by git-foreach
  156. cmd='printf "Deregistering submodule \"%s\"\n" "${path}" && cd .. && rm -rf "${path##*/}"'
  157. _git submodule --quiet foreach "'${cmd}'"
  158. # Checkout the required changeset, so that we can update the required
  159. # submodules.
  160. _git checkout -f -q "'${cset}'"
  161. # Get rid of now-untracked directories (in case a git operation was
  162. # interrupted in a previous run, or to get rid of empty directories
  163. # that were parents of submodules removed above).
  164. _git clean -ffdx
  165. # Get date of commit to generate a reproducible archive.
  166. # %ci is ISO 8601, so it's fully qualified, with TZ and all.
  167. date="$( _plain_git log -1 --pretty=format:%ci )"
  168. # There might be submodules, so fetch them.
  169. if [ ${recurse} -eq 1 ]; then
  170. _git submodule update --init --recursive
  171. # Older versions of git will store the absolute path of the git tree
  172. # in the .git of submodules, while newer versions just use relative
  173. # paths. Detect and fix the older variants to use relative paths, so
  174. # that the archives are reproducible across a wider range of git
  175. # versions. However, we can't do that if git is too old and uses
  176. # full repositories for submodules.
  177. # shellcheck disable=SC2016 # Will be expanded by git-foreach
  178. cmd='printf "%s\n" "${path}/"'
  179. for module_dir in $( _plain_git submodule --quiet foreach "'${cmd}'" ); do
  180. [ -f "${module_dir}/.git" ] || continue
  181. relative_dir="$( sed -r -e 's,/+,/,g; s,[^/]+/,../,g' <<<"${module_dir}" )"
  182. sed -r -i -e "s:^gitdir\: $(pwd)/:gitdir\: ${relative_dir}:" "${module_dir}/.git"
  183. done
  184. fi
  185. # If there are large files then fetch them.
  186. if [ ${large_file} -eq 1 ]; then
  187. _git lfs install --local
  188. _git lfs fetch
  189. _git lfs checkout
  190. # If there are also submodules, recurse into them,
  191. # shellcheck disable=SC2086 # We want word-splitting for GIT
  192. if [ ${recurse} -eq 1 ]; then
  193. _git submodule foreach --recursive ${GIT} lfs install --local
  194. _git submodule foreach --recursive ${GIT} lfs fetch
  195. _git submodule foreach --recursive ${GIT} lfs checkout
  196. fi
  197. fi
  198. # Find files that are affected by the export-subst git-attribute.
  199. # There might be a .gitattribute at the root of the repository, as well
  200. # as in any arbitrary sub-directory, whether from the master repository
  201. # or a submodule.
  202. # "git check-attr -z" outputs results using \0 as separator for everything,
  203. # so there is no difference between field or records (but there is a
  204. # trailing \0):
  205. # path_1\0attr_name\0attr_state\0path_2\0attr_name\0attr_state\0....
  206. mapfile -d "" files < <(
  207. set -o pipefail # Constrained to this sub-shell
  208. find . -print0 \
  209. |_plain_git check-attr --stdin -z export-subst \
  210. |(i=0
  211. while read -r -d "" val; do
  212. case "$((i++%3))" in
  213. (0) path="${val}";;
  214. (1) ;; # Attribute name, always "export-subst", as requested
  215. (2)
  216. if [ "${val}" = "set" ]; then
  217. printf "%s\0" "${path}"
  218. fi;;
  219. esac
  220. done
  221. )
  222. )
  223. # Replace format hints in those files. Always use the master repository
  224. # as the source of the git metadata, even for files found in submodules
  225. # as this is the most practical: there is no way to chdir() in (g)awk,
  226. # and recomputing GIT_DIR for each submodule would really be tedious...
  227. # There might be any arbitrary number of hints on each line, so iterate
  228. # over those one by one.
  229. for f in "${files[@]}"; do
  230. TZ=UTC \
  231. LC_ALL=C \
  232. GIT_DIR="${git_cache}/.git" \
  233. awk -v GIT="${GIT}" '
  234. {
  235. l = $(0);
  236. while( (i = match(l, /\$Format:[^\$]+\$/)) > 0 ) {
  237. len = RLENGTH;
  238. printf("%s", substr(l, 1, i-1) );
  239. fmt = substr(l, i, RLENGTH);
  240. pretty = substr(fmt, 9, length(fmt)-9);
  241. cmd = GIT " -c core.abbrev=40 log -s -n1 --pretty=format:'\''" pretty "'\''";
  242. while ( (cmd | getline replace) > 0) {
  243. printf("%s", replace);
  244. }
  245. ret = close(cmd);
  246. if (ret != 0) {
  247. printf("%s:%d: error while executing command \"%s\"\n", FILENAME, NR, cmd) > "/dev/stderr";
  248. exit 1;
  249. }
  250. l = substr(l, i+len);
  251. }
  252. printf("%s\n", l);
  253. }
  254. ' "${f}" >"${f}.br-temp"
  255. mv -f "${f}.br-temp" "${f}"
  256. done
  257. popd >/dev/null
  258. # Generate the archive.
  259. # We do not want the .git dir; we keep other .git files, in case they are the
  260. # only files in their directory.
  261. # The .git dir would generate non reproducible tarballs as it depends on
  262. # the state of the remote server. It also would generate large tarballs
  263. # (gigabytes for some linux trees) when a full clone took place.
  264. mk_tar_gz "${git_cache}" "${basename}" "${date}" "${output}" ".git/*"