Using @larsks's idea, I achieved the goal using the following commands. I'm leaving it here for reference.
- Create an empty branch [1]:
git switch --orphan new_history
- Rebase the main branch onto it, executing pre-commit hook on every commit:
git rebase --root main --exec .git/hooks/pre-commit -X theirs
Note that I used theirs
merge strategy, so that in case of conflicts, the version from the main branch would be used ([2], [3]).
- After that, the commit date was set to today in all commits. To fix that, I ran ([4]):
git filter-branch --env-filter 'export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"'
Here is my pre-commit
script:
#!/bin/sh
set -x
IFS=$'\n'
files=$(git diff-tree --no-commit-id --name-status -r HEAD | grep -v ^D | cut -c3-)
if [ "$files" != "" ]
then
for f in $files
do
if [[ "$f" =~ [.]md$ ]]
then
# Add a linebreak to the file if it doesn't have one
if [ "$(tail -c1 $f)" != '\n' ]
then
echo >> $f
fi
# Remove trailing whitespace if it exists
if grep -q "[[:blank:]]$" $f
then
sed -i "" -e $'s/[ \t]*$//g' $f
fi
# Remove blank lines
python3 ~/bin/remove_empty_lines_md.py $f $f
fi
done
fi
unset IFS
git add -A
git commit --amend --no-edit --no-verify
Note that --no-verify
in the end is important (it skips hooks), otherwise there is an infinite loop.
For completeness, here is the remove_empty_lines_md.py
script:
def remove_empty_lines(text):
r"""
>>> text = "\n\n- test\n - test2\n\n- test3\n\n\n cointinue\n\n- test 4\n\n- test 5\n - test 6\n continue\n continue\n\n\n\n test7\n\n\n tset8\n\n\n- test 9\n\n\n- final\n\n\n"
>>> remove_empty_lines(text)
'- test\n - test2\n- test3\n\n\n cointinue\n- test 4\n- test 5\n - test 6\n continue\n continue\n\n\n\n test7\n\n\n tset8\n- test 9\n- final\n'
"""
new_lines = []
buffer = []
for i, line in enumerate(text.split('\n')):
if not line.strip():
buffer.append(line)
else:
if buffer:
if line.lstrip() and line.lstrip()[0] != '-':
new_lines.extend(buffer)
buffer = []
new_lines.append(line)
return '\n'.join(new_lines) + '\n'
if __name__ == '__main__':
import doctest
import sys
doctest.testmod()
filename_in, filename_out = sys.argv[1], sys.argv[2]
with open(filename_in) as fin:
text = fin.read()
with open(filename_out, 'w') as fout:
fout.write(remove_empty_lines(text))