133 lines
4.4 KiB
Bash
133 lines
4.4 KiB
Bash
|
#!/usr/bin/env bash
|
||
|
|
||
|
set +o errexit
|
||
|
# Copyright (C) 2022 The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
usage() { # exit code
|
||
|
cat <<-EOF
|
||
|
NAME
|
||
|
git-gc-preserve - Run git gc and preserve old packs to avoid races for JGit
|
||
|
SYNOPSIS
|
||
|
git gc-preserve
|
||
|
DESCRIPTION
|
||
|
Runs git gc and can preserve old packs to avoid races with concurrently
|
||
|
executed commands in JGit.
|
||
|
This command uses custom git config options to configure if preserved packs
|
||
|
from the last run of git gc should be pruned and if packs should be preserved.
|
||
|
This is similar to the implementation in JGit [1] which is used by
|
||
|
JGit to avoid errors [2] in such situations.
|
||
|
The command prevents concurrent runs of the command on the same repository
|
||
|
by acquiring an exclusive file lock on the file
|
||
|
"\$repopath/gc-preserve.pid"
|
||
|
If it cannot acquire the lock it fails immediately with exit code 3.
|
||
|
Failure Exit Codes
|
||
|
1: General failure
|
||
|
2: Couldn't determine repository path. If the current working directory
|
||
|
is outside of the working tree of the git repository use git option
|
||
|
--git-dir to pass the root path of the repository.
|
||
|
E.g.
|
||
|
$ git --git-dir ~/git/foo gc-preserve
|
||
|
3: Another process already runs $0 on the same repository
|
||
|
[1] https://git.eclipse.org/r/c/jgit/jgit/+/87969
|
||
|
[2] https://git.eclipse.org/r/c/jgit/jgit/+/122288
|
||
|
CONFIGURATION
|
||
|
"pack.prunepreserved": if set to "true" preserved packs from the last gc run
|
||
|
are pruned before current packs are preserved.
|
||
|
"pack.preserveoldpacks": if set to "true" current packs will be hard linked
|
||
|
to objects/pack/preserved before git gc is executed. JGit will
|
||
|
fallback to the preserved packs in this directory in case it comes
|
||
|
across missing objects which might be caused by a concurrent run of
|
||
|
git gc.
|
||
|
EOF
|
||
|
exit "$1"
|
||
|
}
|
||
|
# acquire file lock, unlock when the script exits
|
||
|
lock() { # repo
|
||
|
readonly LOCKFILE="$1/gc-preserve.pid"
|
||
|
test -f "$LOCKFILE" || touch "$LOCKFILE"
|
||
|
exec 9> "$LOCKFILE"
|
||
|
if flock -nx 9; then
|
||
|
echo -n "$$ $USER@$(hostname)" >&9
|
||
|
trap unlock EXIT
|
||
|
else
|
||
|
echo "$0 is already running"
|
||
|
exit 3
|
||
|
fi
|
||
|
}
|
||
|
unlock() {
|
||
|
# only delete if the file descriptor 9 is open
|
||
|
if { : >&9 ; } &> /dev/null; then
|
||
|
rm -f "$LOCKFILE"
|
||
|
fi
|
||
|
# close the file handle to release file lock
|
||
|
exec 9>&-
|
||
|
}
|
||
|
# prune preserved packs if pack.prunepreserved == true
|
||
|
prune_preserved() { # repo
|
||
|
configured=$(git --git-dir="$1" config --get pack.prunepreserved)
|
||
|
if [ "$configured" != "true" ]; then
|
||
|
return 0
|
||
|
fi
|
||
|
local preserved=$1/objects/pack/preserved
|
||
|
if [ -d "$preserved" ]; then
|
||
|
printf "Pruning old preserved packs: "
|
||
|
count=$(find "$preserved" -name "*.old-pack" | wc -l)
|
||
|
rm -rf "$preserved"
|
||
|
echo "$count, done."
|
||
|
fi
|
||
|
}
|
||
|
# preserve packs if pack.preserveoldpacks == true
|
||
|
preserve_packs() { # repo
|
||
|
configured=$(git --git-dir="$1" config --get pack.preserveoldpacks)
|
||
|
if [ "$configured" != "true" ]; then
|
||
|
return 0
|
||
|
fi
|
||
|
local packdir=$1/objects/pack
|
||
|
pushd "$packdir" >/dev/null || exit 1
|
||
|
mkdir -p preserved
|
||
|
printf "Preserving packs: "
|
||
|
count=0
|
||
|
for file in pack-*{.pack,.idx} ; do
|
||
|
ln -f "$file" preserved/"$(get_preserved_packfile_name "$file")"
|
||
|
if [[ "$file" == pack-*.pack ]]; then
|
||
|
((count++))
|
||
|
fi
|
||
|
done
|
||
|
echo "$count, done."
|
||
|
popd >/dev/null || exit 1
|
||
|
}
|
||
|
# pack-0...2.pack to pack-0...2.old-pack
|
||
|
# pack-0...2.idx to pack-0...2.old-idx
|
||
|
get_preserved_packfile_name() { # packfile > preserved_packfile
|
||
|
local old=${1/%\.pack/.old-pack}
|
||
|
old=${old/%\.idx/.old-idx}
|
||
|
echo "$old"
|
||
|
}
|
||
|
# main
|
||
|
while [ $# -gt 0 ] ; do
|
||
|
case "$1" in
|
||
|
-u|-h) usage 0 ;;
|
||
|
esac
|
||
|
shift
|
||
|
done
|
||
|
args=$(git rev-parse --sq-quote "$@")
|
||
|
repopath=$(git rev-parse --git-dir)
|
||
|
if [ -z "$repopath" ]; then
|
||
|
usage 2
|
||
|
fi
|
||
|
lock "$repopath"
|
||
|
prune_preserved "$repopath"
|
||
|
preserve_packs "$repopath"
|
||
|
git gc ${args:+"$args"} || { EXIT_CODE="$?"; echo "git gc failed"; exit "$EXIT_CODE"; }
|