git log --no-merges git log --no-merges --first-parent
I don't get the "no merge commits" argument - git log has dozens of options allowing you to bend it to your use case.To address the grand parent that "a clean history is has no merge commits" I would argue that a clean history is also a lie if you're working in a branching workflow (hint: you should be working in that most of the time).
If you want to avoid (note: not eliminate) merge commits then make sure to rebase against master before merging, and then ensure merges are fast-forward merges.
* merge commit
|\
| * branch work
| |
| * branch work
|/
*
If you squash to merge, typically you're also going to rebase it (equivalently, if it's more familiar, cherry-pick the squash onto the branch your 'merging' it into). * squash cherry-picked / rebased
| * both branch works squashed
| * | branch work
| | |
| * | branch work
|/_/
*
(In this case the target branch could have been fast-forwarded, but this also works if there's some other work on the mean time:) * squash cherry-picked / rebased
|
* something unrelated
| * both branch works squashed
| * | branch work
| | |
| * | branch work
|/_/
* git log --first-parent
This only shows the top level commits (either a direct commit or a merge commit) and doesn't show any of the subcommits in the branches.This gives us a very clean history, something like:
commit ce29331f7da82ce528ca6e437b8893248a842169
Merge: a14522cbf 936ef9f90
Author: Joe Sample <joe.sample@example.com
Date: Tue Jul 7 14:34:20 2020 -0500
ISS-2047 - Add ids to user invitation or creation
commit a14522cbf32487dc590c8b6f3332d3fc9371a640
Merge: d08342170 cb94a86b1
Author: Jane Dow <jane.doe@example.com
Date: Tue Jul 7 14:28:28 2020 -0500
ISS-2032 - If an enterprise is disabled, their api keys should be disabled
commit d083421702e3ff50bc4c62e85b687e172e4bfe76
Merge: 0d167d25e ba3900a4a
Author: Joe Sample <joe.sample@example.com>
Date: Tue Jul 7 14:25:41 2020 -0500
ISS-2045 - Return a reason of why the invitation was auto-cancelled
Then if I want to see everything that happened in the branch that was merged in, I can just run: git log d083421702e3ff50bc4c62e85b687e172e4bfe76^..d083421702e3ff50bc4c62e85b687e172e4bfe76
and see all the commits that were in that feature branch. No need to squash things or hide them.I really wish git, by default, showed commits under a merge as a tree, rather than as a flat list. This is what bazaar (bzr now breezy) did, and it made a lot more sense when looking at the history.
Reason for the feature squash: most of the context of the original commit(s) are meaningful only to the original dev who is free to keep that history locally (or share with others). There are time's it's convenient to structure the commits in a certain way, but this is often most useful at PR review time, and less so after merge.
Granted, you may want to be able to re0review code at a later date, and hence keep that structure, but hopefully this is a rare case, and PRs and not often so huge.
## Squash
I often start in a feature branch with a new proof of concept, or other code which is persisted in several commits. Sometimes I'll iterate on a few things, like testing a change in a GitLab CI yaml, and checking whether it works. Or a different compiler flag to enable faster package builds. Or a refactored function which needs to run all the e2e tests to prove the performance gain.
These changes may, or may not work. When they do not work, I'll reset the commits - either soft to keep the changes, or hard to throw away the attempt. This follows a changed history and force push into the remote branch. In case of a shared branch, message colleagues with "git fetch && git reset --hard origin/branchname".
Within pair programming sessions, we often left with commits like "Add REST API HTTP server, WIP 2" in branches and depending on the availability, either one of us continued. At a certain point in development time, we decided to squash and amend the commits. Sometimes not all of them, as rebase/squash also allows you to do the following:
c1 s \
c2 s /
c3 p
c4 s \
c5 s /
Which squashes c1+c2, leaves c3, and squashes c4+c5 again. You can navigate into this on the CLI with "git rebase -i HEAD ~5".
## Rebase/Merge
Short-lived branches which are quickly merged back to the main branch shouldn't cause problems with a broken deployment. In case you get a task assigned where the main branch is far beyond (say, 100 commits or more), it may be the case that
- Branch and merge request works fine, CI/CD pipelines are green - Changes in the main branch which affect your feature.
These changes can be
- Function interfaces renamed, or not existing. Easy to fix upon rebase, build/run does not work anymore. - Runtime changes, for example, queries take longer roundtrip due to a refactor. Your feature only takes the old behaviour into account, and increases the runtime complexity. Or it consumes 10x memory resulting in OOM crashes later.
The last change may not be immediately visible, as it involves staging environments and application performance monitoring results.
### Merge without Rebase
If said changes occur, and the rebase did not happen, the green CI/CD MR is merged back to the main branch. Depending on the releases, you either roll into production, or after days/weeks/months, a new release is cut.
At that point, the regression may be seen in the main branch, and cause delayed analysis and debugging. Often times on-call alerts and all the debug fun which may lead to burnout (been there myself).
### Merge Request with Rebase
During the final review, and prior the merge, the changes are rebased against the latest base in the main branch, to see if they compile or any other influences.
A rebase puts the existing commits onto a new commit base, which influences the calculated checksums. Therefore all commits are newly generated, the author date is preserved with changing the commit date.
### Merge Commits
There are different opinions on them. One of them is to always rebase the MR and then do a merge with a commit. Rationale: Even without GitLab/GitHub/etc. you can reliably see the git graph on the CLI or with other visualization tools.
https://gitlab.com/dnsmichi/dotfiles/-/blob/main/.gitconfig#...
I've recently seen the possibility to reference a PR/MR to a commit as the merge-from-branch reference, without the dedicated merge commit. This can be handy to avoid it, with using GitLab/GitHub/etc. to store this detail in their database. It also is a vendor lock-in in a way, that the native "git clone" does not provide this information for you in Git's database.
That being said, I used to dislike merge commits. With enriched details, and CLI work, I now prefer them again. Git commits as datasource are valuable, and they can be shown/parsed in any environment.
### Rebase, Merge, ... large environments?
This can of course get more complex, with fast moving main branches and lots of merges which depend on each other, and should not reach the main branch. Instead, you'd want them to be queued and tested. We experience that at GitLab quite often, and have created so-called "Merge Trains" which ensure that all MRs in such a queue/train are taken into account: https://docs.gitlab.com/ee/ci/merge_request_pipelines/pipeli...
## A personal note: The best merge/rebase strategy is nothing without tests
I've been working on a monitoring tool in the past which includes distributed environments, and often needed to fix bugs with memory leaks or other performance issues in multi-threaded scenarios. Things you do not see immediately when the MR/PR is green. There was one commit which caused a OOM crash after 3 days of runtime, but only in cloud environments with >100 satellite nodes.
The turnaround was to bisect all the commits, and run each of them in production for 3 days until the crash occurred. IIRC we had 1,200 of them to do in a binary search.
Now the question is:
- Fewer squashed commits - More development history
In this case, fewer commits would have unveiled the error sooner. The resulting commit would be larger and harder to debug & fix though.
In the end, it did not really matter. The thing which would have helped: There was no reliable test environment coupled to CI, CD which ensured to run specific commits & MR/PR in dedicated scenarios and alert of breaking changes soon enough - before the release happens.
## Conclusion
One thing which greatly helped: Looking how others do it, Open Source projects and customer success stories and webinars, online training sessions. Even though you may not adopt the workflows, trying them out is a good way to learn. For instance, "trunk based development with feature flags" is something different to well-known branching models for me, I needed to try them out first to change my opinion. They are indeed useful for certain scenarios.
While committing changes, and keeping them throughout un-squashed MRs, I always remember that I will be highly likely debugging the changes later on. Or someone who finds the MR reviewed by myself, documenting every thought or idea in a commit or MR comment can help.
Some more tips and exercises are discussed in an OSS training I created in the past: https://github.com/NETWAYS/gitlab-training/releases/tag/v2.5...