diff --git a/forgejo/README.md b/forgejo/README.md index 5a6e8b9..402eb5e 100644 --- a/forgejo/README.md +++ b/forgejo/README.md @@ -268,21 +268,32 @@ automatically merge IFC pull requests instead of marking them as conflicted. ### How Forgejo checks mergeability -Forgejo (with git ≥ 2.38) uses `git merge-tree --write-tree` to perform a -trial merge in the bare repository. This command **does** invoke configured -merge drivers — confirmed by testing. With ifcmerge set up correctly, -the "can be automatically merged" indicator on a PR reflects ifcmerge's -actual result, not a naive text-conflict check. +Forgejo checks PR mergeability by calling `git merge-file current base other` +directly for each conflicting file. This command bypasses gitattributes merge +drivers entirely — `/etc/gitconfig` and `core.attributesFile` have no effect +on this code path. -**Important:** bare repositories do not read committed `.gitattributes` files -from the tree. The merge driver must be declared via a system-wide -`core.attributesFile` setting in `/etc/gitconfig`. +The solution is a **git wrapper** at `/usr/bin/git` (with the original binary +moved to `/usr/bin/git.real`) that intercepts `merge-file` calls, detects IFC +files by their ISO-10303 STEP header, and delegates to `ifcmerge` instead. + +The `/etc/gitconfig` merge driver registration and `/etc/gitattributes` are +still useful for client-side `git merge` operations on developer machines, but +they do not affect Forgejo's server-side conflict check. + +**Important:** the git package in apt will overwrite `/usr/bin/git` on upgrade. +Reinstall the wrapper (`server-config/git-wrapper`) after any git package upgrade. ### Prerequisites -`ifcmerge` is a single Perl script from -[github.com/brunopostle/ifcmerge](https://github.com/brunopostle/ifcmerge). -Download and install it on the Forgejo server: +`ifcmerge` is a Perl script — install Perl's DateTime module first: + +```bash +apt install -y libdatetime-perl +``` + +Then download ifcmerge from +[github.com/brunopostle/ifcmerge](https://github.com/brunopostle/ifcmerge): ```bash curl -o /usr/local/bin/ifcmerge \ @@ -290,19 +301,24 @@ curl -o /usr/local/bin/ifcmerge \ chmod +x /usr/local/bin/ifcmerge ``` -Verify it is on `PATH` for the `forgejo` system user: +### Install the git wrapper + +The git wrapper intercepts `git merge-file` for IFC files and routes them to +ifcmerge. Install it by replacing the git binary: ```bash -sudo -u forgejo which ifcmerge +mv /usr/bin/git /usr/bin/git.real +cp forgejo/server-config/git-wrapper /usr/bin/git +chmod +x /usr/bin/git ``` -### Deploy the config files +### Deploy the config files (for client-side merges) ```bash -# System-wide gitattributes (the critical piece for bare repos) +# System-wide gitattributes sudo cp forgejo/server-config/gitattributes /etc/gitattributes -# Append merge driver registration + core.attributesFile to /etc/gitconfig +# Merge driver registration in /etc/gitconfig sudo git config --system include.path /path/to/ifcurl/forgejo/server-config/gitconfig-ifcmerge ``` diff --git a/forgejo/server-config/git-wrapper b/forgejo/server-config/git-wrapper new file mode 100644 index 0000000..d0866ec --- /dev/null +++ b/forgejo/server-config/git-wrapper @@ -0,0 +1,60 @@ +#!/bin/bash +# Drop this at /usr/bin/git (move original to /usr/bin/git.real). +# +# Forgejo checks PR mergeability by calling 'git merge-file current base other' +# directly. This bypasses gitattributes merge drivers entirely — there is no +# way to configure ifcmerge via /etc/gitconfig for this code path. +# +# This wrapper intercepts 'git merge-file' calls where the file being merged +# is an IFC/STEP file (identified by the ISO-10303 header) and delegates to +# ifcmerge instead of git's built-in text merge. +# +# Installation: +# mv /usr/bin/git /usr/bin/git.real +# cp git-wrapper /usr/bin/git +# chmod +x /usr/bin/git +# +# The wrapper is transparent for all other git commands. +# +# NOTE: apt upgrades to the git package will overwrite /usr/bin/git. +# After any git package upgrade, reinstall this wrapper. + +is_merge_file=false +for arg in "$@"; do + [ "$arg" = "merge-file" ] && is_merge_file=true && break +done + +if $is_merge_file; then + files=() + past_subcommand=false + skip_next=false + for arg in "$@"; do + if ! $past_subcommand; then + [ "$arg" = "merge-file" ] && past_subcommand=true + continue + fi + if $skip_next; then skip_next=false; continue; fi + case "$arg" in + -L|--marker-size) skip_next=true ;; + -p|-q|--quiet|--ours|--theirs|--union|--diff3|--zdiff3) ;; + --) ;; + -*) ;; + *) files+=("$arg") ;; + esac + done + + if [ ${#files[@]} -eq 3 ]; then + current="${files[0]}" + base="${files[1]}" + other="${files[2]}" + if [ -f "$current" ] && head -c 20 "$current" 2>/dev/null | grep -q "ISO-10303"; then + dir=$(dirname "$current") + [[ "$base" != /* ]] && base="$dir/$base" + [[ "$other" != /* ]] && other="$dir/$other" + ifcmerge --prioritise-local "$base" "$current" "$other" "$current" + exit $? + fi + fi +fi + +exec /usr/bin/git.real "$@"