Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to abandon old Git Master, and use branch as new master?

Tags:

git

We have a project where we at one time had to go back in Git history a bit and start a new branch (which we here will call "new_branch") from an old commit. Now, all development is happening in new_branch, and the old Master is useless after the point where the branch was made. So now we have a situation that looks like this:

C1 = Commit 1, C2 = Commit 2, etc.

            C1                       
             |                       
            C2                       
             |\                      
            C3 \                     
             |  \                    
            ...  \                   
             |    \                  
Master -->  C17    \                         
                    \                
                   C18               
                    |                        
                   ...               
                    |                
                   C37 <-- new_branch

So what I want to do now, is abandon (undo?) all the commits made in Master after the branching-off point, that is, C3 through C17. At least, that's what I think I want to do? I mean, C3 through C17 are useless now, and if I could undo them then I can merge new_branch back into Master, to make Master useful again.

So my question is, is this the right approach to achieve my goal of being able to use Master again? And which command (I use Git from the command line in Linux) should I use to undo the C3 through C17 commits? This is a project for work, so it's very important that I get this right.

like image 892
Enfors Avatar asked Jan 26 '23 19:01

Enfors


2 Answers

The most straightforward solution is simply to force push new_branch onto origin's master:

git checkout new_branch
git push -f master

Then fixup your local master in one of several ways:

  1. git reset --hard origin/master (on branch master)
  2. git branch -D master (not on branch master, pull again to get the updated one)

There's no need in this scenario to rebase existing master with the new_branch.

This will (obviously) obliterate the existing master and replace it with the contents and history of new_branch. Be sure of your intent.


As Romain points out below, using git branch -f obviates the local fixup step:

git branch -f master new_branch && git push -f origin master

since it moves the branch pointer locally as well.


As with many git issues, there are many ways to solve this, and other answers may be just as viable.

like image 77
msanford Avatar answered Jan 30 '23 00:01

msanford


There are two general approaches to doing this; each has drawbacks, so it depends what's more important in your situation. Each approach has variations as well, with smaller trade-offs...


The first approach is a history rewrite of master. That simply means moving master in a way that removes from its history some commits that currently are part of its history.

Any history rewrite has certain costs no matter how you do it - so it's usually best to do it the simplest way that meets your requirements. In this case, that's probably

git checkout master
git reset --hard new_branch
git push --force-with-lease

This is similar in effect to the approach msanford recommends, but has two advantages. First, it's a little more concise (in that it updates the local branch first and uses that as the setup for updating the rmeote). Secondly, replacing -f with --force-with-lease provides a little extra safety, in that it ensures the remote hasn't had anything new committed to it that you're unaware of. (In your specific case, that probably isn't much of an issue; but it's a really good habit to get into.)

The cost of a history rewrite of a branch that has been shared across repos (i.e. pushed, and then - at least potentially - fetched or pulled into other repos) is that other repos sharing the branch will see the branch move in an "unexpected" way and will have to recover. The recovery procedure is documented in the git rebase docs, in the section "recovering from upstream rebase". It is important to coordinate with the entire team when doing this, as if any team member does the wrong thing when recovering, it can undo the history rewrite.

The upside is that you end with a very nice history that doesn't show the removed commits at all. (Beware, though, that if those commits have been shared, nothing you can do ensures that they're "gone forever". If they contain sensitive information, that's a bigger problem and the information basically has to be considered compromised - though there are steps you can take to at least try to prevent further spread of such info.)

So if you can practically coordinate with all users of the repo, this is a good way to go. But if you can't, there's another way.


The second approach is to undo the changes on master - i.e. revert it back to C2 - and then merge new_branch in. This conforms to the way branches are "supposed to" usually move, so doesn't carry the costs of a rewrite. But, it means that the abandoned commits (and their reversal) remain forever in history.

git checkout master
git revert -n C3..HEAD
git commit
git merge new_branch

This gives you a history like

C1 - C2 - C3 - ... - C17 - R -- M <--(master)
       \                       /
        C18 - ........... - C37 <--(new_branch)

R makes master look as it did at C2, and M is a simple merge of the newer changes over that.

If you omit the -n option from the revert command, you can skip the commit command, but you'll get a separate commit to undo each commit from C3 to C17. There are also techniques for embedding the undoing-of-changes in the merge commit itself if you really feel strongly that this is better, but it creates an "evil merge" - a merge that hides changes and may confuse both users and certain future operations like git rebase.

The downside is a messier history. (If you really need the history to document how you got where you are, then it is arguably more accurate; but it is certainly more complicated and shows information you may no longer need. It's certainly not suitable if the reverted commits contain sensitive information that you don't want to keep spreading.)

But, as noted above, it follows normal branch movement patterns, so avoids the costs of a history rewrite.

So again, it depends what's more important in your situation.

like image 36
Mark Adelsberger Avatar answered Jan 30 '23 00:01

Mark Adelsberger