Skip to content

Instantly share code, notes, and snippets.

@oddlama
Created November 25, 2021 00:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oddlama/ca01f4be2d82a124511c3331f6dafeb8 to your computer and use it in GitHub Desktop.
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.
#!/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