0

I branched out from master and worked in my new branch branchA, I made a few commits in branchA and merged master now and then into branchA to keep it updated with the latest change. Now I want to squash my commits into one commit before I merge into master, what should I do? I know git merge --squash can squash the commits. However, I don't want to merge it now because I'm waiting for code review approval. It's not good to use rebase too because I merged master into branchA, I'm wondering if there is any good way to squash the commits. Thanks!

litaoshen
  • 1,762
  • 1
  • 20
  • 36
  • You said that you've already sent the commits for review so you already made them public - isn't it too late for squash? You can always create a new branch and push it for review and abandon the previous review request, or make a squash and push with `--force` if you are permitted to do so on the remote server. Depending on what `review` means in your place it may also involve commit messages clarity, commits contents etc. – Arkadiusz Drabczyk Mar 14 '17 at 18:41
  • @ArkadiuszDrabczyk Yes, it is public now. But I want to have only one clean commits before it get merged in master. – litaoshen Mar 14 '17 at 19:28

1 Answers1

2

UPDATED : Added comments at end about the case where branch has been pushed

So you have

X --- X --- X --- X --- X <--(master)
       \           \
        A --- B --- M1 --- C <--(branch)

It sounds like once you're done you'd like

X --- X --- X --- X --- X --- ABC <--(master)

but you're not ready to put ABC on the master branch.

I assume that A, B, M1, and C have never been pushed. If this assumption is false, I wouldn't squash them by any method.

So my first thought would be, wait for the code review and then afterward do the squash merge. If A...C are destined to be discarded, what difference does pre-squashing them make?

But if you don't like that for some reason and really want to pre-squash, how about aiming for this:

X --- X --- X --- X --- X <--(master)
                         \ 
                           ABC <--(branch)

There are a few ways to get to this. Here's one:

git checkout master
git checkout -b temp
git merge --squash branch
git branch -f branch
git checkout branch
git branch -d temp

But based on discussion in comments, it now sounds like the A, B, and C commits have been pushed. If that's true, then any technique for squashing them has drawbacks.

My best advice in that case is to merge as is, write a good commit message on the merge commit, and when looking at the history use git log --first-parent. In this case the individual commits on the branch won't show in the log output, and you'll see a single commit (the merge itself) representing the entire branch just as if it were a squashed commit.

But if you don't want to follow that advice, and really must squash the changes, here's the deal: Anyone continuing work on branch will have a problem. And you can say "nobody should do that", and if your team agrees that nobody will ever do that then it'll work out. But since branch is present in a shared repo, nothing would stop it from happening; and then at best you're going to have to deal with messy conflict resolutions.

Suppose you follow the steps above; what you really have is

X --- X --- X --- X --- X <--(master)
       \           \     \ 
        \           \      ABC <--(branch)
         \           \
          A --- B --- M1 --- C <--(origin/branch)

If you try to push branch it will fail (because origin/branch is not reachable from branch). You could force the push, and as soon as you do that anyone with a local branch reference that points to C will start having issues. Not that they can't be corrected, but it could become a hassle, especially if anyone "fixes" it the wrong way.

You could avoid that by never force-pushing branch. You'd wait for the chance to merge your version of branch to master, then push master and delete your local branch.

X --- X --- X --- X --- X --- ABC <--(master)
       \           \
        A --- B --- M1 --- C <--(origin/branch)

Now, even if you force deletion of the remote origin/branch reference, other devs might still have local branch references pointed at C. And since that history was not merged into master (but rather ABC was created to update master in a single non-merge commit), they'll have trouble if ever they merge (further changes to) branch into master.

You could get clever with a follow-up merge using strategy "ours" or something to show Git that branch is an ancestor of master... but at that point, you might as well have just done a normal merge back at the very beginning, because that merge will draw A, B, and C into the default log history of master just like a normal merge would've (i.e. such that to exclude it you could use --first-parent).

Again, all of this can be managed; but it's unnecessary hassle, which is why the best practice is: once a commit has been shared, allow it to be part of the history.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52