0

I have been using squash with the steps below and I find it really time consuming and error prone

git rebase upstream/develop
git merge-base mylocaldev upstream/develop  (this will display a commit hash that can be used in the next step)
git rebase -i (hash from last step).
   (now in vi leave first line as "pick" and change the other line to "squash").
   (vi launches again, I can clean up commit message then save).
git push -f origin mylocaldev

Very often I have to resolve multiple conflicts in the process. (What I don't understand is if I don't do the local squash, push it to my origin, open a PR between my origin and upstream, there I have the github option to squash, and it NEVER needs me to resolve any conflict as long as my branch has the latest changes from upstream).

What am I missing in my local workflow that github does in its squash process?

I am wondering if there is a better practice/workflow I can use (to have similar effect as squash)? or at least something I can use to avoid resolving conflicts.

Thanks!

2
  • Which step involves multiple conflicts?
    – matt
    Commented Jul 12, 2023 at 0:10
  • The second rebase, git does it one by one so I have to resolve conflicts one by one. Matt's answer below makes sense, I should have combined all my commits first then I only need to resolve conflicts once. Commented Jul 12, 2023 at 15:16

2 Answers 2

1

It looks like you are trying to turn the many commits of mylocaldev, which was branched from upstream/develop, into a single commit that comes off the end of upstream/develop.

For example you wish to turn this:

A -- B <- (upstream/develop)
 \
  X -- Y -- Z <- (mylocaldev)

into this:

A -- B <- (upstream/develop)
      \
       XYZ-ish <- (mylocaldev)

The way to do that is to do it in the opposite order from what you're doing:

git fetch upstream/develop
git reset --soft $(git merge-base upstream/develop @)
git commit -m 'my one cool commit'
git rebase upstream/develop
git push -f origin @

something I can use to avoid resolving conflicts

Do the above only once while working on the same local branch. Merge conflicts are extremely likely if you keep a branch alive and rebase it multiple times onto the same branch as the latter grows independently. The reason is that the merge-base does not move as a result of the rebase, so if you rebase multiple times in this way, you are replaying against more and more new commits that keep appearing on the target branch — including your own! — which is a golden recipe for merge conflicts.

What am I missing in my local workflow that github does in its squash process

Because the thing that happens in GitHub happens only once. They warn you quite loudly in their docs not to use a "squash and merge" multiple times off the same branch.

1
  • I see, so use reset --soft to stage commits X-Y-Z again, make them into one commit, rebase it using the latest upstream snapshot. Thanks for the tip! Commented Jul 12, 2023 at 6:18
1

You should read Github documentation: when you merge a pull request, it explains the differences between the choices:

Merge all of the commits into the base branch by clicking Merge pull request. If the Merge pull request option is not shown, click the merge dropdown menu and select Create a merge commit.

Squash the commits into one commit by clicking the merge dropdown menu, selecting Squash and merge and then clicking Squash and merge.

Rebase the commits individually onto the base branch by clicking the merge dropdown menu, selecting Rebase and merge and then clicking Rebase and merge.

The git operations done by GitHub should be:

  • git merge target
  • git rebase -i target + squash all except first commit (there is probably an option in git for that, otherwise it is custom made by GitHub) then git merge target (HEAD = branch with squashed commits)
  • git rebase target then git merge target

The three GitHub option all boil down to what you want to see in the history.

In most case, the last option is best because it minimize history by removing redundant merge commit (since the default mode of git pull is to merge, so a pull request may contains merge with its target branch).

If you want to perform the same locally, you would avoid git merge (even if git pull will do it by default, except with --no-ff):

  • you have to squash before fetching or you have to know your ancestor commit. I personally ignore this and do an interactive rebase with git rebase -i HEAD~10. Then I carefully "fix" (= squash, but without keeping the commit message) the commit after the one(s) I want to keep.
  • after interactive rebase, you can rebase with upstream/develop: if a commit bring a conflict onto a file, then any remaining commit touching that file may also bring a conflict. It is normal even if git probably tries to minimize that.
  • then you can git push -f after performing some check (compiling, testing, ...).

You could also do a git merge rather than git rebase, but you will have merge commit in your branch which I think should be avoided as much as possible because it clobber the history.

1
  • Thanks, your idea is similar to Matt's, I should squash before re-basing, that way the conflicts only need to be resolved once since there will be only one commit. I want to avoid interactive rebase as much as possible Commented Jul 12, 2023 at 15:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.