101

I have the following situation:

  • I created a clone(Y) from a main repository(X), because there were many people working on Y we didn't do any rebase but only merges. When we want to deliver(push) Y to X we would like to do a rebase in order to have things nice and clean

The problem is that when doing rebase we are asked to do all the merges that we already did in the previous merge steps. Is there a solution to this, beside the one that means actually re-doing the merges?

I expected it to be pretty straightforward since we already solved the conflicting merges.

рüффп
  • 5,172
  • 34
  • 67
  • 113
INS
  • 10,594
  • 7
  • 58
  • 89

6 Answers6

178

git merge --squash is now my preferred way of rebasing after a large amount of work and many merges (see this answer). If the branch you're working on is called my-branch and you want to rebase from main then just do the following:

git checkout my-branch
git branch -m my-branch-old
git checkout main
git checkout -b my-branch
git merge --squash my-branch-old
git commit
Sky
  • 4,327
  • 4
  • 26
  • 40
  • This was much easier than squashing with rebasing. It just worked. – Matt Aug 10 '21 at 15:08
  • Yeah rabasing is simply a horrible idea, thx for the solution – Ini Jun 02 '22 at 12:53
  • 1
    If the branch track an remote one, you may add `git branch --set-upstream-to origin/my-branch` and optionally `git push --force`. W/O this, the new `my-branch` won't have the link with the remote. The forced push allows to rewrite the history on the remote too, by only have the squashed commit in its history. That said, my comment assumes no one but myself is tracking the remote branch, to avoid breaking the history – Steve B Jul 12 '22 at 07:48
  • This is so much better than constantly rebasing and solving conflicts. I'm going to do this from now on. Thank you for saving me an hour of my time:D – Aron Atilla Hegedus Feb 02 '23 at 14:50
  • I have been wrestling with rebasing for years when all I wanted to do was this. Thank you, kind soul. – Michael Fulton Feb 04 '23 at 00:03
  • git checkout main -- need to make sure you are on the latest if main default not pointing to head ! – Sion C Feb 07 '23 at 10:47
88

Rebasing to get a "clean" history is overrated. The best way if you want to preserve history is just to do the merge instead of a rebase. That way if you ever need to go back to a revision, it is exactly the same as the one you tested during development. That also solves your issue about the previously solved merge conflicts.

If you don't care about preserving history, you can create a new branch off of master, check it out, then do a git read-tree -u -m dev to update your working tree to match the dev branch. Then you can commit everything into one big commit and merge it into master as normal.

Karl Bielefeldt
  • 47,314
  • 10
  • 60
  • 94
  • 1
    What you've effectively done here is a merge. The new branch is unnecessary. – isak gilbert Aug 13 '14 at 01:43
  • 3
    @isakgilbert It's only the same if you merge with `--squash`. A regular merge will add _N_ or _N + 1_ commits to master if there were _N_ commits on the branch. The suggestion above, or `merge --squash`, will always add only a single commit to master. – peterflynn Mar 17 '15 at 17:40
  • 3
    @ytpete, yes exactly. I feel the above is just a roundabout way of doing a merge --squash from dev to master. "Creating a new branch" is just pointing at the same place as master is. "Commit everything into one big commit" is just doing the squash. – isak gilbert Mar 17 '15 at 22:18
  • 6
    merge commits are noisy when trying to see the full set of changes on a single branch. It's a pain to extract the diff and look at it when a clean history makes it simple. – Ajax Mar 05 '19 at 23:56
  • 10
    I couldn't disagree more with with a "clean" history is overrated. It's not overrated it saves time and makes things less confusing. – basickarl Mar 06 '20 at 09:35
  • 1
    I also could not disagree more. I want to see clean, atomic commits, with the logical intent of the change contained all in one commit, not spread over multiple commits. – thewoolleyman Jun 30 '20 at 20:04
  • I hate it when someone does a commit, then do a local merge with some latest commit from master, and then the remote git(like github) does yet another merge on the pull request. – Fai Ng Nov 06 '20 at 02:44
15

Two remarks:

  • you can rebase your own (non yet pushed) work as many time as you want on top of newly fetched commits.
  • You could avoid the merge conflicts (during rebase) if you had activated git rerere, which is done for this kind of situation.
    http://git-scm.com/images/rerere2.png See more at git rerere.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • @lulian: in that case, if you have to rebase your work, I think you will have to do those merge conflict resolutions again. – VonC Jun 06 '11 at 08:09
  • @krlmlr Thank you. I have restored the link, added an illustration and a reference to the man page of that command. – VonC Dec 11 '13 at 12:56
10

You can take all of the changes in your branch and put them into a new commit in master with the following:

git diff master > my_branch.patch
git checkout master
patch -p1 < my_branch.patch

Then stage your files and commit.

dbaston
  • 903
  • 1
  • 10
  • 20
4

Regarding the replay of merge conflicts, you can use git rerere to maintain a database of how merge conflicts have already been solved, so that performing a rebase that results in the same conflicts will have the laborious parts done for you automatically.

https://hackernoon.com/fix-conflicts-only-once-with-git-rerere-7d116b2cec67

git config --global rerere.enabled true

The one thing to look out for is that if you resolved something incorrectly it will be automatically borked for you next time too, and you may not really realize it.

More formal documentation here: https://git-scm.com/docs/git-rerere

Ajax
  • 2,465
  • 23
  • 20
1

I found @sky's answer helpful. And I made it into a squash-rebase function.

Assuming you are on your-branch, what this does is create a branch your-branch-tmp that is the result of doing merge --squash on main. It also preserves all the commit messages and lets you edit. Importantly, it also leaves your current branch alone.

After running squash-rebase, if you're satisfied with what it did, you must do a hard reset to the tmp branch. Then in effect you've completed a squash rebase.

function squash-rebase() {
  MESSAGES="$(git log $(git merge-base main HEAD)..HEAD --reverse --format=%B)"
  SRC_BRANCH="$(git_current_branch)"
  TMP_BRANCH="$(git_current_branch)-tmp"
  echo "creating temp branch $TMP_BRANCH from $(git_current_branch)"
  git checkout -b $TMP_BRANCH main  # create tmp branch from main and checkout
  git merge $SRC_BRANCH --squash
  git commit -m "$MESSAGES" -n
  git commit --amend -n # just so you can edit the messages
  git checkout $SRC_BRANCH
  echo "IMPORTANT: run git reset --hard $TMP_BRANCH to complete squash rebase."
  echo "First, though, you may run git diff $TMP_BRANCH to make sure no changes."
  echo "Finally, delete the tmp branch with git branch -D $TMP_BRANCH"
}

Note: assumes you are using oh-my-zsh, otherwise you may have to find another way to get current branch name.

dstandish
  • 2,328
  • 18
  • 34