xref: /OK3568_Linux_fs/buildroot/support/download/git (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env bash
2*4882a593Smuzhiyun
3*4882a593Smuzhiyun# NOTE: if the output of this backend has to change (e.g. we change what gets
4*4882a593Smuzhiyun# included in the archive (e.g. LFS), or we change the format of the archive
5*4882a593Smuzhiyun# (e.g. tar options, compression ratio or method)), we MUST update the format
6*4882a593Smuzhiyun# version in the variable BR_FMT_VERSION_git, in package/pkg-download.mk.
7*4882a593Smuzhiyun
8*4882a593Smuzhiyun# We want to catch any unexpected failure, and exit immediately
9*4882a593Smuzhiyunset -E
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun# Download helper for git, to be called from the download wrapper script
12*4882a593Smuzhiyun#
13*4882a593Smuzhiyun# Options:
14*4882a593Smuzhiyun#   -q          Be quiet.
15*4882a593Smuzhiyun#   -r          Clone and archive sub-modules.
16*4882a593Smuzhiyun#   -o FILE     Generate archive in FILE.
17*4882a593Smuzhiyun#   -u URI      Clone from repository at URI.
18*4882a593Smuzhiyun#   -c CSET     Use changeset CSET.
19*4882a593Smuzhiyun#   -n NAME     Use basename NAME.
20*4882a593Smuzhiyun#
21*4882a593Smuzhiyun# Environment:
22*4882a593Smuzhiyun#   GIT      : the git command to call
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun. "${0%/*}/helpers"
25*4882a593Smuzhiyun
26*4882a593Smuzhiyun# Save our path and options in case we need to call ourselves again
27*4882a593Smuzhiyunmyname="${0}"
28*4882a593Smuzhiyundeclare -a OPTS=("${@}")
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun# This function is called when an error occurs. Its job is to attempt a
31*4882a593Smuzhiyun# clone from scratch (only once!) in case the git tree is borked, or in
32*4882a593Smuzhiyun# case an unexpected and unsupported situation arises with submodules
33*4882a593Smuzhiyun# or uncommitted stuff (e.g. if the user manually mucked around in the
34*4882a593Smuzhiyun# git cache).
35*4882a593Smuzhiyun_on_error() {
36*4882a593Smuzhiyun    local ret=${?}
37*4882a593Smuzhiyun
38*4882a593Smuzhiyun    printf "Detected a corrupted git cache.\n" >&2
39*4882a593Smuzhiyun    if ${BR_GIT_BACKEND_FIRST_FAULT:-false}; then
40*4882a593Smuzhiyun        printf "This is the second time in a row; bailing out\n" >&2
41*4882a593Smuzhiyun        exit ${ret}
42*4882a593Smuzhiyun    fi
43*4882a593Smuzhiyun    export BR_GIT_BACKEND_FIRST_FAULT=true
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun    printf "Removing it and starting afresh.\n" >&2
46*4882a593Smuzhiyun
47*4882a593Smuzhiyun    popd >/dev/null
48*4882a593Smuzhiyun    rm -rf "${git_cache}"
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun    exec "${myname}" "${OPTS[@]}" || exit ${ret}
51*4882a593Smuzhiyun}
52*4882a593Smuzhiyun
53*4882a593Smuzhiyunquiet=
54*4882a593Smuzhiyunrecurse=0
55*4882a593Smuzhiyunwhile getopts "${BR_BACKEND_DL_GETOPTS}" OPT; do
56*4882a593Smuzhiyun    case "${OPT}" in
57*4882a593Smuzhiyun    q)  quiet=-q; exec >/dev/null;;
58*4882a593Smuzhiyun    r)  recurse=1;;
59*4882a593Smuzhiyun    o)  output="${OPTARG}";;
60*4882a593Smuzhiyun    u)  uri="${OPTARG}";;
61*4882a593Smuzhiyun    c)  cset="${OPTARG}";;
62*4882a593Smuzhiyun    d)  dl_dir="${OPTARG}";;
63*4882a593Smuzhiyun    n)  basename="${OPTARG}";;
64*4882a593Smuzhiyun    :)  printf "option '%s' expects a mandatory argument\n" "${OPTARG}"; exit 1;;
65*4882a593Smuzhiyun    \?) printf "unknown option '%s'\n" "${OPTARG}" >&2; exit 1;;
66*4882a593Smuzhiyun    esac
67*4882a593Smuzhiyundone
68*4882a593Smuzhiyun
69*4882a593Smuzhiyunshift $((OPTIND-1)) # Get rid of our options
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun# Create and cd into the directory that will contain the local git cache
72*4882a593Smuzhiyungit_cache="${dl_dir}/git"
73*4882a593Smuzhiyunmkdir -p "${git_cache}"
74*4882a593Smuzhiyunpushd "${git_cache}" >/dev/null
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun# Any error now should try to recover
77*4882a593Smuzhiyuntrap _on_error ERR
78*4882a593Smuzhiyun
79*4882a593Smuzhiyun# Caller needs to single-quote its arguments to prevent them from
80*4882a593Smuzhiyun# being expanded a second time (in case there are spaces in them)
81*4882a593Smuzhiyun_git() {
82*4882a593Smuzhiyun    if [ -z "${quiet}" ]; then
83*4882a593Smuzhiyun        printf '%s ' GIT_DIR="${git_cache}/.git" ${GIT} "${@}"; printf '\n'
84*4882a593Smuzhiyun    fi
85*4882a593Smuzhiyun    _plain_git "$@"
86*4882a593Smuzhiyun}
87*4882a593Smuzhiyun# Note: please keep command below aligned with what is printed above
88*4882a593Smuzhiyun_plain_git() {
89*4882a593Smuzhiyun    eval GIT_DIR="${git_cache}/.git" ${GIT} "${@}"
90*4882a593Smuzhiyun}
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun# Create a warning file, that the user should not use the git cache.
93*4882a593Smuzhiyun# It's ours. Our precious.
94*4882a593Smuzhiyuncat <<-_EOF_ >"${dl_dir}/git.readme"
95*4882a593Smuzhiyun	IMPORTANT NOTE!
96*4882a593Smuzhiyun
97*4882a593Smuzhiyun	The git tree located in this directory is for the exclusive use
98*4882a593Smuzhiyun	by Buildroot, which uses it as a local cache to reduce bandwidth
99*4882a593Smuzhiyun	usage.
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun	Buildroot *will* trash any changes in that tree whenever it needs
102*4882a593Smuzhiyun	to use it. Buildroot may even remove it in case it detects the
103*4882a593Smuzhiyun	repository may have been damaged or corrupted.
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun	Do *not* work in that directory; your changes will eventually get
106*4882a593Smuzhiyun	lost. Do *not* even use it as a remote, or as the source for new
107*4882a593Smuzhiyun	worktrees; your commits will eventually get lost.
108*4882a593Smuzhiyun_EOF_
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun# Initialise a repository in the git cache. If the repository already
111*4882a593Smuzhiyun# existed, this is a noop, unless the repository was broken, in which
112*4882a593Smuzhiyun# case this magically restores it to working conditions. In the latter
113*4882a593Smuzhiyun# case, we might be missing blobs, but that's not a problem: we'll
114*4882a593Smuzhiyun# fetch what we need later anyway.
115*4882a593Smuzhiyun#
116*4882a593Smuzhiyun# We can still go through the wrapper, because 'init' does not use the
117*4882a593Smuzhiyun# path pointed to by GIT_DIR, but really uses the directory passed as
118*4882a593Smuzhiyun# argument.
119*4882a593Smuzhiyun_git init .
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun# Ensure the repo has an origin (in case a previous run was killed).
122*4882a593Smuzhiyunif ! _plain_git remote |grep -q -E '^origin$'; then
123*4882a593Smuzhiyun    _git remote add origin "'${uri}'"
124*4882a593Smuzhiyunfi
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun_git remote set-url origin "'${uri}'"
127*4882a593Smuzhiyun
128*4882a593Smuzhiyunprintf "Fetching all references\n"
129*4882a593Smuzhiyun_git fetch origin
130*4882a593Smuzhiyun_git fetch origin -t
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun# Try to get the special refs exposed by some forges (pull-requests for
133*4882a593Smuzhiyun# github, changes for gerrit...). There is no easy way to know whether
134*4882a593Smuzhiyun# the cset the user passed us is such a special ref or a tag or a sha1
135*4882a593Smuzhiyun# or whatever else. We'll eventually fail at checking out that cset,
136*4882a593Smuzhiyun# below, if there is an issue anyway. Since most of the cset we're gonna
137*4882a593Smuzhiyun# have to clone are not such special refs, consign the output to oblivion
138*4882a593Smuzhiyun# so as not to alarm unsuspecting users, but still trace it as a warning.
139*4882a593Smuzhiyunif ! _git fetch origin "'${cset}:${cset}'" >/dev/null 2>&1; then
140*4882a593Smuzhiyun    printf "Could not fetch special ref '%s'; assuming it is not special.\n" "${cset}"
141*4882a593Smuzhiyunfi
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun# Check that the changeset does exist. If it does not, re-cloning from
144*4882a593Smuzhiyun# scratch won't help, so we don't want to trash the repository for a
145*4882a593Smuzhiyun# missing commit. We just exit without going through the ERR trap.
146*4882a593Smuzhiyunif ! _git rev-parse --quiet --verify "'${cset}^{commit}'" >/dev/null 2>&1; then
147*4882a593Smuzhiyun    printf "Commit '%s' does not exist in this repository.\n" "${cset}"
148*4882a593Smuzhiyun    exit 1
149*4882a593Smuzhiyunfi
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun# The new cset we want to checkout might have different submodules, or
152*4882a593Smuzhiyun# have sub-dirs converted to/from a submodule. So we would need to
153*4882a593Smuzhiyun# deregister _current_ submodules before we checkout.
154*4882a593Smuzhiyun#
155*4882a593Smuzhiyun# Using "git submodule deinit --all" would remove all the files for
156*4882a593Smuzhiyun# all submodules, including the corresponding .git files or directories.
157*4882a593Smuzhiyun# However, it  was only introduced with git-1.8.3, which is too recent
158*4882a593Smuzhiyun# for some enterprise-grade distros.
159*4882a593Smuzhiyun#
160*4882a593Smuzhiyun# So, we fall-back to just removing all submodules directories. We do
161*4882a593Smuzhiyun# not need to be recursive, as removing a submodule will de-facto remove
162*4882a593Smuzhiyun# its own submodules.
163*4882a593Smuzhiyun#
164*4882a593Smuzhiyun# For recent git versions, the repository for submodules is stored
165*4882a593Smuzhiyun# inside the repository of the super repository, so the following will
166*4882a593Smuzhiyun# only remove the working copies of submodules, effectively caching the
167*4882a593Smuzhiyun# submodules.
168*4882a593Smuzhiyun#
169*4882a593Smuzhiyun# For older versions however, the repository is stored in the .git/ of
170*4882a593Smuzhiyun# the submodule directory, so the following will effectively remove the
171*4882a593Smuzhiyun# the working copy as well as the repository, which means submodules
172*4882a593Smuzhiyun# will not be cached for older versions.
173*4882a593Smuzhiyun#
174*4882a593Smuzhiyuncmd='printf "Deregistering submodule \"%s\"\n" "${path}" && cd .. && rm -rf "${path##*/}"'
175*4882a593Smuzhiyun_git submodule --quiet foreach "'${cmd}'"
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun# Checkout the required changeset, so that we can update the required
178*4882a593Smuzhiyun# submodules.
179*4882a593Smuzhiyun_git checkout -f -q "'${cset}'"
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun# Get rid of now-untracked directories (in case a git operation was
182*4882a593Smuzhiyun# interrupted in a previous run, or to get rid of empty directories
183*4882a593Smuzhiyun# that were parents of submodules removed above).
184*4882a593Smuzhiyun_git clean -ffdx
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun# Get date of commit to generate a reproducible archive.
187*4882a593Smuzhiyun# %ci is ISO 8601, so it's fully qualified, with TZ and all.
188*4882a593Smuzhiyundate="$( _plain_git log -1 --pretty=format:%ci )"
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun# There might be submodules, so fetch them.
191*4882a593Smuzhiyunif [ ${recurse} -eq 1 ]; then
192*4882a593Smuzhiyun    _git submodule update --init --recursive
193*4882a593Smuzhiyun
194*4882a593Smuzhiyun    # Older versions of git will store the absolute path of the git tree
195*4882a593Smuzhiyun    # in the .git of submodules, while newer versions just use relative
196*4882a593Smuzhiyun    # paths. Detect and fix the older variants to use relative paths, so
197*4882a593Smuzhiyun    # that the archives are reproducible across a wider range of git
198*4882a593Smuzhiyun    # versions. However, we can't do that if git is too old and uses
199*4882a593Smuzhiyun    # full repositories for submodules.
200*4882a593Smuzhiyun    cmd='printf "%s\n" "${path}/"'
201*4882a593Smuzhiyun    for module_dir in $( _plain_git submodule --quiet foreach "'${cmd}'" ); do
202*4882a593Smuzhiyun        [ -f "${module_dir}/.git" ] || continue
203*4882a593Smuzhiyun        relative_dir="$( sed -r -e 's,/+,/,g; s,[^/]+/,../,g' <<<"${module_dir}" )"
204*4882a593Smuzhiyun        sed -r -i -e "s:^gitdir\: $(pwd)/:gitdir\: "${relative_dir}":" "${module_dir}/.git"
205*4882a593Smuzhiyun    done
206*4882a593Smuzhiyunfi
207*4882a593Smuzhiyun
208*4882a593Smuzhiyunpopd >/dev/null
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun# Generate the archive.
211*4882a593Smuzhiyun# We do not want the .git dir; we keep other .git files, in case they are the
212*4882a593Smuzhiyun# only files in their directory.
213*4882a593Smuzhiyun# The .git dir would generate non reproducible tarballs as it depends on
214*4882a593Smuzhiyun# the state of the remote server. It also would generate large tarballs
215*4882a593Smuzhiyun# (gigabytes for some linux trees) when a full clone took place.
216*4882a593Smuzhiyunmk_tar_gz "${git_cache}" "${basename}" "${date}" "${output}" ".git/*"
217