File: git-utils.sh

package info (click to toggle)
cockpit 354-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 308,956 kB
  • sloc: javascript: 775,606; python: 40,351; ansic: 35,655; cpp: 11,117; sh: 3,511; makefile: 580; xml: 261
file content (150 lines) | stat: -rw-r--r-- 5,160 bytes parent folder | download | duplicates (12)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# shellcheck shell=sh
# doesn't do anything on its own.  must be sourced.

# The script which sources this script must set the following variables:
#   GITHUB_REPO = the relative repo name of the submodule on github
#   SUBDIR = the location in the working tree where the submodule goes
# We also expect `set -eu`, but set them ourselves for shellcheck.
set -eu
[ -n "${GITHUB_REPO}" ]
[ -n "${SUBDIR}" ]

# Set by git-rebase for spawned actions
unset GIT_DIR GIT_EXEC_PATH GIT_PREFIX GIT_REFLOG_ACTION GIT_WORK_TREE

GITHUB_BASE="${GITHUB_BASE:-cockpit-project/cockpit}"
GITHUB_REPOSITORY="${GITHUB_BASE%/*}/${GITHUB_REPO}"
HTTPS_REMOTE="https://github.com/${GITHUB_REPOSITORY}"
# shellcheck disable=SC2034 # used in other scripts
SSH_REMOTE="git@github.com:${GITHUB_REPOSITORY}"

CACHE_DIR="${XDG_CACHE_HOME-${HOME}/.cache}/cockpit-dev/${GITHUB_REPOSITORY}.git"

if [ "${V-}" = 0 ]; then
    message() { printf "  %-8s %s\n" "$1" "$2" >&2; }
    quiet='--quiet'
else
    message() { :; }
    quiet=''
fi

init_cache() {
    if [ ! -d "${CACHE_DIR}" ]; then
        message INIT "${CACHE_DIR}"
        mkdir -p "${CACHE_DIR}"
        git init --bare --template='' ${quiet} "${CACHE_DIR}"
        git --git-dir "${CACHE_DIR}" remote add origin "${HTTPS_REMOTE}"
    fi
}

# runs a git command on the cache dir
git_cache() {
    init_cache
    git --git-dir "${CACHE_DIR}" "$@"
}

# reads the named gitlink from the current state of the index
# returns (ie: prints) a 40-character commit ID
get_index_gitlink() {
    if ! git ls-files -s "$1" | grep -E -o '\<[[:xdigit:]]{40}\>'; then
        echo "*** couldn't read gitlink for file $1 from the index" >&2
        exit 1
    fi
}

# This checks if the given argument "$1" (already) exists in the repository
# we use git rev-list --objects to to avoid problems with incomplete fetches:
# we want to make sure the complete commit is there
check_ref() {
    git_cache rev-list --quiet --objects "$1" -- 2>/dev/null
}

# Fetch a specific commit ID into the cache
# Either we have this commit available locally (in which case this function
# does nothing), or we need to fetch it.  There's no chance that the object
# changed on the server, because we define it by its checksum.
fetch_sha_to_cache() {
    sha="$1"

    # No "offline mode" here: we either have the commit, or we don't
    if ! check_ref "${sha}"; then
        message FETCH "${SUBDIR}  [ref: ${sha}]"
        git_cache fetch --no-tags ${quiet} origin "${sha}"
        # tag it to keep it from being GC'd.
        git_cache tag "sha-${sha}" "${sha}"
    fi
}

# General purpose "fetch" function to be used with tags, refs, or nothing at
# all (to fetch everything).  This checks the server for updates, because all
# of those things might change at any given time.  Supports an "offline" mode
# to skip the fetch and use the possibly-stale local version, if we have it.
fetch_to_cache() {
    # We're fetching a named ref (or all refs), which means:
    #  - we should always do the fetch because it might have changed. but
    #  - we might be able to skip updating in case we already have it
    if [ -z "${OFFLINE-}" ]; then
        for retry in $(seq 3); do
            message FETCH "${SUBDIR}  ${1+[ref: $*]}"
            if git_cache fetch --prune ${quiet} origin "$@"; then
                return
            fi
            sleep $((retry * retry * 5))
        done
        echo "repeated git fetch failure, giving up" >&2
        exit 1
    fi
}

# Get the content of "$2" from cache commit "$1"
cat_from_cache() {
    git_cache cat-file blob "$1:$2"
}

# Consistency checking: for a given cache commit "$1", check if it contains a
# file "$2" which is equal to the file "$3" present in the working tree.
cmp_from_cache() {
    cat_from_cache "$1" "$2" | cmp "$3"
}

# Like `git clone` except that it uses the original origin url and supports
# checking out commit IDs as detached heads.  The target directory must either
# be empty, or not exist.
clone_from_cache() {
    message CLONE "${SUBDIR}  [ref: $1]"
    [ ! -e "${SUBDIR}" ] || rmdir "${SUBDIR}"
    mkdir "${SUBDIR}"
    cp -a --reflink=auto "${CACHE_DIR}" "${SUBDIR}/.git"
    git --git-dir "${SUBDIR}/.git" config --unset core.bare
    git -c advice.detachedHead=false -C "${SUBDIR}" checkout ${quiet} "$1"
}

# This stores a .tar file from stdin into the cache as a tree object.
# Returns the ID.  Opposite of `git archive`, basically.
tar_to_cache() {
    # Need to do this before we set the GIT_* variables
    init_cache

    # Use a sub-shell to enable cleanup of the temporary directory
    (
        tmpdir="$(mktemp --tmpdir --directory cockpit-tar-to-git.XXXXXX)"
        # shellcheck disable=SC2064 # we want ${tmpdir} expanded now
        trap "rm -r '${tmpdir}'" EXIT

        export GIT_INDEX_FILE="${tmpdir}/tmp-index"
        export GIT_WORK_TREE="${tmpdir}/work"

        mkdir "${GIT_WORK_TREE}"
        cd "${GIT_WORK_TREE}"

        tar --extract --exclude '.git*'
        message INDEX "${SUBDIR}"
        git_cache add --all
        git_cache write-tree
    )
}

 # Small helper to run a git command on the cache directory
cmd_git() {
    git_cache "$@"
}