Looking to squash commits, so that a publicly facing project doesn't have meaningless commit messages like "temp" or "temp123".
I am looking for a way to use git reset --soft HEAD~5
, but instead of randomly picking the number 5, I want to go back through the Git logs and find all the the commits that do not match a pattern, and once I hit the pattern I stop. So let's say my git log
commit messages look like:
temp123
fooGit
temp
fml
temp24
tmp69
tttoday
publish/release:xyz
so I would squash the top 7 most recent commits, but leave everything "below" publish/release:xyz
.
For fairly complicated reasons, git merge --squash
issued from the public facing branch won't work for me because I am end up deleting or renaming files quite a bit in a project on the private branch and so there are just too many merge conflicts. It seems to me, after trying it several ways, that it's best to push to the public facing branch with -f, and not worry about conflicts in that way.
I think the better approach in my case is to squash commits like so:
# on private "dev" branch
# make a bunch of changes, rename files, yadda yadda
git add . && git add -A && git commit -am "temp" &&
git checkout -b squash_branch # we do squashing on this branch to be safer
git reset --soft head~15 # apparently this undoes commits for the last 15 commits
git commit -am "A serious commit message"
git push -u public master -f # overwrite public master
this way, I never have to resolve conflicts between the public facing branch and the private development branch, which can be super pointless and annoying from my experience.
However, the problem I am trying to solve has to do with the number 15 above. I am simply just guessing about how many commits to "squash". I would rather know the exact number to squash.
Perhaps I can do a squash of all commits that do not have a Git commit message that fits a certain pattern? So when I run the squash, I create a commit message with a certain pattern, let's say it's "XYZ". Could I run git reset --soft HEAD~(some git commit pattern matching to find the count)
?
Also one more question. After doing the squash (git reset --soft
) on the squash_branch
, should I merge that branch back into the private dev
branch?
You could find the count, but this is a little bit dangerous as it will be the wrong number if there are merge commits present in the chain (not illustrated here):
git rev-list --count HEAD^{/publish/release:xyz}..HEAD
The notation rev^{/text}
means "starting from the given rev
, search each commit message (a la git log --grep
) for the given text". So the first expression matches the commit with publish/release:xyz
in its commit message. The ..
syntax then uses that commit's ID to remove that commit and earlier commits from the set of commits selected by the name HEAD
. The --count
then gives us a count of remaining commits. Hence if your graph looks like this:
...--o--*--o--o--o--o <-- HEAD
where *
is the commit with the text in it, this discards commit *
and the other further-left commits, leaving the four final o
commit nodes to which HEAD
points. The --count
then counts those four commits and prints 4
.
Overall, though, this is a bad strategy. It's much better to start by marking the point with a branch or tag name. A branch or tag name identifies a commit by pointing to it. Instead of trying to find commit *
by pattern, just mark it when you make it:
git tag xyz
Now you have this:
tag:xyz
|
v
...--o--*--o--o--o <-- HEAD -> dev
and xyz..HEAD
gets you the right set of commits, and you can git reset --soft xyz
to make the branch label dev
point back to commit *
:
tag:xyz
|
v
...--o--* <-- HEAD -> dev
\
o--o--o [only findable via reflogs]
Once you are done with this, simply delete the tag.
(Again, you can use a branch or tag name; use whichever you prefer; I illustrated this with a tag because a tag name is not supposed to move, while branch names do move. They move automatically, in fact, whenever you git checkout
the branch and then make commits or use git reset
. Tag names point to one particular commit and just stay there.)
Note, by the way, that you can use the search notation while creating a tag:
git tag xyz HEAD^{/publish/release:xyz}
That is, you identify which commit to tag when creating the tag name. (The same is true for branch names, actually.) You can then see this tag with git log --decorate
, and graphical viewers like gitk
generally show you which commits have which tags as well.
(Searching and tagging is a good way to transition to this better, at least in theory, habit.)