Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | # Copyright (C) 2022 The Android Open Source Project |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 16 | usage() { # exit code |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 17 | cat <<-EOF |
| 18 | NAME |
| 19 | git-gc-preserve - Run git gc and preserve old packs to avoid races for JGit |
| 20 | |
Matthias Sohn | 1356738 | 2022-12-15 16:16:45 +0100 | [diff] [blame] | 21 | SYNOPSIS |
| 22 | git gc-preserve |
| 23 | |
| 24 | DESCRIPTION |
| 25 | Runs git gc and can preserve old packs to avoid races with concurrently |
| 26 | executed commands in JGit. |
| 27 | |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 28 | This command uses custom git config options to configure if preserved packs |
| 29 | from the last run of git gc should be pruned and if packs should be preserved. |
| 30 | |
| 31 | This is similar to the implementation in JGit [1] which is used by |
| 32 | JGit to avoid errors [2] in such situations. |
| 33 | |
Matthias Sohn | 3ab45f0 | 2022-12-15 15:48:46 +0100 | [diff] [blame] | 34 | The command prevents concurrent runs of the command on the same repository |
| 35 | by acquiring an exclusive file lock on the file |
| 36 | "\$repopath/gc-preserve.pid" |
| 37 | If it cannot acquire the lock it fails immediately with exit code 3. |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 38 | |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 39 | Failure Exit Codes |
| 40 | 1: General failure |
| 41 | 2: Couldn't determine repository path. If the current working directory |
| 42 | is outside of the working tree of the git repository use git option |
| 43 | --git-dir to pass the root path of the repository. |
| 44 | E.g. |
| 45 | $ git --git-dir ~/git/foo gc-preserve |
Matthias Sohn | 3ab45f0 | 2022-12-15 15:48:46 +0100 | [diff] [blame] | 46 | 3: Another process already runs $0 on the same repository |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 47 | |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 48 | [1] https://git.eclipse.org/r/c/jgit/jgit/+/87969 |
| 49 | [2] https://git.eclipse.org/r/c/jgit/jgit/+/122288 |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 50 | |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 51 | CONFIGURATION |
| 52 | "gc.prunepreserved": if set to "true" preserved packs from the last gc run |
| 53 | are pruned before current packs are preserved. |
| 54 | |
| 55 | "gc.preserveoldpacks": if set to "true" current packs will be hard linked |
| 56 | to objects/pack/preserved before git gc is executed. JGit will |
| 57 | fallback to the preserved packs in this directory in case it comes |
| 58 | across missing objects which might be caused by a concurrent run of |
| 59 | git gc. |
| 60 | EOF |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 61 | exit "$1" |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 62 | } |
| 63 | |
Matthias Sohn | 3ab45f0 | 2022-12-15 15:48:46 +0100 | [diff] [blame] | 64 | # acquire file lock, unlock when the script exits |
| 65 | lock() { # repo |
| 66 | readonly LOCKFILE="$1/gc-preserve.pid" |
| 67 | test -f "$LOCKFILE" || touch "$LOCKFILE" |
| 68 | exec 9> "$LOCKFILE" |
| 69 | if flock -nx 9; then |
Matthias Sohn | 2cc3b5b | 2023-01-09 17:58:09 +0100 | [diff] [blame] | 70 | echo -n "$$ $USERNAME@$HOSTNAME" >&9 |
Matthias Sohn | 3ab45f0 | 2022-12-15 15:48:46 +0100 | [diff] [blame] | 71 | trap unlock EXIT |
| 72 | else |
| 73 | echo "$0 is already running" |
| 74 | exit 3 |
| 75 | fi |
| 76 | } |
| 77 | |
| 78 | unlock() { |
| 79 | # only delete if the file descriptor 9 is open |
| 80 | if { : >&9 ; } &> /dev/null; then |
| 81 | rm -f "$LOCKFILE" |
| 82 | fi |
| 83 | # close the file handle to release file lock |
| 84 | exec 9>&- |
| 85 | } |
| 86 | |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 87 | # prune preserved packs if gc.prunepreserved == true |
| 88 | prune_preserved() { # repo |
| 89 | configured=$(git --git-dir="$1" config --get gc.prunepreserved) |
| 90 | if [ "$configured" != "true" ]; then |
| 91 | return 0 |
| 92 | fi |
| 93 | local preserved=$1/objects/pack/preserved |
| 94 | if [ -d "$preserved" ]; then |
| 95 | printf "Pruning old preserved packs: " |
| 96 | count=$(find "$preserved" -name "*.old-pack" | wc -l) |
| 97 | rm -rf "$preserved" |
| 98 | echo "$count, done." |
| 99 | fi |
| 100 | } |
| 101 | |
| 102 | # preserve packs if gc.preserveoldpacks == true |
| 103 | preserve_packs() { # repo |
| 104 | configured=$(git --git-dir="$1" config --get gc.preserveoldpacks) |
| 105 | if [ "$configured" != "true" ]; then |
| 106 | return 0 |
| 107 | fi |
| 108 | local packdir=$1/objects/pack |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 109 | pushd "$packdir" >/dev/null || exit 1 |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 110 | mkdir -p preserved |
| 111 | printf "Preserving packs: " |
| 112 | count=0 |
| 113 | for file in pack-*{.pack,.idx} ; do |
| 114 | ln -f "$file" preserved/"$(get_preserved_packfile_name "$file")" |
| 115 | if [[ "$file" == pack-*.pack ]]; then |
| 116 | ((count++)) |
| 117 | fi |
| 118 | done |
| 119 | echo "$count, done." |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 120 | popd >/dev/null || exit 1 |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 121 | } |
| 122 | |
| 123 | # pack-0...2.pack to pack-0...2.old-pack |
| 124 | # pack-0...2.idx to pack-0...2.old-idx |
| 125 | get_preserved_packfile_name() { # packfile > preserved_packfile |
| 126 | local old=${1/%\.pack/.old-pack} |
| 127 | old=${old/%\.idx/.old-idx} |
| 128 | echo "$old" |
| 129 | } |
| 130 | |
| 131 | # main |
| 132 | |
| 133 | while [ $# -gt 0 ] ; do |
| 134 | case "$1" in |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 135 | -u|-h) usage 0 ;; |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 136 | esac |
| 137 | shift |
| 138 | done |
| 139 | args=$(git rev-parse --sq-quote "$@") |
| 140 | |
| 141 | repopath=$(git rev-parse --git-dir) |
| 142 | if [ -z "$repopath" ]; then |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 143 | usage 2 |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 144 | fi |
| 145 | |
Matthias Sohn | 3ab45f0 | 2022-12-15 15:48:46 +0100 | [diff] [blame] | 146 | lock "$repopath" |
Nasser Grainawi | 59c9ae7 | 2022-12-07 18:20:06 +0100 | [diff] [blame] | 147 | prune_preserved "$repopath" |
| 148 | preserve_packs "$repopath" |
Matthias Sohn | e7e8213 | 2022-12-15 16:02:22 +0100 | [diff] [blame] | 149 | git gc ${args:+"$args"} || { echo "git gc failed"; exit "$?"; } |