git-repo

Tools for sharing git bare repositories
git clone git://git.meso-star.fr/git-repo.git
Log | Files | Refs | README | LICENSE

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