gitmerge

git squash merge with a moved pointer


Consider this repo commit structure

dev: [base]....A..B..C..D..E..F..G..H..I..J..K..L..M..N..O..P..Q..R..S..T..U..V..W..X..Y..Z....
       |                                                    \                       \     \
tst: [base]..................................................P'......................X'....Z'....
       |                                                                                   \
prd: [base].................................................................................Z''....

Where each letter is a commit. Each branch we want to keep the full history of, but squash commits down so each higher environment has a progressively cleaner history. Doing this once is clearly a squash-merge, easy enough. But the squash-merge doesn't move the internal pointer, so each subsequent merge is likely to produce conflicts.

For example, squash-merging devA..devP into tstP' is easy. But then to update tst into X' will produce a conflict, in, for example, the version file -- since P' ocurred after P, technically speaking, the file is newer and not reflected in tst. As long as no file movements happened, usually --squash -X theirs will do the job, but not always (checkout BRANCH * is closer and not the same in some cases). Rebasing is out because, by definition, I want to preserve the history of the lower environments.

What I'd like is a "yes we did a real merge, but no we don't actually want the commits from that other branch, just assume you're synced up now" merge. You can kinda get merges to play nice by walking back up the environments and merge -s ours-ing back, but that screws with the commit graph. I'd like a perpetual, everlasting one-way waterfall merge, basically.

I'm not sure it's possible (some commit equivalency mapping would need to be stashed), for example saying commit c9c9c9c9 on dev == b8b8b8b8 on tst, so merges from dev after c9c9c9c9 into tst should only look at a subset of commits, but figured might as well ask.


Solution

  • This is definitely possible, and both of your ideas are in fact part of the solution: you need to have a prior merge, and you need to somehow know what's already been merged. Fortunately there is a straight-forward solution that accomplishes both requirements.

    Let's assume tst is currently pointing to P' and you're about to do your second squash merge, of X:

    # 1.) Create and checkout a temp branch called temp/tst starting at tst
    git switch -c temp/tst tst # Now both branches are pointing to P'
    
    # 2.) Merge in P (Note this doesn't change state so -s ours isn't necessary)
    git merge P
    
    # 3.) Now merge in X (This no longer has conflicts because P is already merged)
    git merge X
    
    # 4.) Now squash merge temp/tst into tst
    git switch tst
    git merge --squash temp/tst # No conflicts because temp/tst is fully ahead
    git commit -m "Squash merge up to X"
    

    If you wish to delete the temp branch, you could include the commit ID in the squash merge commit message so that you know where the starting point is for the next merge. Or you can keep the temp/txt and temp/prd branches around indefinitely since they aren't hurting anything. If you do this, perhaps you would tweak the steps as follows:

    # After finishing the above steps, do step 5.)
    git switch temp/tst
    git merge tst
    
    # The next time you wish to merge again, instead of creating a new branch,
    #   you can now replace steps 1 and 2 with simply:
    git switch temp/tst
    

    Side Note: perhaps it's kind of neat to know that it's possible to have a situation where merging a set of commits has conflicts, but just by breaking that single merge into multiple smaller merges one can avoid having conflicts. (Obviously this only works in specific scenarios.)