Is there a way to replay commits from a source branch into a destination branch but taking into consideration changes made in .gitignore
in the destination branch?
Here is the scenario:
Say I branch out of master and start committing files, including .bin
files that should've been in .gitignore
but weren't. Is there any way I can go to master, commit "*.bin" to .gitignore
and then fix my topic branch when rebasing it (or some other automatic operation)? By fixing I mean remove the changesets of any .bin file, which are now ignored. This means 1) if a commit has a change on a.txt
and foo.bin
it should commit only a.txt
and 2) if a commit only has a change on foo.bin
it should be dropped altogether
The goal is to easily cleanup multiple mistakes only caught at Pull Request time.
Regular git rebase
didn't work. the mistakenly committed file remains committed even if in the (new) linear history of the repo, the gitignore pattern was there before the bad commit
CodePudding user response:
Is there a way to replay commits from a source branch into a destination branch but taking into consideration changes made in
.gitignore
in the destination branch?
Yes, it's possible, however, it would take some scripting work. (My example script in the loop below might even work for you as is.) Basically you would simulate the rebase but with a few steps in-between each commit. The algorithm would look something like this:
- Obtain the list of commits to re-write and store them in an array or list.
- Reset your source branch to the target. (The
.gitignore
file should now be in place.) - For each commit in the list: cherry-pick the commit with the
--no-commit
flag so you don't finish the commit, thenreset
to unstage the changes, thenadd
them back which heeds the.gitignore
instructions.
Here's a working example bash script (save it as a file and run it in an empty directory to test):
#!/bin/bash -v
git init
git branch -m main # rename to main in case that's not your default branch name
echo asdf > asdf.txt && git add . && git commit -m "Add asdf.txt"
git branch test-branch
echo "*.log" > .gitignore && git add . && git commit -m "Add .gitignore"
git switch test-branch
echo abc > abc.txt
echo def > def.log
git add . && git commit -m "Add abc.txt and def.log"
echo ghi > ghi.txt
echo hij > hij.log
git add . && git commit -m "Add ghi.txt and hij.log"
git log --all --graph --name-only
# Get all the commit IDs that would be rebased if we rebased test-branch onto main,
# and store them into an array in reverse order
declare -a commitIDs=(`git log main..test-branch --pretty=format:"%h" --reverse`)
# reset test-branch to main
git reset --hard main
# loop through the commitIDs and cherry-pick each one without committing
for i in "${commitIDs[@]}"
do
echo "About to cherry-pick commit $i"
git cherry-pick $i --no-commit # this leaves the commit staged
git reset # unstage so we can commit with the .gitignore
git add . # this doesn't add ignored files
git commit --reuse-message=$i
done
git log --all --graph --name-only
When finished, look at the output of the two git log
statements. The first one has 2 commits each with a .txt and .log file in it. The second one uses the .gitignore
file to remove all the .log files from those commits.
Note I used the previous commit messages when re-writing since that's usually what you would want. But I purposefully named my commits in such a way to highlight scenarios where you wouldn't want to do this, for example when the file name you are ignoring is specified in the commit message.
CodePudding user response:
No, you need to use git rm
to remove files that are you already tracking (or if you want to erase all traces of it git-filter-branch
). Here is the relevant bits from the gitignore man page (notice the last sentence):
A gitignore file specifies intentionally untracked files that Git should ignore. Files already tracked by Git are not affected