git-repo

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

commit d73674c918d3a1a4ed636dd1f13d30326573740e
parent 80b694c974a91889c6c0187ce5e82818d52a2925
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Sun, 30 Nov 2025 16:10:19 +0100

git-repo: major update to the command

Define two options that perform the opposite action to those already
defined. This means that the absence of the original options does not
cancel their action as before. This not only makes the command more
intuitive, but also corrects certain special corner cases in its use.

For example, enabling dumb transport on a repository whose write access
was shared with its group could return an error if not all of its files
belonged to the user, i.e., if the repository had been updated by at
least one of its co-authors. In fact, it is no longer possible to update
the group, either to privatize the repository or to update the group
that shares write access. Thus, setting (updating the group) or not
setting (privatizing the repository) the -s option returned an error in
all situations.

From now on, to make the repository private, the user must explicitly
set the -p option. If the -s and -p options are not set, the repository
group and its permissions remain unchanged. This allows you to enable
dumb transport on a repository without having to change the group and
its access rights.

And, following the same logic, not setting the -d option no longer
disables basic transport. To do so, the caller must set the new -D
option.

The documentation has been updated to reflect these changes.

Diffstat:
Mgit-repo | 168++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mgit-repo.1 | 38+++++++++++++++++++++++++-------------
2 files changed, 148 insertions(+), 58 deletions(-)

diff --git a/git-repo b/git-repo @@ -17,78 +17,143 @@ set -e -######################################################################## -# Helper functions -######################################################################## +git_repo_tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/git_repo_XXXXXX")" + die() { + rm -rf "${git_repo_tmpdir}" # cleanup temporary files exit "${1:-1}" # return status code (default is 1) } +# Configure signal processing +trap 'die $?' EXIT + +######################################################################## +# Helper functions +######################################################################## synopsis() { cmd="${0##*/}" - >&2 printf 'usage: %s [-d] [-s group] directory\n' "${cmd}" + >&2 printf 'usage: %s [-Ddp] [-s group] directory\n' "${cmd}" } +# Print group permissions to be sent to chmod to make a file private +grp_priv_perm() # file +( + # shellcheck disable=SC2012 + grp="$(ls -dl "$1" | cut -d' ' -f1 | cut -c5-7)" + + # Disable the write and set-group-ID-on-execution permissions if there + # are not default file permissions + if ! printf '%s\n' "${grp}" | grep -e 'w'; then + perm="${perm}w" + fi + + if ! printf '%s\n' "${perm}" | grep -e '[sS]'; then + perm="${perm}s" + fi + + printf 'g-%s\n' "${perm}" +) + +# Print group permissions to be sent to chmod to make a regular file +# private +file_grp_priv_perm() +( + f="${git_repo_tmpdir}/file" + touch "${f}" + grp_priv_perm "${f}" +) + +# Print group permissions to be sent to chmod to make a directory +# private +dir_grp_priv_perm() +( + d="${git_repo_tmpdir}/dir" + mkdir -p "${d}" + grp_priv_perm "${d}" +) + +# Share the repository share() # repo, group -{ - # Share the repository - if [ -n "$2" ]; then - chown -R :"$2" "$1" - find "$1" -type f -exec chmod "g+w" {} \; - find "$1" -type d -exec chmod "g+ws" {} \; - cd -- "$1" - git config --local core.sharedRepository group - - # Do not share the repository and define the group as the user's group - else - - u="$(id -un)" - g="$(id -gn)" - - f="$(find "$1" ! -user "${u}")" - if [ -n "${f}" ]; then - >&2 printf 'The repository cannot be privatised: '\ +( + u="$(id -un)" + + # Search for files in the repository that do NOT belong to the user. + # If there are any, the repository permissions cannot be updated. + f="$(find "$1" ! -user "${u}")" + if [ -n "${f}" ]; then + >&2 printf 'Could not share the repository: '\ 'some files do not belong to %s\n' "${u}" - die - fi - - chown -R :"${g}" "$1" - find "$1" -type f -exec chmod "g-w" {} \; - find "$1" -type d -exec chmod "g-ws" {} \; - cd -- "$1" - git config --local core.sharedRepository false + die fi -} -dumb() # git_dir, dumb -{ - if [ "$2" -eq 0 ]; then - if [ -f "$1"/hooks/post-update ]; then - mv "$1"/hooks/post-update "$1"/hooks/post-update.sample - fi + chown -R :"$2" "$1" + find "$1" -type f -exec chmod "g+w" {} \; + find "$1" -type d -exec chmod "g+ws" {} \; + cd -- "$1" + git config --local core.sharedRepository group + cd -- "${OLDPWD}" +) + +# Do not share the repository and define the group as the user's group +privatise() # repo +( + u="$(id -un)" + g="$(id -gn)" + + # Search for files in the repository that do NOT belong to the user. + # If there are any, the repository permissions cannot be updated. + f="$(find "$1" ! -user "${u}")" + if [ -n "${f}" ]; then + >&2 printf 'Could not privatise the repository: '\ +'some files do not belong to %s\n' "${u}" + die + fi + + fgperm="$(file_grp_priv_perm)" + dgperm="$(dir_grp_priv_perm)" - elif [ -f "$1"/hooks/post-update.sample ]; then + chown -R :"${g}" "$1" + find "$1" -type f -exec chmod "${fgperm}" {} \; + find "$1" -type d -exec chmod "${dgperm}" {} \; + cd -- "$1" + git config --local core.sharedRepository false + cd -- "${OLDPWD}" +) + +dumb() # git_dir +( + if [ -f "$1"/hooks/post-update.sample ]; then mv "$1"/hooks/post-update.sample "$1"/hooks/post-update cd -- "$1"/hooks/ ./post-update || die "$?" - cd "${OLDPWD}" + cd -- "${OLDPWD}" fi -} +) + +nodumb() # git_dir +( + if [ -f "$1"/hooks/post-update ]; then + mv "$1"/hooks/post-update "$1"/hooks/post-update.sample + fi +) ######################################################################## # The script ######################################################################## -cur="$(pwd)" +nodumb=0 dumb=0 +privatise=0 group="" # Parse input arguments OPTIND=1 -while getopts ":ds:" opt; do +while getopts ":dDps:" opt; do case "${opt}" in + D) nodumb=1 ;; d) dumb=1 ;; + p) privatise=1 ;; s) group="${OPTARG}" ;; *) synopsis; die ;; esac @@ -136,7 +201,20 @@ fi cd -- "${git_dir}" git_dir="$(pwd)" -cd -- "${cur}" +cd -- "${OLDPWD}" -share "${repo}" "${group}" -dumb "${git_dir}" "${dumb}" +if [ -n "${group}" ]; then + share "${repo}" "${group}" +fi + +if [ ! "${privatise}" -eq 0 ]; then + privatise "${repo}" +fi + +if [ ! "${dumb}" -eq 0 ]; then + dumb "${git_dir}" +fi + +if [ ! "${nodumb}" -eq 0 ]; then + nodumb "${git_dir}" +fi diff --git a/git-repo.1 b/git-repo.1 @@ -12,7 +12,7 @@ .\" .\" You should have received a copy of the GNU General Public License .\" along with this program. If not, see <http://www.gnu.org/licenses/>. -.Dd October 1, 2024 +.Dd November 30, 2025 .Dt GIT-REPO 1 .Os .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -22,7 +22,7 @@ .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh SYNOPSIS .Nm -.Op Fl d +.Op Fl Ddp .Op Fl s Ar group .Ar directory .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -41,9 +41,19 @@ is indeed a bare git repository before updating its configuration. .Pp The options are as follows: .Bl -tag -width Ds +.It Fl D +Disable read access via the HTTP[S] protocol, i.e., disable dumb +transport. +This is the default configuration for the repository. .It Fl d Prepare the repository for read-only access via the HTTP[S] protocol, -i.e. for use over dumb transports. +i.e., for use over dumb transports. +.It Fl p +Make the repository private, i.e., define the group as the effective +group as returned by +.Ql id -gn +and restore the group's default permissions, most likely read-only +access. .It Fl s Ar group Share write access to the given .Ar group @@ -52,12 +62,14 @@ so that each member of the group can update the repository. Once shared, internal files belong to the users who created them, so the repository is effectively owned by the co-authors. Consequently, all users, including the one who created and shared the -repository, have write access via their group ID, rather than their user -ID, which is not sufficient to update all the files in the repository. -Privatising a shared repository by reconfiguring it without the -.Fl s -option is therefore impossible if a co-author has already pushed -updates. +repository, have write access via their group ID rather than their user +ID. +It is therefore impossible to privatize a shared repository +.Po +option +.Fl p +.Pc +or update its group if a co-author has already pushed updates. .El .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh EXAMPLES @@ -73,14 +85,14 @@ group: git repo -s users /path/to/the/git/repository.git .Ed .Pp -Same as above, but enables dump transports to allow read-only access via +Same as above, but enables dumb transports to allow read-only access via HTTP[S]: .Bd -literal -offset Ds -git repo -d -s users /path/to/the/git/repository.git +git repo -ds users /path/to/the/git/repository.git .Ed .Pp Create a regular bare repository. -Update it to enable dump transports. +Update it to enable dumb transports. Then give write access to the .Ar users group. @@ -90,8 +102,8 @@ access with .Bd -literal -offset Ds git repo /path/to/the/git/repository.git git repo -d /path/to/the/git/repository.git -git repo -d -s users /path/to/the/git/repository.git git repo -s users /path/to/the/git/repository.git +git repo -D /path/to/the/git/repository.git .Ed .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .Sh SEE ALSO