Search code examples
gitgithubmergebranchsquash

GIT: can I maintain a ongoing development branch while squash-merging to master?


this seems like it should be a simple question to find the answer for, but I've been searching and pulling my hair out all morning and I just cant find an answer that satisfies me. The closest thing I've been able to find is this question, Git merge squash repeatedly, but that question is from 11 years ago, so hopefully the consensus of "you simply can't do that unless you create temporary branches" has changed.

I'm working on a long-term project where I'm basically the only contributor. Ideally, I want there to be one long-term ongoing development branch that I can continue tinkering and working on bit by bit, and periodically make a squash-merge to the master branch whenever I get to a stable, releasable state. I don't want to have the full commit history available/visible on master, because I want to be able to easily walk backwards thru the history of stable official releases. But, I do want the full commit history to be available somewhere for if it ever becomes relevant.

The problem is, if I try to do this the way that intuitively feels like it should work, it just doesn't. The squash-merge-commit creates a commit on master that doesn't match anything in development, so the next time I try do do a squash-merge it will say there is a conflict and I need to merge from master into development... and as I repeat this process more and more, this problem keeps growing and I've got half a dozen commits bouncing back and forth between the two branches that don't actually change any files, but I just can't seem to get rid of.

The graphical picture I want to make looks something like this, if that makes any sense:

      B-------------------G-----------J master
     /                   /           /
a---b---c---d---e---f---g---h---i---j development

The solution I've been using up until now is annoyingly ugly: I create a new branch with the version number, such as development_601, then I do a pull request from that branch to master when I'm stable again and squash-merge the commits, then I create another new branch development_602 from master after the squash-merge. development_601 gets mothballed. This seems cluttered and annoying and just wrong, especially because of how Git seems to be hinting that I should delete my branch after merging the pull request, but like I said I want to have a record of my complete messy history somewhere, I just don't want it to be (easily) outwardly visible.

I've been using the Github Desktop app along with the Github website; I've been trying to avoid putting console commands into my workflow.

Is this such a strange behavior to want? I feel like "having one long-term development branch with a master/release branch that periodically but incompletely mirrors it" is an extremely common system, but somehow I just can't find anywhere that explains how to make it happen.


Solution

  • This question is answered in the Git FAQ, and the problem you're experiencing is a common one:

    In general, there are a variety of problems that can occur when using squash merges to merge two branches multiple times. These can include seeing extra commits in git log output, with a GUI, or when using the ... notation to express a range, as well as the possibility of needing to re-resolve conflicts again and again.

    When Git does a normal merge between two branches, it considers exactly three points: the two branches and a third commit, called the merge base, which is usually the common ancestor of the commits. The result of the merge is the sum of the changes between the merge base and each head. When you merge two branches with a regular merge commit, this results in a new commit which will end up as a merge base when they’re merged again, because there is now a new common ancestor. Git doesn’t have to consider changes that occurred before the merge base, so you don’t have to re-resolve any conflicts you resolved before.

    When you perform a squash merge, a merge commit isn’t created; instead, the changes from one side are applied as a regular commit to the other side. This means that the merge base for these branches won’t have changed, and so when Git goes to perform its next merge, it considers all of the changes that it considered the last time plus the new changes. That means any conflicts may need to be re-resolved. Similarly, anything using the ... notation in git diff, git log, or a GUI will result in showing all of the changes since the original merge base.

    This approach is always going to lead to problems. If you have two long-running branches, you need to use a regular merge commit. Otherwise, you could create short-lived branches that you merge into each of your long running branches separately, or use a rebasing strategy. All of these approaches are more appealing if you take the time to create independent, logical commits with good commit message like Git encourages you to do.

    My personal recommendation as a Git developer is to write sane commits and then use regular merge commits. If your history is nice, then there's little reason to hide it.

    However, the fundamentals of how Git and merges work haven't changed over time, so the answer "don't do this" as to long-running branches and squash merges hasn't changed, either..