简体   繁体   中英

How do I reset master and keep my branch in git?

Suppose I do

$ git checkout master
$ touch foo.py
$ git commit -m "oops" foo.py
$ git checkout -b new_branch
$ touch bar.py
$ git commit -m "changes" bar.py

Now when I try to push back changes on new_branch, I get

Local branch 'master' is ahead of remote branch 'origin/master'

How do I reset master while not losing my changes (foo.py, bar.py) on new_branch?

I read the git reset page , and it looked like it might involve --keep, but I couldn't tell.

This can be very confusing initially, and what you needed was a proper introduction to how Git implements branches; but at this point we'll use the retrofit method. :-) The trick to understanding all this is that Git's commits are permanent and unchanging, but its branches —or more precisely, branch names —are temporary, and in fact mostly irrelevant.

There are three things that matter when you are building a new commit (they are HEAD , the index , and the work-tree ), but once you have the commit built and committed, it's quite permanent and it is very hard to get Git to lose it entirely. It's easy enough to accidentally misplace it, though, so let's try to avoid that. :-)

If we ignore the branch names entirely, we can draw a graph of the commits that exist in your repository. Given what you have done—making two new commits—let's draw them like this, where round o s represent commits, and A and B are your two new commits:

...--o--o--o
            \
             A
              \
               B

We could draw them all on one line, but I want to leave room to write labels in on the right. Commit A is your "oops", and B is your "changes".

The main noteworthy thing about this graph drawing is that each commit points to (stores the hash ID of) its predecessor commit. This means that commit B points back to commit A . Commit A points back to the next-most-recent commit, which points back still further, and so on.

Now we add the labels—the branch names . The last boring commit o still probably has a label origin/master . Commit A has the label master , and commit B has the label new_branch , so let's draw these in:

...--o--o--o   <-- origin/master
            \
             A   <-- master
              \
               B   <-- new_branch (HEAD)

This is what branch names are and do for you: they are pointers to commits; they remember each commit's big ugly hash ID for you.

When you are on some branch and make a new commit, the branch name comes along for the ride. The special name HEAD remembers which branch you're on, so that Git knows which name to move to the new commit. (We needed this temporarily at least, when master and new_branch briefly both pointed to commit A .)

What you want to do now is move master back to point to the last of the boring o commits. To do so, you can use git reset , which lets you move a name in an arbitrary way:

git checkout master
git reset --hard origin/master

This assumes (see final section below) that origin/master really does point to the last of the boring o commits. The git reset --hard says: Wipe out my current index and work-tree, and move my current branch—according to HEAD —so that it points to the commit I name here. We must git checkout master first , so that HEAD names master . Then the git reset does this:

...--o--o--o   <-- master (HEAD), origin/master
            \
             A
              \
               B   <-- new_branch

So now your two new commits A and B are found only via new_branch and not via master .

(There are several more things that branch names do for you. In particular, they protect commits from being removed by Git's "garbage collector". If a commit has any name by which we can find it, it's protected. If it has no name, it's no longer protected. There are some semi-hidden names that protect everything for a while—at least 30 days by default—to make sure commits don't get trashed accidentally, but finding them through these reflog names is annoying, so we try not to rely on it so much.

The branch names are also used for git push , so there they matter a fair bit.)

(I mention "trashing" the index and work-tree above, which isn't completely true. The git reset command, used the way we're using it here, has three jobs it can do:

  • move the current branch
  • reset the index
  • reset the work-tree

It always does the first, optionally adds the second job, and then optionally adds the third. The --hard mode does all three, the --mixed mode does the first two, and the --soft mode does only one. To understand them all properly, we would have to look closely at the definitions of index and work-tree and I'll leave that for other SO questions/answers. The key item here, though, is that you don't want to git reset --hard until you have everything saved away in commits.)

What if there's no origin/master (or it points back too far)?

We assumed, above, that there was a handy label—a so-called remote-tracking branch —identifying the commit we want to git reset our master to point-to. If we don't have this label, or it points to an even earlier commit, we must locate that commit some other way.

There are a lot of ways to do this. The most straightforward is to go by its hash ID. If you run git log while on some branch, you see each commit that is "on" or "contained in" that branch, normally in Git's usual backwards-going order. For instance, we might git log and see commit B , with its big ugly hash ID, then commit A with its hash ID, and then that boring commit o with its big ugly hash ID.

We can use the raw hash ID:

git checkout master && git reset --hard <hash-id>

if we don't have the label. If we do have the label, we should probably just use it.

You might remember to get help from "A DOG" with git log :

git log --all --decorate --oneline --graph

The a ll makes Git show all branches and all remote-tracking branches (and everything else: tags, the "stash", notes, etc). The d ecorate option attaches the label names to commits. The o neline option makes the output show just one line for each commit, and the g raph option makes Git try to draw the commit graph.

How do I reset master while not losing my changes (foo.py, bar.py) on new_branch?

Yes, foo.py and bar.py is in your new branch.

  • You have created a branch from the master which is not pushed to the origin. so when you try to push the master you got the error because its ahead of remote.

  • You get that message because you made changes in your local master and you didn't push them to remote.

You have several ways to "solve" it and it normally depends on how your workflow looks like:

  • In a good workflow your remote copy of master should be the good one while your local copy of master is just a copy of the one in remote. Using this workflow you'll never get this message again.

  • If you work in another way and your local changes should be pushed then just git push origin assuming origin is your remote

How I will be doing it :

git checkout master
git create -b la_lalalla
touch foo.py bar.py
git add drama.py bar.py
git commit -m "Drama commit"

merge the branch to your master, this depends on you to do merge or do rebase, then,

git push origin master. 

your done with adding two files to origin/master.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM