Reputation: 988
I have to rollback master to a previous state without overriding the commits made so far. What is the best way of creating new branches of master and rollback master.
/| merge branch A into master (commit id a.into.m)
| | commit (id a2) branch A
| | commit (id 3) branch master
| | commit (id a1) branch A
\| new branch A commit (id a0)
| commit 2 master (id m2)
| commit 1 master (id m1)
I am trying to create multiple branches of this and reset master to a previous state.
branch new_calculator from commit (id 3)
branch new_form from commit (id a2)
branch rollback_master commit (id m1)
I tried this for all branches
git checkout m1
git checkout -b rollback_master
git push origin rollback_master
When I do a pull request of rollback_master into master I don't see any changes. Nothing to merge.
I also tried on the rollback_master branch
git reset --hard m1
nothing to commit.
Upvotes: 1
Views: 4372
Reputation: 850
In order to revert your master back to an older commit / branch, you require git reset
.
If you want to reset e.g. a branch current
to older_branch
, do
current
branch: git checkout current
(possibly after cloning your repo or stashing uncommitted changes)current
and older_branch
, create a new branch as backup: git branch backup
current
to older_branch
: git reset older_branch
Doing so, your checked out files won't be altered, thus the state you end in will be your current
branch having been reset to older_branch
, but your checked out files still being the versions of current
before the reset.
You probably want to replace your checked out files with the one from older_branch
as well. For this end, do a git reset --hard
.
Btw: You can reset to basically anything, it need not be a branch name - you can also use the hash of a commit instead.
However, it's getting more tricky if you deal with remote repositories and you already pushed current
onto the remote - many remotes won't allow modification of already existing history due to potential conflicts with other contributors. There is another option, though: You can let git create a commit reverting older commits, thus the history won't be altered, but you nevertheless end up with current
containing the exact state of older_branch
. This reverting commit can safely pushed into remotes. Check out git revert
.
Upvotes: 3
Reputation: 487745
Git isn't really about branches. It's really about commits. Branch names are just labels we (and Git) use to find commits.
The log output you drew:
/| merge branch A into master (commit id a.into.m)
| | commit (id a2) branch A
| | commit (id 3) branch master
| | commit (id a1) branch A
\| new branch A commit (id a0)
| commit 2 master (id m2)
| commit 1 master (id m1)
is kind of close to the ASCII-art graphs that git log --decorate --oneline --graph
draws, but lacks the little asterisks that it uses to mark commits:
* 4291fbf Merge dotfiles installer
|\
| * 4f5b84b dotfiles: add --tar option
| * 8a45ff6 dotfiles: refuse to install over special files
| * e750c89 dotfiles: reshuffle work-to-do code
| * 36aa02c dotfiles: s/--install-to/--homedir/
| * 93c597c dotfiles: simplify FileInfo
| * 4771f69 dotfiles: manipulate home dir .foorc etc
|/
* 6c9a43a add prototype Dot-files
Note that each commit also has a unique hash ID (here they've been shortened). These hash IDs are the real names of the commits.
Branch names merely act as pointers, pointing to these commits. I like to draw these things sideways, rather than vertically, for StackOverflow postings. I also like to give them single uppercase letters, instead of big ugly hash IDs. The result is:
...--F--G--H <-- branch1
\
I--J <-- branch2
The idea here is that each branch name points to one commit. The commits themselves also point (backwards) one step, so that J
points to I
, I
points to H
, H
points to G
, and so on.
The commits that are "on" a branch in Git are those you can get to by starting with the branch name, following its arrow to its commit, and then working backwards from that commit. It's therefore in some sense better to say that these commits are contained in the branch, because commits up through H
are on—or contained in—both branches, while commits I
and J
are currently contained only branch2
.
Now, the things to know about these commits and branch names include:
No commit can ever be changed. At most, you can take some existing commit and copy it to a new-and-improved version, which will get a new and different hash ID.
As we just said, the set of commits contained in some branch are obtained by starting where the branch points, and then walking from commit to commit, backwards, one step at a time. The names themselves just contain the commit hash ID of whatever commit we say should be the last commit in the branch.
Branch names therefore move. Their normal progression is forwards: we start with some set of commits, like the ones above, and then add a new commit. For instance, if we git checkout branch1
, we get commit H
. If we make a new commit, it gets a new ID, which we'll call K
(I
and J
are in use):
...--F--G--H---K <-- branch1 (HEAD)
\
I--J <-- branch2
(We can use the word HEAD
, attaching it to some branch like this, to remember which branch we're on.)
The normal motion, as we just saw, is to add-on. Let's merge branch2
into branch1
. Without worrying about how Git accomplishes this, let's draw the result:
...--F--G--H---K--L <-- branch1 (HEAD)
\ /
I--J <-- branch2
New commit L
is a merge commit, which is one with two parents (or more than two, but we won't see that here).
Since branch names just point to specific commits, we can add more names and/or force names to move in directions they normally don't:
...--F--G--H---K--L <-- branch1 (HEAD), master
\ /
I--J <-- branch2
Let's force the name master
to point to commit K
instead of L
:
$ git branch -f master <hash-of-K>
and draw the result:
K <-- master
/ \_
/ \
...--F--G--H------L <-- branch1 (HEAD)
\ /
I--J <-- branch2
Nothing has actually changed in terms of the commits, but the labels have moved.
But master
moved in the "wrong" direction. If we connect our Git to some other Git, and their master
points to commit L
, and we ask them to move their master to point to commit K
like ours does, they'll generally say no. The reason they will say no is that this branch label move is in the wrong direction. The Git term for this is that it is not a fast-forward.
We can use git push --force
to command them to move their master
like this. They may or may not obey. Other clones of our or their repository may also have a master
that points "ahead of" commit K
, e.g., to L
, and we'll have to convince them to move the same way.
So:
I am trying to create multiple branches of this and reset master to a previous state. branch new_calculator from commit (id 3) branch new_form from commit (id a2) branch rollback_master commit (id m1)
You can do this, in your own Git repository, where you control everything. Just change your labels around and make new labels as needed. All the existing commits stay put; only the labels move. See Michael Beer's answer for more details on how to do this (with git reset
and/or git branch -f
).
You won't necessarily be able to change anyone else's Git repository. That includes any copy of your own Git repository that you put on GitHub. That's GitHub's repository, after all, not yours: they're just letting you control it, and it's up to them to decide how much control to give you.
If you own it so that GitHub will give you full control, and no one else has Git repositories that you need to worry about, you can git push --force
your updates to your GitHub repository (aka "GitHub's repository that they're holding for you").
If not, though, what you generally should do is add new commits to the repository, so that the new commits extend existing chains of commits in this "moving forward" manner. The new commits simply record the new state of things as well. All Gits everywhere understand the concept of moving forward, and the new commits refer back to the existing commits, so all the previous committed files are still there in their previously-committed versions.
When I do a pull request of rollback_master into master ...
Pull requests aren't part of Git. They're an extension offered by services like GitHub.
When you make a pull request, you attach a label—the actual label is up to the service—to some commit in some repository that exists on the server. That commit has some hash ID, as all commits do. Then, the server delivers the pull request (complete with internal attached label) to whoever they're having manage other repository—the one you forked from. (The server has recorded that repository somewhere. In some cases, you choose as the "other" repository, this repository; then the someone is whoever owns this repository.) That person gets a user interface that allows them to:
git merge
on the GitHub server, orgit rebase
on the GitHub server, orgit merge --squash
on the GitHub server.None of these steps include a git push --force
"move label in arbitrary manner" operation. All of them either generally or specifically create a new commit that adds on to existing commits. So all operations done through pull requests are necessarily very unlike the kind of git push --force
that moves a label backwards.
To put it another way, if you go down that road, "you can't get there from here". :-)
Upvotes: 1