2
1

git 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. #!/usr/bin/env bash
  2. # We want to catch any unexpected failure, and exit immediately
  3. set -E
  4. # Download helper for git, to be called from the download wrapper script
  5. #
  6. # Options:
  7. # -q Be quiet.
  8. # -r Clone and archive sub-modules.
  9. # -o FILE Generate archive in FILE.
  10. # -u URI Clone from repository at URI.
  11. # -c CSET Use changeset CSET.
  12. # -n NAME Use basename NAME.
  13. #
  14. # Environment:
  15. # GIT : the git command to call
  16. # Save our path and options in case we need to call ourselves again
  17. myname="${0}"
  18. declare -a OPTS=("${@}")
  19. # This function is called when an error occurs. Its job is to attempt a
  20. # clone from scratch (only once!) in case the git tree is borked, or in
  21. # case an unexpected and unsupported situation arises with submodules
  22. # or uncommitted stuff (e.g. if the user manually mucked around in the
  23. # git cache).
  24. _on_error() {
  25. local ret=${?}
  26. printf "Detected a corrupted git cache.\n" >&2
  27. if ${BR_GIT_BACKEND_FIRST_FAULT:-false}; then
  28. printf "This is the second time in a row; bailing out\n" >&2
  29. exit ${ret}
  30. fi
  31. export BR_GIT_BACKEND_FIRST_FAULT=true
  32. printf "Removing it and starting afresh.\n" >&2
  33. popd >/dev/null
  34. rm -rf "${git_cache}"
  35. exec "${myname}" "${OPTS[@]}" || exit ${ret}
  36. }
  37. verbose=
  38. recurse=0
  39. while getopts "${BR_BACKEND_DL_GETOPTS}" OPT; do
  40. case "${OPT}" in
  41. q) verbose=-q; exec >/dev/null;;
  42. r) recurse=1;;
  43. o) output="${OPTARG}";;
  44. u) uri="${OPTARG}";;
  45. c) cset="${OPTARG}";;
  46. d) dl_dir="${OPTARG}";;
  47. n) basename="${OPTARG}";;
  48. :) printf "option '%s' expects a mandatory argument\n" "${OPTARG}"; exit 1;;
  49. \?) printf "unknown option '%s'\n" "${OPTARG}" >&2; exit 1;;
  50. esac
  51. done
  52. shift $((OPTIND-1)) # Get rid of our options
  53. # Create and cd into the directory that will contain the local git cache
  54. git_cache="${dl_dir}/git"
  55. mkdir -p "${git_cache}"
  56. pushd "${git_cache}" >/dev/null
  57. # Any error now should try to recover
  58. trap _on_error ERR
  59. # Caller needs to single-quote its arguments to prevent them from
  60. # being expanded a second time (in case there are spaces in them)
  61. _git() {
  62. eval GIT_DIR="${git_cache}/.git" ${GIT} "${@}"
  63. }
  64. # Create a warning file, that the user should not use the git cache.
  65. # It's ours. Our precious.
  66. cat <<-_EOF_ >"${dl_dir}/git.readme"
  67. IMPORTANT NOTE!
  68. The git tree located in this directory is for the exclusive use
  69. by Buildroot, which uses it as a local cache to reduce bandwidth
  70. usage.
  71. Buildroot *will* trash any changes in that tree whenever it needs
  72. to use it. Buildroot may even remove it in case it detects the
  73. repository may have been damaged or corrupted.
  74. Do *not* work in that directory; your changes will eventually get
  75. lost. Do *not* even use it as a remote, or as the source for new
  76. worktrees; your commits will eventually get lost.
  77. _EOF_
  78. # Initialise a repository in the git cache. If the repository already
  79. # existed, this is a noop, unless the repository was broken, in which
  80. # case this magically restores it to working conditions. In the latter
  81. # case, we might be missing blobs, but that's not a problem: we'll
  82. # fetch what we need later anyway.
  83. #
  84. # We can still go through the wrapper, because 'init' does not use the
  85. # path pointed to by GIT_DIR, but really uses the directory passed as
  86. # argument.
  87. _git init .
  88. # Ensure the repo has an origin (in case a previous run was killed).
  89. if ! _git remote |grep -q -E '^origin$'; then
  90. _git remote add origin "'${uri}'"
  91. fi
  92. _git remote set-url origin "'${uri}'"
  93. # Try to fetch with limited depth, since it is faster than a full clone - but
  94. # that only works if the version is a ref (tag or branch). Before trying to do
  95. # a shallow clone we check if ${cset} is in the list provided by git ls-remote.
  96. # If not we fallback to a full fetch.
  97. #
  98. # Messages for the type of clone used are provided to ease debugging in
  99. # case of problems
  100. git_done=0
  101. if [ -n "$(_git ls-remote origin "'${cset}'" 2>&1)" ]; then
  102. printf "Doing a shallow fetch\n"
  103. if _git fetch "${@}" --depth 1 origin "'${cset}'"; then
  104. git_done=1
  105. else
  106. printf "Shallow fetch failed, falling back to fetching all refs\n"
  107. fi
  108. fi
  109. if [ ${git_done} -eq 0 ]; then
  110. printf "Fetching all references\n"
  111. _git fetch origin
  112. _git fetch origin -t
  113. fi
  114. # Try to get the special refs exposed by some forges (pull-requests for
  115. # github, changes for gerrit...). There is no easy way to know whether
  116. # the cset the user passed us is such a special ref or a tag or a sha1
  117. # or whatever else. We'll eventually fail at checking out that cset,
  118. # below, if there is an issue anyway. Since most of the cset we're gonna
  119. # have to clone are not such special refs, consign the output to oblivion
  120. # so as not to alarm unsuspecting users, but still trace it as a warning.
  121. if ! _git fetch origin "'${cset}:${cset}'" >/dev/null 2>&1; then
  122. printf "Could not fetch special ref '%s'; assuming it is not special.\n" "${cset}"
  123. fi
  124. # Check that the changeset does exist. If it does not, re-cloning from
  125. # scratch won't help, so we don't want to trash the repository for a
  126. # missing commit. We just exit without going through the ERR trap.
  127. if ! _git rev-parse --quiet --verify "'${cset}^{commit}'" >/dev/null 2>&1; then
  128. printf "Commit '%s' does not exist in this repository\n." "${cset}"
  129. exit 1
  130. fi
  131. # The new cset we want to checkout might have different submodules, or
  132. # have sub-dirs converted to/from a submodule. So we would need to
  133. # deregister _current_ submodules before we checkout.
  134. #
  135. # Using "git submodule deinit --all" would remove all the files for
  136. # all submodules, including the corresponding .git files or directories.
  137. # However, it was only introduced with git-1.8.3, which is too recent
  138. # for some enterprise-grade distros.
  139. #
  140. # So, we fall-back to just removing all submodules directories. We do
  141. # not need to be recursive, as removing a submodule will de-facto remove
  142. # its own submodules.
  143. #
  144. # For recent git versions, the repository for submodules is stored
  145. # inside the repository of the super repository, so the following will
  146. # only remove the working copies of submodules, effectively caching the
  147. # submodules.
  148. #
  149. # For older versions however, the repository is stored in the .git/ of
  150. # the submodule directory, so the following will effectively remove the
  151. # the working copy as well as the repository, which means submodules
  152. # will not be cached for older versions.
  153. #
  154. cmd='printf "Deregistering submodule \"%s\"\n" "${path}" && cd .. && rm -rf "${path##*/}"'
  155. _git submodule --quiet foreach "'${cmd}'"
  156. # Checkout the required changeset, so that we can update the required
  157. # submodules.
  158. _git checkout -f -q "'${cset}'"
  159. # Get rid of now-untracked directories (in case a git operation was
  160. # interrupted in a previous run, or to get rid of empty directories
  161. # that were parents of submodules removed above).
  162. _git clean -ffdx
  163. # Get date of commit to generate a reproducible archive.
  164. # %cD is RFC2822, so it's fully qualified, with TZ and all.
  165. date="$( _git log -1 --pretty=format:%cD )"
  166. # There might be submodules, so fetch them.
  167. if [ ${recurse} -eq 1 ]; then
  168. _git submodule update --init --recursive
  169. fi
  170. # Generate the archive, sort with the C locale so that it is reproducible.
  171. # We do not want the .git dir; we keep other .git files, in case they are the
  172. # only files in their directory.
  173. # The .git dir would generate non reproducible tarballs as it depends on
  174. # the state of the remote server. It also would generate large tarballs
  175. # (gigabytes for some linux trees) when a full clone took place.
  176. find . -not -type d \
  177. -and -not -path "./.git/*" >"${output}.list"
  178. LC_ALL=C sort <"${output}.list" >"${output}.list.sorted"
  179. # Create GNU-format tarballs, since that's the format of the tarballs on
  180. # sources.buildroot.org and used in the *.hash files
  181. tar cf - --transform="s#^\./#${basename}/#" \
  182. --numeric-owner --owner=0 --group=0 --mtime="${date}" --format=gnu \
  183. -T "${output}.list.sorted" >"${output}.tar"
  184. gzip -6 -n <"${output}.tar" >"${output}"
  185. rm -f "${output}.list"
  186. rm -f "${output}.list.sorted"
  187. popd >/dev/null