Add gitattributesdb git hook script.
This commit is contained in:
commit
223b88afd7
1 changed files with 192 additions and 0 deletions
192
gitattributsdb
Executable file
192
gitattributsdb
Executable file
|
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env bash
|
||||
# Version: 0.0.2
|
||||
# Copyright (c) 2023:
|
||||
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
||||
# Licensed under the terms of the GNU General Public License version 3.
|
||||
|
||||
# Defaults.
|
||||
DB_FILE=".gitattributsdb" # Database file, relative to the repository root.
|
||||
DB_EXTRA=".gitattributsdb-extra" # List of base64 encoded filenames (one per line) to also store/restore attributes for.
|
||||
# To add entries to this file, use: printf "%s" "<filename>" | base64 -w 0 >>.gitattributsdb-extra
|
||||
# Where '<filename>' is relative to the repository root.
|
||||
|
||||
# Variables.
|
||||
declare -A DB_OWNERSHIPS DB_MODES DB_ACLS
|
||||
# shellcheck disable=SC2155
|
||||
declare PLATFORM="$(uname -s)"
|
||||
|
||||
# Function to output a log/info message.
|
||||
log() {
|
||||
printf "%s: %s\\n" "${0##*/}" "$*"
|
||||
}
|
||||
|
||||
# Function to output a warning message to stderr.
|
||||
warn() {
|
||||
log "Warning:" "$*" >&2
|
||||
}
|
||||
|
||||
# Function to output an error message to stderr and die.
|
||||
error() {
|
||||
log "Error:" "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Function to display help.
|
||||
show_help() {
|
||||
local SCRIPT="${0##*/}"
|
||||
|
||||
#........1.........2.........3.........4.........5.........6.........7.........8
|
||||
cat <<-EOF
|
||||
Usage: $SCRIPT [options] <hook name>
|
||||
Store and restore file attributes for files within the git repository from a
|
||||
database stored within the repository itself.
|
||||
|
||||
This program is intended to be called from git hooks, rather than directly.
|
||||
|
||||
# FIXME: Write some info about using as a submodule and as a hook in .githooks/.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Function to read the database into an array.
|
||||
read_db() {
|
||||
local FILENAME MODE OWNERSHIP ACL
|
||||
|
||||
# Do nothing if the DB file doesn't exist.
|
||||
[[ ! -e "$DB_FILE" ]] && return 0
|
||||
|
||||
# Read the file.
|
||||
while read -r FILENAME OWNERSHIP MODE ACL; do
|
||||
# Store the attributes in arrays.
|
||||
DB_OWNERSHIPS[$FILENAME]="$OWNERSHIP"
|
||||
DB_MODES[$FILENAME]="$MODE"
|
||||
DB_ACLS[$FILENAME]="$ACL"
|
||||
done < <(grep -Ev '^(#|$)' "$DB_FILE")
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to store file attributes into the database.
|
||||
store_attributes() {
|
||||
local COUNT=0 DB_TMP FILE X
|
||||
|
||||
# Informational message.
|
||||
log "Storing file attributes into database"
|
||||
|
||||
# Use a temporary file for the new database.
|
||||
DB_TMP="$(mktmp "$DB_FILE.XXXXXX" 2>/dev/null)" || error "Failed to create temporary database file"
|
||||
|
||||
# While Darwin supports ACLs, there is no standard output and input format for them - don't even try.
|
||||
[[ "$PLATFORM" == "Darwin" ]] && warn "Not storing ACLs on Darwin"
|
||||
|
||||
# File header.
|
||||
printf "# %s\\n" "This is the gitattributsdb database file." >"$DB_TMP"
|
||||
printf "# %s\\n\\n" "Do not manually edit this file - any changes will be overwritten." >>"$DB_TMP"
|
||||
|
||||
# Create the database.
|
||||
while read -r -d $'\0' FILE; do
|
||||
# No need to process the database files themselves.
|
||||
[[ "$FILE" == "$DB_FILE" ]] || [[ "$FILE" == "$DB_EXTRA" ]] && continue
|
||||
|
||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||
# On Linux, we can handle ACLs too.
|
||||
printf "%s %s %s\\n" "$(printf "%s" "$FILE" | base64 -w 0 2>/dev/null)" "$(stat --printf '%U:%G %.4a' -- "$FILE" 2>/dev/null)" \
|
||||
"$(getfacl -cEsp -- "$FILE" 2>/dev/null | base64 -w 0 2>/dev/null)" >>"$DB_TMP"
|
||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||
# Darwin just has to be different.
|
||||
# Use the full path to Darwin's stat, in case there's a macports/brew/etc version installed.
|
||||
printf "%s %s\\n" "$(printf "%s" "$FILE" | base64 2>/dev/null)" "$(/usr/bin/stat -f "%Su:%Sg %Mp%Lp" -- "$FILE" 2>/dev/null)" >>"$DB_TMP"
|
||||
fi
|
||||
|
||||
(( COUNT++ ))
|
||||
done < <(git ls-files -z --full-name -- . 2>/dev/null; while read -r X; do printf "%s\\0" "$(printf "%s" "$X" | base64 -d 2>/dev/null)"; done < \
|
||||
<(grep -Ev '^(#|$)' "$DB_EXTRA" 2>/dev/null))
|
||||
|
||||
# Move the temporary file into place.
|
||||
mv -- "$DB_TMP" "$DB_FILE" 2>/dev/null || { rm -f -- "$DB_TMP"; error "Failed to move database temporary file into place"; }
|
||||
|
||||
info "$COUNT entries stored"
|
||||
|
||||
# Add the databases themselves to the commit.
|
||||
git add --all -f -- "$DB_EXTRA" 2>/dev/null # OK to fail silently.
|
||||
git add --all -f -- "$DB_FILE" 2>/dev/null || error "Failed to add database files to commit"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Function to restore file attributes from the database.
|
||||
restore_attributes() {
|
||||
local COUNT=0 FILE ID
|
||||
|
||||
# Informational message.
|
||||
log "Restoring file attributes from database"
|
||||
|
||||
# Read the database.
|
||||
read_db
|
||||
|
||||
# While Darwin supports ACLs, there is no standard output and input format for them - don't even try.
|
||||
[[ "$PLATFORM" == "Darwin" ]] && warn "Not restoring ACLs on Darwin"
|
||||
|
||||
# Restore from the read database.
|
||||
while read -r ID; do
|
||||
# Decode the filename from the array ID.
|
||||
FILE="$(printf "%s" "$ID" | base64 -d 2>/dev/null)" || { warn "Failed to decode filename: $ID"; continue; }
|
||||
|
||||
# Ignore empty filenames, or non-existant files.
|
||||
[[ -z "$FILE" ]] || [[ ! -e "$FILE" ]] && continue
|
||||
|
||||
# Don't restore attributes for symlinks.
|
||||
[[ -L "$FILE" ]] && warn "Not restoring attributes for symlink: $FILE" && continue
|
||||
|
||||
# Restore ownerships.
|
||||
chown -- "${DB_OWNERSHIPS[$ID]}" "$FILE" 2>/dev/null || warn "Failed to restore ownership: $FILE"
|
||||
|
||||
# Store mode.
|
||||
chmod -- "${DB_MODES[$ID]}" "$FILE" 2>/dev/null || warn "Failed to restore permissions: $FILE"
|
||||
|
||||
# Restore ACLs on Linux.
|
||||
[[ "$PLATFORM" == "Linux" ]] && {
|
||||
printf "%s" "${DB_ACLS[$ID]}" | base64 -d 2>/dev/null | setfacl -M - "$FILE" 2>/dev/null || warn "Failed to restore ACL: $FILE"
|
||||
}
|
||||
|
||||
(( COUNT++ ))
|
||||
done < <(printf "%s\\n" "${!DB_OWNERSHIPS[@]}")
|
||||
|
||||
info "$COUNT entries restored"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Exit if bash isn't v4+.
|
||||
(( BASH_VERSINFO[0] < 4 )) && error "Bash v4 or later is required"
|
||||
|
||||
# Change to the root directory of the repository.
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
|
||||
[[ -z "$REPO_ROOT" ]] && error "Could not determine git repository root"
|
||||
pushd -- "$REPO_ROOT" >/dev/null 2>&1 || error "Failed to switch to git repository root"
|
||||
|
||||
# Parse command line.
|
||||
(( $# == 0 )) && {
|
||||
printf "%s: %s\\n" "${0##*/}" "missing argument" >&2
|
||||
printf "%s: %s %s\\n" "Try" "${0##*/}" "--help" >&2
|
||||
exit 1
|
||||
}
|
||||
case "$1" in
|
||||
'-h'|'--h'|'--help')
|
||||
show_help
|
||||
;;
|
||||
'post-checkout'|'post-merge')
|
||||
# Restore the file attributes from the database.
|
||||
restore_attributes
|
||||
;;
|
||||
'pre-commit')
|
||||
# Store the file attributes into the database.
|
||||
store_attributes
|
||||
;;
|
||||
*)
|
||||
printf "%s: %s: %s\\n" "${0##*/}" "invalid option" "$1" >&2
|
||||
printf "%s: %s %s\\n" "Try" "${0##*/}" "--help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue