Logic changes within the hook. Expand glob entries in the extra list.
This commit is contained in:
parent
cafe074f3f
commit
522b711313
2 changed files with 105 additions and 64 deletions
|
|
@ -151,4 +151,6 @@ To add paths to the "extra" files database, use:
|
||||||
```
|
```
|
||||||
Where `<path>` is a path relative to the repository root.
|
Where `<path>` is a path relative to the repository root.
|
||||||
|
|
||||||
|
The `<path>` is expanded while being processed, so may contain bash pathname glob characters.
|
||||||
|
|
||||||
Old paths (that no longer exist on the filesystem) stored in the `.gitattributesdb-extra` file are ignored when commiting.
|
Old paths (that no longer exist on the filesystem) stored in the `.gitattributesdb-extra` file are ignored when commiting.
|
||||||
|
|
|
||||||
167
gitattributesdb
167
gitattributesdb
|
|
@ -1,14 +1,14 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Version: 0.0.2
|
# Version: 0.1.0
|
||||||
# Copyright (c) 2023:
|
# Copyright (c) 2023-2024:
|
||||||
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
# Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
||||||
# Licensed under the terms of the GNU General Public License version 3.
|
# Licensed under the terms of the GNU General Public License version 3.
|
||||||
|
|
||||||
# Defaults.
|
# Defaults.
|
||||||
DB_FILE=".gitattributesdb" # Database file, relative to the repository root.
|
DB_FILE=".gitattributesdb" # Database file, relative to the repository root.
|
||||||
DB_EXTRA=".gitattributesdb-extra" # List of base64 encoded filenames (one per line) to also store/restore attributes for.
|
DB_EXTRA=".gitattributesdb-extra" # List of base64 encoded paths (one per line) to also store/restore attributes for.
|
||||||
# To add entries to this file, use: printf "%s" "<filename>" | base64 -w 0 >>.gitattributesdb-extra
|
# To add entries to this file, use: printf "%s" "<path>" | base64 -w 0 >>.gitattributesdb-extra
|
||||||
# Where '<filename>' is relative to the repository root.
|
# Where '<path>' is relative to the repository root.
|
||||||
|
|
||||||
# Variables.
|
# Variables.
|
||||||
declare -A DB_ACLS DB_ATIMES DB_MODES DB_MTIMES DB_OWNERSHIPS DB_XATTRS
|
declare -A DB_ACLS DB_ATIMES DB_MODES DB_MTIMES DB_OWNERSHIPS DB_XATTRS
|
||||||
|
|
@ -38,8 +38,8 @@ show_help() {
|
||||||
#........1.........2.........3.........4.........5.........6.........7.........8
|
#........1.........2.........3.........4.........5.........6.........7.........8
|
||||||
cat <<-EOF
|
cat <<-EOF
|
||||||
Usage: $SCRIPT [options] <hook name>
|
Usage: $SCRIPT [options] <hook name>
|
||||||
Store and restore file attributes for files within the git repository from a
|
Store and restore attributes for paths within the git repository from a database
|
||||||
database stored within the repository itself.
|
stored within the repository itself.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-h|--help Display this help page.
|
-h|--help Display this help page.
|
||||||
|
|
@ -49,36 +49,64 @@ show_help() {
|
||||||
full installation/usage instructions.
|
full installation/usage instructions.
|
||||||
|
|
||||||
This program is intended to be called from git hooks, rather than directly.
|
This program is intended to be called from git hooks, rather than directly.
|
||||||
|
|
||||||
|
Copyright (c) 2023-2024:
|
||||||
|
Darren 'Tadgy' Austin <darren (at) afterdark.org.uk>
|
||||||
|
Licensed under the terms of the GNU General Public License version 3.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to read the database into an array.
|
# Function to read the database into an array.
|
||||||
read_db() {
|
read_db_entries() {
|
||||||
local ACL ATIME FILENAME MODE MTIME OWNERSHIP XATTR
|
local ACL ATIME MODE MTIME OWNERSHIP PATHNAME XATTR
|
||||||
|
|
||||||
# Do nothing if the DB file doesn't exist.
|
# Do nothing if the DB file doesn't exist, isn't a regular file or is empty.
|
||||||
[[ ! -e "$DB_FILE" ]] && return 0
|
[[ ! -f "$DB_FILE" ]] || [[ ! -s "$DB_FILE" ]] && return 0
|
||||||
|
|
||||||
# Read the file.
|
# Read the file.
|
||||||
while read -r FILENAME MTIME ATIME OWNERSHIP MODE ACL XATTR; do
|
while read -r PATHNAME MTIME ATIME OWNERSHIP MODE ACL XATTR; do
|
||||||
# Store the attributes in arrays.
|
# Store the attributes in arrays.
|
||||||
DB_MTIMES[$FILENAME]="$MTIME"
|
DB_MTIMES[$PATHNAME]="$MTIME"
|
||||||
DB_ATIMES[$FILENAME]="$ATIME"
|
DB_ATIMES[$PATHNAME]="$ATIME"
|
||||||
DB_OWNERSHIPS[$FILENAME]="$OWNERSHIP"
|
DB_OWNERSHIPS[$PATHNAME]="$OWNERSHIP"
|
||||||
DB_MODES[$FILENAME]="$MODE"
|
DB_MODES[$PATHNAME]="$MODE"
|
||||||
DB_ACLS[$FILENAME]="$ACL"
|
DB_ACLS[$PATHNAME]="$ACL"
|
||||||
DB_XATTRS[$FILENAME]="$XATTR"
|
DB_XATTRS[$PATHNAME]="$XATTR"
|
||||||
done < <(grep -Ev '^(#|$)' "$DB_FILE")
|
done < <(grep -Ev '^(#|$)' "$DB_FILE")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to store file attributes into the database.
|
# Function to store path attributes into the database.
|
||||||
|
add_db_entry() {
|
||||||
|
local ERR=0
|
||||||
|
|
||||||
|
[[ -z "$1" ]] && return 1
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||||
|
# On Linux, we can handle ACLs and xattrs too.
|
||||||
|
ACL="$(getfacl -cEsp -- "$1" 2>/dev/null | base64 -w 0 2>/dev/null)"
|
||||||
|
XATTR="$(getfattr -dhe base64 -- "$1" 2>/dev/null | base64 -w 0 2>/dev/null)"
|
||||||
|
printf "%s %s %s %s\\n" "$(printf "%s" "$1" | base64 -w 0 2>/dev/null)" "$(stat --printf '%.9Y %.9X %U:%G %.4a' -- "$1" 2>/dev/null)" "${ACL:--}" \
|
||||||
|
"${XATTR:--}" >>"$DB_TMP" || { warn "Failed to add database entry: $1"; ERR=1; }
|
||||||
|
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||||
|
# Darwin just has to be different, so no ACLs or xattrs.
|
||||||
|
# Use the full path to Darwin's stat, in case there's a macports/brew/etc version installed.
|
||||||
|
printf "%s %s\\n" "$(printf "%s" "$1" | base64 -b 0 2>/dev/null)" "$(/usr/bin/stat -f '%Fm %Fa %Su:%Sg %Mp%Lp' -- "$1" 2>/dev/null)" >>"$DB_TMP" || \
|
||||||
|
{ warn "Failed to add database entry: $1"; ERR=1; }
|
||||||
|
else
|
||||||
|
error "Unsupported platform: $PLATFORM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "$ERR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process the paths to add to the database.
|
||||||
store_attributes() {
|
store_attributes() {
|
||||||
local ACL COUNT=0 DB_TMP EXTRA FILE XATTR
|
local ACL ADD_COUNT=0 DB_TMP ERR_COUNT=0 EXTRA NAME PATHNAME XATTR
|
||||||
|
|
||||||
# Informational message.
|
# Informational message.
|
||||||
log "Storing file attributes into database"
|
log "Storing path attributes into database"
|
||||||
|
|
||||||
# Use a temporary file for the new database.
|
# Use a temporary file for the new database.
|
||||||
DB_TMP="$(mktemp "$DB_FILE.XXXXXX" 2>/dev/null)" || error "Failed to create temporary database file"
|
DB_TMP="$(mktemp "$DB_FILE.XXXXXX" 2>/dev/null)" || error "Failed to create temporary database file"
|
||||||
|
|
@ -91,30 +119,40 @@ store_attributes() {
|
||||||
printf "# %s\\n\\n" "Do not manually edit this file - any changes will be overwritten." >>"$DB_TMP"
|
printf "# %s\\n\\n" "Do not manually edit this file - any changes will be overwritten." >>"$DB_TMP"
|
||||||
|
|
||||||
# Create the database.
|
# Create the database.
|
||||||
while read -r -d $'\0' FILE; do
|
while read -r -d $'\0' PATHNAME; do
|
||||||
# No need to process the database files themselves.
|
# No need to process the database files themselves.
|
||||||
[[ "$FILE" == "$DB_FILE" ]] || [[ "$FILE" == "$DB_EXTRA" ]] && continue
|
[[ "$PATHNAME" == "$DB_FILE" ]] || [[ "$PATHNAME" == "$DB_EXTRA" ]] && continue
|
||||||
|
|
||||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
# Add the path's attributes to the database.
|
||||||
# On Linux, we can handle ACLs and xattrs too.
|
if add_db_entry "$PATHNAME"; then
|
||||||
ACL="$(getfacl -cEsp -- "$FILE" 2>/dev/null | base64 -w 0 2>/dev/null)"
|
(( ADD_COUNT++ ))
|
||||||
XATTR="$(getfattr -dhe base64 -- "$FILE" 2>/dev/null | base64 -w 0 2>/dev/null)"
|
else
|
||||||
printf "%s %s %s %s\\n" "$(printf "%s" "$FILE" | base64 -w 0 2>/dev/null)" "$(stat --printf '%.9Y %.9X %U:%G %.4a' -- "$FILE" 2>/dev/null)" \
|
(( ERR_COUNT++ ))
|
||||||
"${ACL:--}" "${XATTR:--}" >>"$DB_TMP"
|
|
||||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
|
||||||
# Darwin just has to be different, so no ACLs or xattrs.
|
|
||||||
# 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 -b 0 2>/dev/null)" "$(/usr/bin/stat -f '%Fm %Fa %Su:%Sg %Mp%Lp' -- "$FILE" 2>/dev/null)" >>"$DB_TMP"
|
|
||||||
fi
|
fi
|
||||||
|
done < <(git ls-files -z --full-name -- . 2>/dev/null)
|
||||||
|
while read -r -d $'\0' PATHNAME; do
|
||||||
|
# PATHNAME should not be quoted - it needs to be expanded.
|
||||||
|
for NAME in $PATHNAME; do
|
||||||
|
# If the path doesn't exist, ignore it.
|
||||||
|
[[ ! -e "$NAME" ]] && continue
|
||||||
|
|
||||||
(( COUNT++ ))
|
# No need to process the database files themselves.
|
||||||
done < <(git ls-files -z --full-name -- . 2>/dev/null; while read -r EXTRA; do printf "%s\\0" "$(printf "%s" "$EXTRA" | base64 -d 2>/dev/null)"; done < \
|
[[ "$NAME" == "$DB_FILE" ]] || [[ "$NAME" == "$DB_EXTRA" ]] && continue
|
||||||
<(grep -Ev '^(#|$)' "$DB_EXTRA" 2>/dev/null))
|
|
||||||
|
# Add the path's attributes to the database.
|
||||||
|
if add_db_entry "$NAME"; then
|
||||||
|
(( ADD_COUNT++ ))
|
||||||
|
else
|
||||||
|
(( ERR_COUNT++ ))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done < <(while read -r EXTRA; do printf "%s%b" "$(printf "%s" "$EXTRA" | base64 -d 2>/dev/null)" "\\0"; done < <(grep -Ev '^(#|$)' "$DB_EXTRA" 2>/dev/null))
|
||||||
|
|
||||||
# Move the temporary file into place.
|
# 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"; }
|
mv -- "$DB_TMP" "$DB_FILE" 2>/dev/null || { rm -f -- "$DB_TMP"; error "Failed to move database temporary file into place"; }
|
||||||
|
|
||||||
log "$COUNT entries stored"
|
(( ADD_COUNT > 0 )) && log "$ADD_COUNT entries stored"
|
||||||
|
(( ERR_COUNT > 0 )) && warn "$ERR_COUNT failied entries"
|
||||||
|
|
||||||
# Add the databases themselves to the commit.
|
# 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_EXTRA" 2>/dev/null # OK to fail silently.
|
||||||
|
|
@ -123,57 +161,58 @@ store_attributes() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to restore file attributes from the database.
|
# Function to restore path attributes from the database.
|
||||||
restore_attributes() {
|
restore_attributes() {
|
||||||
local COUNT=0 FILE ID
|
local COUNT=0 ID PATHNAME WARN
|
||||||
|
|
||||||
# Informational message.
|
# Informational message.
|
||||||
log "Restoring file attributes from database"
|
log "Restoring path attributes from database"
|
||||||
|
|
||||||
# Read the database.
|
# Read the database.
|
||||||
read_db
|
read_db_entries
|
||||||
|
|
||||||
# While Darwin supports ACLs, there is no standard output and input format for them - don't even try.
|
# While Darwin supports ACLs, there is no standard output and input format for them - don't even try.
|
||||||
[[ "$PLATFORM" == "Darwin" ]] && warn "Not restoring ACLs or xattrs on Darwin"
|
[[ "$PLATFORM" == "Darwin" ]] && warn "Not restoring ACLs or xattrs on Darwin"
|
||||||
|
|
||||||
# Restore from the read database.
|
# Restore from the read database.
|
||||||
while read -r ID; do
|
while read -r ID; do
|
||||||
# Decode the filename from the array ID.
|
# Decode the path name from the array ID.
|
||||||
FILE="$(printf "%s" "$ID" | base64 -d 2>/dev/null)" || { warn "Failed to decode filename: $ID"; continue; }
|
PATHNAME="$(printf "%s" "$ID" | base64 -d 2>/dev/null)" || { warn "Failed to decode path: $ID"; continue; }
|
||||||
|
|
||||||
# Ignore empty filenames, or non-existant files.
|
# Ignore empty path names, or non-existant paths.
|
||||||
[[ -z "$FILE" ]] || [[ ! -e "$FILE" ]] && continue
|
[[ -z "$PATHNAME" ]] || [[ ! -e "$PATHNAME" ]] && continue
|
||||||
|
|
||||||
# Don't restore attributes for symlinks.
|
# Don't restore attributes for symlinks.
|
||||||
[[ -L "$FILE" ]] && warn "Not restoring attributes for symlink: $FILE" && continue
|
[[ -L "$PATHNAME" ]] && warn "Not restoring attributes for symlink: $PATHNAME" && continue
|
||||||
|
|
||||||
# Restore ownerships.
|
# Restore ownerships.
|
||||||
chown -- "${DB_OWNERSHIPS[$ID]}" "$FILE" 2>/dev/null || warn "Failed to restore ownership: $FILE"
|
chown -- "${DB_OWNERSHIPS[$ID]}" "$PATHNAME" 2>/dev/null || { warn "Failed to restore ownership: $PATHNAME"; WARN=1; }
|
||||||
|
|
||||||
# Store mode.
|
# Restore mode.
|
||||||
chmod -- "${DB_MODES[$ID]}" "$FILE" 2>/dev/null || warn "Failed to restore permissions: $FILE"
|
chmod -- "${DB_MODES[$ID]}" "$PATHNAME" 2>/dev/null || { warn "Failed to restore permissions: $PATHNAME"; WARN=1; }
|
||||||
|
|
||||||
# Restore ACLs on Linux.
|
# Restore {a,m}times (and ACLs on Linux).
|
||||||
if [[ "$PLATFORM" == "Linux" ]]; then
|
if [[ "$PLATFORM" == "Linux" ]]; then
|
||||||
touch -m --date="$(date --date="19700101 00:00:00 + ${DB_MTIMES[$ID]} seconds" +'%Y/%m/%d %H:%M:%S.%N' 2>/dev/null)" -- "$FILE" 2>/dev/null || \
|
touch -m --date="$(date --date="19700101 00:00:00 + ${DB_MTIMES[$ID]} seconds" +'%Y/%m/%d %H:%M:%S.%N' 2>/dev/null)" -- "$PATHNAME" 2>/dev/null || \
|
||||||
warn "Failed to restore mtime: $FILE"
|
{ warn "Failed to restore mtime: $PATHNAME"; WARN=1; }
|
||||||
touch -a --date="$(date --date="19700101 00:00:00 + ${DB_ATIMES[$ID]} seconds" +'%Y/%m/%d %H:%M:%S.%N' 2>/dev/null)" -- "$FILE" 2>/dev/null || \
|
touch -a --date="$(date --date="19700101 00:00:00 + ${DB_ATIMES[$ID]} seconds" +'%Y/%m/%d %H:%M:%S.%N' 2>/dev/null)" -- "$PATHNAME" 2>/dev/null || \
|
||||||
warn "Failed to restore atime: $FILE"
|
{ warn "Failed to restore atime: $PATHNAME"; WARN=1; }
|
||||||
[[ "${DB_ACLS[$ID]}" != "-" ]] && { printf "%s" "${DB_ACLS[$ID]}" | base64 -d 2>/dev/null | setfacl -M - -- "$FILE" 2>/dev/null || \
|
[[ "${DB_ACLS[$ID]}" != "-" ]] && { printf "%s" "${DB_ACLS[$ID]}" | base64 -d 2>/dev/null | setfacl -M - -- "$PATHNAME" 2>/dev/null || \
|
||||||
warn "Failed to restore ACLs: $FILE"; }
|
warn "Failed to restore ACLs: $PATHNAME"; WARN=1; }
|
||||||
[[ "${DB_XATTRS[$ID]}" != "-" ]] && { printf "%s" "${DB_XATTRS[$ID]}" | base64 -d 2>/dev/null | setfattr --restore=- 2>/dev/null || \
|
[[ "${DB_XATTRS[$ID]}" != "-" ]] && { printf "%s" "${DB_XATTRS[$ID]}" | base64 -d 2>/dev/null | setfattr --restore=- 2>/dev/null || \
|
||||||
warn "Failed to restore xattrs: $FILE"; }
|
warn "Failed to restore xattrs: $PATHNAME"; WARN=1; }
|
||||||
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
elif [[ "$PLATFORM" == "Darwin" ]]; then
|
||||||
touch -m -d "$(date -j -r "${DB_MTIMES[$ID]%.*}" +"%Y-%m-%dT%H:%M:%S.${DB_MTIMES[$ID]#*.}")" -- "$FILE" 2>/dev/null || \
|
touch -m -d "$(date -j -r "${DB_MTIMES[$ID]%.*}" +"%Y-%m-%dT%H:%M:%S.${DB_MTIMES[$ID]#*.}")" -- "$PATHNAME" 2>/dev/null || \
|
||||||
warn "Failed to restore mtime: $FILE"
|
{ warn "Failed to restore mtime: $PATHNAME"; WARN=1; }
|
||||||
touch -a -d "$(date -j -r "${DB_ATIMES[$ID]%.*}" +"%Y-%m-%dT%H:%M:%S.${DB_ATIMES[$ID]#*.}")" -- "$FILE" 2>/dev/null || \
|
touch -a -d "$(date -j -r "${DB_ATIMES[$ID]%.*}" +"%Y-%m-%dT%H:%M:%S.${DB_ATIMES[$ID]#*.}")" -- "$PATHNAME" 2>/dev/null || \
|
||||||
warn "Failed to restore atime: $FILE"
|
{ warn "Failed to restore atime: $PATHNAME"; WARN=1; }
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(( COUNT++ ))
|
(( COUNT++ ))
|
||||||
done < <(printf "%s\\n" "${!DB_OWNERSHIPS[@]}")
|
done < <(printf "%s\\n" "${!DB_OWNERSHIPS[@]}")
|
||||||
|
|
||||||
log "$COUNT entries restored"
|
# shellcheck disable=SC2015
|
||||||
|
[[ ! -v WARN ]] && log "$COUNT entries restored" || log "$COUNT entries restored (with warnings)"
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -197,11 +236,11 @@ case "$1" in
|
||||||
show_help
|
show_help
|
||||||
;;
|
;;
|
||||||
'post-checkout'|'post-merge')
|
'post-checkout'|'post-merge')
|
||||||
# Restore the file attributes from the database.
|
# Restore the path attributes from the database.
|
||||||
restore_attributes
|
restore_attributes
|
||||||
;;
|
;;
|
||||||
'pre-commit')
|
'pre-commit')
|
||||||
# Store the file attributes into the database.
|
# Store the path attributes into the database.
|
||||||
store_attributes
|
store_attributes
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue