Created
November 25, 2021 00:44
-
-
Save oddlama/ca01f4be2d82a124511c3331f6dafeb8 to your computer and use it in GitHub Desktop.
Resign git history and tags while preserving (or faking) the GPG signature date.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# This is free and unencumbered software released into the public domain. | |
# This script (re)signs all commits in a git repository, while preserving | |
# commit dates, even in the gpg signature. If a commit didn't have a signature, | |
# the signature time is faked to be 3-15 seconds after the commit. | |
# | |
# usage: | |
# | |
# ./git-resign-history.sh | |
# | |
# Resign the history of the git repository. This is an all in one command to | |
# collect signature times and resign all commits. This will clean the temporary | |
# directory automatically. | |
# | |
# ./git-resign-history.sh collect | |
# | |
# Just collect existing signature dates. Useful if you want to make modifications | |
# in-between collecting and resigning. Times are saved as <commit_id>:<unix timestamp> | |
# pairs in "$tmpdir/timedb". Be careful that your commit id's will change when you do | |
# other history-rewrites in between, so be sure to update this mapping in that case. | |
# | |
# ./git-resign-history.sh resign | |
# | |
# Use the collected times to resign the history. If a commit has no associated | |
# time, a random time offset of [3,15) seconds will be added to the commit time, | |
# and becomes the signature time. This doesn't delete the temporary directory. | |
set -uo pipefail | |
# Any temporary directory that allows executing files (/tmp doesn't always work) | |
# Will automatically be created and removed. | |
tmpdir="$HOME/tmp-gpg-faketime" | |
mkdir -p "$tmpdir" | |
if [[ "${1-collect}" == "collect" ]]; then | |
# Collect existing signing dates of commits | |
commits=($(git rev-list HEAD)) | |
i=0 | |
echo -n > "$tmpdir"/timedb | |
for cid in "${commits[@]}"; do | |
printf "Collecting existing signature dates (%3d/%3d)\r" "$i" "${#commits[@]}" | |
existing_iso="$(git show -s --format="%GG" "$cid" | grep "Signature made" | cut -d" " -f4-)" | |
if [[ -n "$existing_iso" ]]; then | |
echo -n "$cid:" >> "$tmpdir"/timedb | |
date -d "$existing_iso" "+%s" >> "$tmpdir"/timedb | |
fi | |
((++i)) | |
done | |
fi | |
if [[ "${1-resign}" == "resign" ]]; then | |
# Create gpg wrapper that forces GPG to use a faked system time. | |
# If the timedb includes an entry for the current commit, we will | |
# use this time, otherwise the processed commit's commit time | |
# will be used with a random offset of [3,15) seconds. | |
cat > "$tmpdir"/gpg <<EOF | |
#!/bin/bash | |
min_offset=3 | |
max_offset=15 | |
offset=\$((min+(RANDOM%(max_offset-min_offset)))) | |
commit_time="\$(grep "\$GIT_COMMIT" "$tmpdir"/timedb)" | |
commit_time="\${commit_time##*:}" | |
if [[ -z "\$commit_time" ]]; then | |
commit_time="\${GIT_COMMITTER_DATE%% *}" | |
commit_time="\${commit_time:1}" | |
commit_time="\$((offset + commit_time))" | |
fi | |
exec /usr/bin/gpg --ignore-time-conflict --ignore-valid-from --faked-system-time "\$commit_time!" "\$@" | |
EOF | |
# Make the script executable and resign the all commits | |
chmod +x "$tmpdir"/gpg | |
git filter-branch --tag-name-filter cat --commit-filter 'PATH="'"$tmpdir"':$PATH" git commit-tree -S "$@" ;' -- --all | |
fi | |
if [[ "$1" == "" ]]; then | |
# Remove temporary files and directory | |
rm -d "$tmpdir"/{gpg,timedb,} | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment