I have a repo. It is a little bit chaotic...
I can let it be as is and just go on using proper flow from now on, but...
Can I at least fix some commit messages and keep old branches for history?)
It will also be good to keep old commit dates.
You do not need to move commits between branches or create new branches to make it look like repo was done in a proper flow from the beginning, but I won't stop you if you will.
And how to properly push a result to gitlab?
Here is script to generate a sample repo (run it inside an empty directory):
#!/bin/bash
git init
# how to rebase it?
# need to fix some bad commit messages
# and prefer to keep old commit dates
# main
git branch -m main
# init commit #0
echo '#0 init commit in main' >> file.txt
git add file.txt
git commit --message="#0 init: all is fine for now"
# commit #1 (problem here)
sleep 1s
echo 'commit #1 in main' >> file.txt
git add file.txt
git commit --message="#1 rebase and add a tag for this message"
# branching
# dev
git branch dev
git checkout dev
# feat
git branch feat
git checkout feat
# commit #2
sleep 1s
echo 'commit #2 in feat' >> file.txt
git add file.txt
git commit --message="#2 feat: good commit"
# commit #3
sleep 1s
echo 'commit #3 in feat' >> file.txt
git add file.txt
git commit --message="#3 feat: good commit"
# commit #4
sleep 1s
git checkout main
echo 'commit #4 in main' >> file_2.txt
git add file_2.txt
git commit --message="#4 fix: small fix in main"
# merge #5
sleep 1s
git checkout feat
git merge main --no-ff --message="#5 merge: main into feat"
# commit #6
sleep 1s
echo 'commit #6 in feat' >> file.txt
git add file.txt
git commit --message="#6 feat: good commit"
# commit #7
sleep 1s
echo 'commit #7 in feat' >> file.txt
git add file.txt
git commit --message="#7 rebase and add a tag for this message"
# commit #8
sleep 1s
echo 'commit #8 in feat' >> file.txt
git add file.txt
git commit --message="#8 feat: good commit"
# commit #9
sleep 1s
git checkout main
echo 'commit #9 in main' >> file_2.txt
git add file_2.txt
git commit --message="#9 style: small style fix in main"
# commit #10
sleep 1s
git checkout feat
echo 'commit #10 in feat' >> file.txt
git add file.txt
git commit --message="#10 feat: good commit"
# merge #11
sleep 1s
git checkout dev
git merge main --no-ff --message="#11 merge: main into dev"
# merge #12
sleep 1s
git merge feat --no-ff --message="#12 merge: feat into dev"
# commit #13
sleep 1s
echo 'commit #13 in dev' >> file_2.txt
git add file_2.txt
git commit --message="#13 refactor: good commit"
# commit #14
sleep 1s
echo 'commit #14 in dev' >> file_2.txt
git add file_2.txt
git commit --message="#14 rebase and add a tag for this message"
# commit #15
sleep 1s
echo 'commit #15 in dev' >> file_2.txt
git add file_2.txt
git commit --message="#15 refactor: good commit"
# merge #16
sleep 1s
git checkout main
git merge dev --no-ff --message="#16 merge: dev into main and call it a release_##"
# branching
# hotfix
git branch hotfix
git checkout hotfix
# commit #17
sleep 1s
echo 'commit #17 in hotfix' >> file_2.txt
git add file_2.txt
git commit --message="#17 fix: no more issues"
# merge #18
sleep 1s
git checkout main
git merge hotfix --no-ff --message="#18 merge: hotfix into main"
# merge #19
sleep 1s
git checkout dev
git merge hotfix --no-ff --message="#19 merge: hotfix into dev"
# end
git checkout main
I tried rebasing:
git rebase --interactive --keep-base --rebase-merges <SHA #0>
I expected it to rebuild all branches an just update messages...
But it keeps all old commits and adds copy of everything on top of the main
branch. And looks like this copy has no branches - it is all inside the main
.
And it is possible to restore commit date after rebase with:
git rebase --committer-date-is-author-date <SHA ##>
(one by one... for ~40 Commits)
git rebase
is not the right tool to use here. Rebase is intended to take a list of commits (of a single ref) and create copies of them, potientially changing their parent. If you want to update only the commit messages, but keep the structure of your history, git filter-branch
or git filter-repo
are better suited.
It's unclear how you want to "fix your messages" exactly, but --msg-filter
of filter-branch
receives the original message on standard input and should output the new message on standard output. You can pass any binary, shell script or function to the filter, including sed
, awk
, a python file, or a custom program that you wrote.
git filter-branch --msg-filter '
msg="$(cat)" # get original message
if printf "%s\n" "$msg" | grep -q "#7"; then
echo "#7 reworded message" # reword message
else
printf "%s\n" "$msg" # keep message
fi
' -- --all
If you have tags that you want to transfer to the new history and need updating, pass --tag-name-filter cat
.
With filter-repo
, you would use the --message-callback '...'
option, which accepts python code that will be the body of a function – it must return the new/updated message, e.g.
git filter-repo --message-callback '
if b"#7" in message:
return "#7 reworded message" # reword message
else:
return message # keep message
Use whatever complex logic you require.
An potentially simpler alternative is git replace
, which uses the replace mechanism to "hide" commits and to show their replacement commits from the replacement refs instead. You can make replacement commits permanent with filter-branch
.
git replace --edit commit-7 # edit commit 7
git replace --edit ... # edit more commits
git replace --edit ...
git filter-branch -- --all # make replacements permanent