git-repo (5542B)
1 #!/bin/sh 2 3 # Copyright (C) 2024, 2025 |Méso|Star> (contact@meso-star.com) 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 set -e 19 20 git_repo_tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/git_repo_XXXXXX")" 21 22 die() 23 { 24 rm -rf "${git_repo_tmpdir}" # cleanup temporary files 25 exit "${1:-1}" # return status code (default is 1) 26 } 27 28 # Configure signal processing 29 trap 'die $?' EXIT 30 31 ######################################################################## 32 # Helper functions 33 ######################################################################## 34 synopsis() 35 { 36 cmd="${0##*/}" 37 >&2 printf 'usage: %s [-Ddp] [-s group] directory\n' "${cmd}" 38 } 39 40 # Print group permissions to be sent to chmod to make a file private 41 grp_priv_perm() # file 42 ( 43 # shellcheck disable=SC2012 44 grp="$(ls -dl "$1" | cut -d' ' -f1 | cut -c5-7)" 45 46 # Disable the write and set-group-ID-on-execution permissions if there 47 # are not default file permissions 48 if ! printf '%s\n' "${grp}" | grep -e 'w'; then 49 perm="${perm}w" 50 fi 51 52 if ! printf '%s\n' "${perm}" | grep -e '[sS]'; then 53 perm="${perm}s" 54 fi 55 56 printf 'g-%s\n' "${perm}" 57 ) 58 59 # Print group permissions to be sent to chmod to make a regular file 60 # private 61 file_grp_priv_perm() 62 ( 63 f="${git_repo_tmpdir}/file" 64 touch "${f}" 65 grp_priv_perm "${f}" 66 ) 67 68 # Print group permissions to be sent to chmod to make a directory 69 # private 70 dir_grp_priv_perm() 71 ( 72 d="${git_repo_tmpdir}/dir" 73 mkdir -p "${d}" 74 grp_priv_perm "${d}" 75 ) 76 77 # Share the repository 78 share() # repo, group 79 ( 80 u="$(id -un)" 81 82 # Search for files in the repository that do NOT belong to the user. 83 # If there are any, the repository permissions cannot be updated. 84 f="$(find "$1" ! -user "${u}")" 85 if [ -n "${f}" ]; then 86 >&2 printf 'Could not share the repository: '\ 87 'some files do not belong to %s\n' "${u}" 88 die 89 fi 90 91 chown -R :"$2" "$1" 92 find "$1" -type f -exec chmod "g+w" {} \; 93 find "$1" -type d -exec chmod "g+ws" {} \; 94 cd -- "$1" 95 git config --local core.sharedRepository group 96 cd -- "${OLDPWD}" 97 ) 98 99 # Do not share the repository and define the group as the user's group 100 privatise() # repo 101 ( 102 u="$(id -un)" 103 g="$(id -gn)" 104 105 # Search for files in the repository that do NOT belong to the user. 106 # If there are any, the repository permissions cannot be updated. 107 f="$(find "$1" ! -user "${u}")" 108 if [ -n "${f}" ]; then 109 >&2 printf 'Could not privatise the repository: '\ 110 'some files do not belong to %s\n' "${u}" 111 die 112 fi 113 114 fgperm="$(file_grp_priv_perm)" 115 dgperm="$(dir_grp_priv_perm)" 116 117 chown -R :"${g}" "$1" 118 find "$1" -type f -exec chmod "${fgperm}" {} \; 119 find "$1" -type d -exec chmod "${dgperm}" {} \; 120 cd -- "$1" 121 git config --local core.sharedRepository false 122 cd -- "${OLDPWD}" 123 ) 124 125 dumb() # git_dir 126 ( 127 if [ -f "$1"/hooks/post-update.sample ]; then 128 mv "$1"/hooks/post-update.sample "$1"/hooks/post-update 129 cd -- "$1"/hooks/ 130 ./post-update || die "$?" 131 cd -- "${OLDPWD}" 132 fi 133 ) 134 135 nodumb() # git_dir 136 ( 137 if [ -f "$1"/hooks/post-update ]; then 138 mv "$1"/hooks/post-update "$1"/hooks/post-update.sample 139 fi 140 ) 141 142 ######################################################################## 143 # The script 144 ######################################################################## 145 nodumb=0 146 dumb=0 147 privatise=0 148 group="" 149 150 # Parse input arguments 151 OPTIND=1 152 while getopts ":dDps:" opt; do 153 case "${opt}" in 154 D) nodumb=1 ;; 155 d) dumb=1 ;; 156 p) privatise=1 ;; 157 s) group="${OPTARG}" ;; 158 *) synopsis; die ;; 159 esac 160 done 161 162 [ "${OPTIND}" -gt "$#" ] && { synopsis; die; } 163 164 # Skip parsed arguments 165 shift $((OPTIND - 1)) 166 167 repo="$1" 168 169 # The input repository does not exist 170 if [ ! -e "${repo}" ]; then 171 172 # Remove trailing slashes 173 repo="$(echo "${repo}" | sed 's#/\+$##g')" 174 175 # Ensure that the repository name includes the .git extension, in 176 # accordance with the convention applicable to bare repositories. 177 suffix="${repo##*.}" 178 if [ "${suffix}" != "git" ] \ 179 || [ "${suffix}" = "${repo}" ]; then 180 repo="${repo}.git" 181 fi 182 183 mkdir -p "${repo}" 184 git init --bare "${repo}" 185 186 git_dir="${repo}" 187 188 # Verify that the existing path is indeed a bare git repository 189 else 190 # Check that it is a directory 191 if [ ! -d "${repo}" ]; then 192 >&2 printf '%s: not a git repository\n' "${repo}" 193 die 194 fi 195 196 cd -- "${repo}" 197 198 # Retrieve the "git" directory, i.e., the directory where git data is 199 # stored 200 if ! git_dir=$(git rev-parse --path-format=absolute --git-dir 2>&1) 201 then 202 >&2 printf '%s: %s\n' "${repo}" "${git_dir}" 203 die 204 fi 205 206 # Check that the repository is a bare repository 207 if ! git_bare=$(git rev-parse --is-bare-repository) \ 208 || [ "${git_bare}" = "false" ]; then 209 >&2 printf '%s: not a git bare repository\n' "${repo}" 210 die 211 fi 212 213 cd -- "${OLDPWD}" 214 fi 215 216 if [ -n "${group}" ]; then 217 share "${repo}" "${group}" 218 fi 219 220 if [ "${privatise}" -ne 0 ]; then 221 privatise "${repo}" 222 fi 223 224 if [ "${dumb}" -ne 0 ]; then 225 dumb "${git_dir}" 226 fi 227 228 if [ "${nodumb}" -ne 0 ]; then 229 nodumb "${git_dir}" 230 fi