简体   繁体   中英

Committing to master with detached HEAD

I was up coding late last night and made a fatal mistake. I was on a branch and ran git checkout on an older commit. I then started writing on top of that code. What are the steps needed to push to git master?

To be clear: I want to push the repo I have locally saved on my computer to the master origin.

Thank you!

Stash your changes, switch back to master and apply changes there, and continue your work there:

git stash save
git checkout master
git stash pop

After that you can do git add and git commit when needed, it'll be on top of the master branch.

Assuming the detached HEAD is where you want master to be (ie you don't have any commits in master that you want to keep nor you have any modified files in your working directory) you can move the master branch to point to the commit referenced by HEAD by using the --force option of git branch .

From the documentation :

-f
--force
Reset <branchname> to <startpoint> if <branchname> exists already. Without -f git branch refuses to change an existing branch.

In your case, you would simply say:

git branch -f master HEAD

Then you can checkout master and push the commits to the remote master branch like you usually would:

git checkout master
git push origin master

Again, as @torek pointed out in the comments, this solution assumes that you want to reset the master branch to the commit referenced by HEAD and don't care about whatever commits are currently reachable from master .

It's not clear from your question whether you made any new commits .

There are three things to do, depending on your situation:

  • use git checkout -b newbranch and then rebase or merge
  • just git checkout master , if you can (but see below)
  • use git stash , then git checkout master , then git stash apply and eventually git stash drop

Which one to use depends mainly on whether you have already made new commits.

Necessary background information

When you're working within your own local repository, editing files, and getting ready to make new commits, there are really just three things that matter:

  • Your current commit, aka, HEAD .
  • Your current work-tree . The work-tree is where you do your work: edit files to modify them, create new files, or git rm some existing file both from the work-tree and ...
  • Your current index . The index is where you build the next commit: every time you run git add , you copy a file from the work-tree into the index. This either updates the file that was already in it, or adds it as a new file. When you run git rm , you remove the file from the index.

If you made commits

If you made new commits, this created a new branch, and the "detached HEAD" just means that your new branch has no name .

Eventually, when you decide you have things ready, you run git commit . This makes a new commit, by turning the index—where you have added, updated, and/or removed some files—into a saved work-tree snapshot that you can look at again later as needed. It writes your log message into the commit, writes the current commit as the new commit's "parent", and then writes the new commit's ID—the big ugly SHA-1, deadc0decafe... or whatever, into the branch.

In the ordinary, "on a branch" case, the place the new commit's ID goes is the branch name .

In the "detached HEAD" case, though, there is no branch name , so the new commit's ID goes directly into HEAD .

We can draw this the same way we draw any set of commits on branches:

...--o--o--o--o--o   <-- master
         \
          o   <-- HEAD

This is what you get if you commit when in "detached HEAD" mode: your new commit is on a new branch. It just has no name. If you made multiple commits, they string together as usual:

...--o--o--o--o--o   <-- master
         \
          o--o--o   <-- HEAD

If you are in this situation, all you have to do is give your branch a name. Simply run git checkout -b newbranch to create a new branch named newbranch (put in any name you like), and now all the commits you've made are on the new branch.

Once you're on a normal, named branch, you can use all the usual means to bring these commits into the main line, such as using git rebase or git merge .

If you haven't made commits

If you have not made any new commits, you may be in extra-good luck. You may be able to just git checkout master . Note: do not use --force .

The same rule applies as before, that there are three things that matter: your current ( HEAD ) commit, your current work-tree, and your current index. But now we get into an interesting part of Git: How git checkout carries changes around. If you try to git checkout master , what Git will do is compare the current commit—the detached HEAD —to the tip commit on master .

Let's draw that original chain of commits again but mark HEAD with * , and the tip commit on master with X :

...--o--*--o--o--X   <-- master

Ask yourself this: if you were Git, and you were trying to move from HEAD to the tip of master , and knowing that as Git, you can very easily tell which files are the same, and which are different, in any two commits ... what would be the fastest, easiest, laziest way move from * to X ?

Remember that the index records what would go into the next commit, so it started out holding what's in * (because you checked that out earlier). The work-tree also started out holding what's in * . If none of those have changed, then the fastest-easiest-laziest way to move from * to X is: only replace, in the index and work-tree, the files that are different .

The tricky bit here is that even—or maybe I should say, especially—if you have made changes in the index and/or work-tree, Git still does the same thing. It tries to leave all your modifications in place. It can do this if (and only if) the files you've changed are the same in the current commit * and the new target X . It only needs to swap out the "changed-from- * -to- X " files (in both index and work-tree), so it leaves everything else alone. (See also Git - checkout another branch when there are uncommitted changes on the current branch .)

In this particular case, running git checkout master reattaches your HEAD, making your current commit be the tip of master , and carries all your changes along with you—and now you can git commit , or keep editing, or whatever.

What if git checkout complains?

If git checkout master says that it can't do that—that it would overwrite your files—then you have only one sensible choice, which is to commit them. However, you can use a short-cut commit, via the git stash command.

What git stash save does is actually to make some commits, one for the index, and one for the work-tree. 1 But they're commits that are on no branch . Having made those commits, the stash command cleans up the index and work-tree so that they match the HEAD commit. You can now git checkout some other commit—such as the tip of master —and then apply the saved stash :

git stash apply

Internally, applying the saved stash is a bit complicated, because it's more than one commit. For this particular purpose, though, you can ignore all of that: applying simply "smooshes up" the commits and does its best to make the same changes to the new current commit, ie, the tip of master .

The apply step uses the internal merge code, just like git rebase does when rebase is copying commits. This means you may end up having to resolve merge conflicts. If you want to put this off, you can abort the attempt to use the stash, and because the apply has not dropped the stash, it's still in there, and you can apply it later, or turn it into its own branch.

If the apply goes well, though—usually it does—it's now safe to git stash drop the stash. If you run git stash pop , that combines the apply step and the drop step. I recommend keeping them separate in case you change your mind—once dropped, the applied stash is very difficult to recover. 2 Not everyone is as cautious as I am, of course, and git stash pop is pretty safe: it specifically doesn't drop the stash if it does not apply properly, for instance.


1 Normally, it's just these two commits, but with -u or -a , it makes three commits. These commits have the form of a merge, so that it takes only one Git reference to record them, but they are not a normal merge in any sense.

2 Since they are commits, they are recoverable for a little while—but since stash commits are on no branch, none of the usual means works at all, and the usual 30-day guarantee does not function at all.

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