git-barepo

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

commit caf3604198b708f3629ec4bb550870f015b88947
parent cb0dff95e8e44908c7837ae00d38efd00fafcbb5
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Sat, 20 Jun 2026 15:49:23 +0200

git-publish: Grant the group write access to the HTML content

This feature is necessary to allow the "post-receive" hook to
automatically update the HTML code when a contributor other than the
user who published the repository updates it.

Note that the group with which the git repository is shared and the
group for the HTML content may be different. Therefore, contributors
must be members of both groups to update both the repository's content
and its display via the HTML pages.

Diffstat:
Mgit-publish | 51++++++++++++++++++++++++++++++++++++++++++---------
Mgit-publish.1 | 33++++++++++++++++++++++++++++++---
Mpost-receive.in | 5++++-
Mtest_publish.sh | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 150 insertions(+), 13 deletions(-)

diff --git a/git-publish b/git-publish @@ -43,6 +43,7 @@ dir_www="$(trim_trailing_slash "${GIT_PUBLISH_DIR_WWW:-/srv/www/git}")" force=0 # Force HTML generation delete=0 # Delete publication +group="" # Group with which write access is shared # Move the repository to the publication directory instead of creating a # link to it there. The original repository then becomes a symbolic link @@ -66,11 +67,11 @@ die() synopsis() { >&2 printf \ -'usage: %s [-dfm] [-g dir_git] [-u base_url] [-w dir_www] repository ...\n' \ - "${0##*/}" +'usage: %s [-dfm] [-g dir_git] [-s group] [-u base_url] [-w dir_www]\n'\ +' repository ...\n' "${0##*/}" } -check_resources() # path +check_resources() # path, group { # Copy resources using the cat command rather than cp to ensure # that the file is created in accordance with the umask settings. @@ -174,6 +175,7 @@ check_post_receive_hook() # - base_url: base URL under which the git HTML repository is exposed # - dir_git: directory where to publish the git repository # - dir_www: directory where to publish the git repository's HTML pages +# - group: group to grant write access to # - header: hook magic cookie # - hook: path to the hook template setup_post_receive_hook() @@ -186,10 +188,18 @@ setup_post_receive_hook() return 1 fi + # Grant write access to the defined group if any. + if [ -n "${group}" ]; then + _umask="002" + else + _umask="$(umask)" + fi + sed "2i ${header}" "${hook}" \ | sed -e "s#@DIR_GIT@#${dir_git}#g" \ -e "s#@DIR_WWW@#${dir_www}#g" \ -e "s#@BASE_URL@#${base_url}#g" \ + -e "s#@UMASK@#${_umask}#g" \ > "$1/hooks/post-receive" chmod 755 "$1/hooks/post-receive" @@ -201,6 +211,7 @@ setup_post_receive_hook() # Inputs: # - dir_git: directory where to publish the git repository # - dir_www: directory where to publish the git repository's HTML pages +# - group: group to grant write access to make_index() { _tmpfile="${TMPDIR:-/tmp}/git-publish-index.txt" @@ -229,13 +240,21 @@ make_index() _repo_list=$(find "${dir_git}" -path "${dir_git}/*.git" -prune | sort \ | join - "${_tmpfile}" | tr '\n' ' ') - if [ -z "${_repo_list}" ]; then - # No repo to index. Delete index file if any - rm -f "${dir_www}/index.html" - else + rm -f "${dir_www}/index.html" + + if [ -n "${_repo_list}" ]; then # Generate the index # shellcheck disable=SC2086 stagit-index ${_repo_list} > "${dir_www}/index.html" + + if [ -n "${group}" ]; then + # Grant write access to the specified group, if it exists. This + # allows the index to be updated automatically by the + # "post-receive" hook whenever a user in that group updates the + # repository. + chown :"${group}" "${dir_www}/index.html" + chmod 664 "${dir_www}/index.html" + fi fi rm -f "${_tmpfile}" @@ -247,6 +266,7 @@ make_index() # - dir_git: directory where to publish the git repository # - dir_www: directory where to publish the git repository's HTML pages # - force: force generation of HTML pages from scratch +# - group: group to grant write access to # - mv_repo: reverse the symbolic link publish_repo() { @@ -312,6 +332,14 @@ publish_repo() [ "${force}" -ne 0 ] && rm -rf "${_repo_www}" mkdir -p "${_repo_www}" + _umask="$(umask)" + if [ -n "${group}" ]; then + # Grant write access to the defined group if any. + chown -R :"${group}" "${_repo_www}" + chmod 2775 "${_repo_www}" + umask "002" + fi + # Generate HTML pages for the repository to be published # Make sure the links are relative to the repository directory to # avoid problems on the web server when it chroots @@ -323,6 +351,9 @@ publish_repo() ln -sf '../favicon.png' ./favicon.png cd "${OLDPWD}" + # Restore default permissions + umask "${_umask}" + setup_post_receive_hook "${_repo}" printf 'done\n' @@ -334,6 +365,7 @@ publish_repo() # - dir_git: directory where to publish the git repository # - dir_www: directory where to publish the git repository's HTML pages # - force: force generation of HTML pages from scratch +# - group: group to grant write access to publish() # list of repositories { printf '%s\n' "$@" | while read -r _i; do @@ -410,13 +442,14 @@ unpublish() ######################################################################## # Parse input arguments OPTIND=1 -while getopts ":dfg:mu:w:" opt; do +while getopts ":dfg:ms:u:w:" opt; do case "${opt}" in d) delete=1 ;; f) force=1 ;; - u) base_url="${OPTARG}" ;; g) dir_git="${OPTARG}" ;; # git directory m) mv_repo=1;; # Reverse symlink + s) group="${OPTARG}" ;; + u) base_url="${OPTARG}" ;; w) dir_www="${OPTARG}" ;; # WWW directory *) synopsis; die ;; esac diff --git a/git-publish.1 b/git-publish.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 May 27, 2026 +.Dd June 20, 2026 .Dt GIT-PUBLISH 1 .Os .\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" @@ -24,6 +24,7 @@ .Nm .Op Fl dfm .Op Fl g Ar dir_git +.Op Fl s Ar group .Op Fl u Ar base_url .Op Fl w Ar dir_www .Ar repository No ... @@ -85,6 +86,21 @@ For example, for security reasons, the server might serve public repositories via a chroot jail, thereby prohibiting the use of links that would take users outside it. .\"""""""""""""""""""""""""""""""""" +.It Fl s Ar group +Grant write access to the HTML content to the +.Ar group +specified as an argument. +This way, the web pages in a repository with shared write access can be +automatically updated by the post-receive hook, regardless of who +committed the changes, provided that the committer is a member of the +.Ar group . +.Pp +Note that the group associated with the HTML content is not necessarily +the same as that of the git repository. +Therefore, for a committer to be able to update both the content of the +git repository and that of its HTML content, they must be a member of +both groups. +.\"""""""""""""""""""""""""""""""""" .It Fl u Ar base_url Base URL to make links in the Atom feeds absolute. By default, the @@ -188,13 +204,24 @@ directory Make Atom feed URLs to repository commits absolute at URL https://www.example.com/git: .Bd -literal -offset Ds -git-publish -u https://www.example.com/git /path/to/repository.git +git-publish -u https://www.example.com/git /path/to/repo.git .Ed .Pp Undo the previous publication, i.e. delete the data that made the repository public: .Bd -literal -offset Ds -git-publish -d /path/to/repository.git +git-publish -d /path/to/repo.git +.Ed +.Pp +Grant write access to HTML content of the repository to members of the +.Ql www +group, so that this content can be updated automatically when the +repository is updated, provided that the committer has write access to +the git repository and is a member of the +.Ql www +group: +.Bd -literal -offset Ds +git-publish -s www -u https://www.example.com/git /path/to/repo.git .Ed .Pp Make public a set of bare git repositories: diff --git a/post-receive.in b/post-receive.in @@ -78,7 +78,7 @@ make_index() # Generate the index # shellcheck disable=SC2086 - stagit-index ${repo_list} > "${destdir}/index.html" + ( umask @UMASK@; stagit-index ${repo_list} > "${destdir}/index.html" ) rm -f "${tmpfile}" } @@ -119,6 +119,9 @@ fi make_index +# Update permission +umask @UMASK@ + # make pages. stagit -c "${cachefile}" -u "@BASE_URL@/${d}/" "${reposdir}/${r}" diff --git a/test_publish.sh b/test_publish.sh @@ -55,6 +55,48 @@ check_git_content() # repo-url rm -rf "${tmpdir:?}/${name}" } +get_group() # file +{ + # shellcheck disable=SC2012 + ls -dl "$1" | cut -d' ' -f4 +} + +get_group_perms() # file +{ + # shellcheck disable=SC2012 + ls -dl "$1" | cut -d' ' -f1 | cut -c5-7 +} + +check_www_content_perm() # group, [fperm, dperm] +{ + # Check file permissions + find -L "${srv}/www/" \ + -path "${srv}/www/${name}/*" -o -name "index.html" \ + | while read -r i; do + _group="$(get_group "${i}")" + _perm="$(get_group_perms "${i}")" + + if [ -L "${i}" ]; then # Do not check symbolic links + continue + fi + + [ "${_group}" = "$1" ] || return 1 + + if [ -z "$2" ] || [ -z "$3" ]; then + # Do not check group's permissions + continue + fi + + # Check group's permissions + if [ -d "${i}" ]; then + [ "${_perm}" = "$3" ] || return 1 # Directory + else + _p="$(printf '%s\n' "${_perm}" | cut -c1,2)" + [ "${_p}" = "$2" ] || return 1 # File + fi + done +} + check_www_content() { [ -f "${srv}/www/index.html" ] @@ -226,4 +268,36 @@ git publish -g "$(dirname "${git}")" -d "${srv}/git/${name}.git" [ ! -e "${git}" ] check_git_content "file://${srv}/git/${name}.git" +# Restore the original repository +mv "${srv}/git/${name}.git" "${git}" + +######################################################################## +# Grant write access to the WWW content +######################################################################## +# Find another group that is not the user's group, but to which the user +# belongs. Fails if such group is not found, even though this situation +# is perfectly valid from the system's perspective, because the test +# cannot actually be performed. +group="$(id -gn)" +group="$(id -Gn | tr ' ' '\n' | grep -v "${group}" | head -n1)" +[ -n "${group}" ] + +# Grant the group write access to the WWW content +git publish -s "${group}" "${git}" +check_www_content_perm "${group}" "rw" "rws" + +# Republishing without the -s option does not guarantee that the group's +# write permissions will be preserved or that they will be reset +git publish "${git}" +# shellcheck disable=SC2310 +! check_www_content_perm "${group}" "rw" "rws" || die 1 +group="$(id -gn)" +# shellcheck disable=SC2310 +! check_www_content_perm "${group}" || die 1 + +# Republish with the -f option ensure that the file permissions are set +# to the default values +git publish -f "${git}" +check_www_content_perm "${group}" + die 0