10

Using the latest git (2.1.0.24), whenever I try to git rebase -i to squash some commits, the squash cannot detach HEAD state. I expect it to squash the commits and put me back on my branch as I expected. There are no unstaged files, changes in my working tree, or anything in the stash. Why is it doing this?

> [master] » git rebase -i HEAD~3

(I squash a few commits)...

pick c9e9b62 Fixes super important bug #123. 
squash c0dc9f9 wip 
pick 5385a37 wip2

# Rebase fb83e59..5385a37 onto fb83e59 

(Then it gives me)

Note: checking out 'c9e9b62'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at c9e9b62... Fixes super-important bug #123.
could not detach HEAD

It appears to rebase successfully, but for some reason won't put me back on the branch.

> [c9e9b62] » git rebase --continue
No rebase in progress?

git graph log BEFORE the rebase attempt:

* 5385a37 (HEAD, master) wip2
* c0dc9f9 wip
* c9e9b62 Fixes super-important bug #123.
* ff80ed9 random commit msg
* 1f407d5 random commit msg
...
* ef106db random commit msg
*   6c244ef Merge branch 'sentences'
|\
| * a641cbf (origin/sentences) random commit msg
| * bfe8eae random commit msg
| ...

git graph log AFTER the rebase attempt:

* c9e9b62 (HEAD) Fixes super-important bug #123.
* ff80ed9 random commit msg
* 1f407d5 random commit msg
...
* ef106db random commit msg
*   6c244ef Merge branch 'sentences'
|\
| * a641cbf (origin/sentences) random commit msg
| * bfe8eae random commit msg
| ...
atp
  • 30,132
  • 47
  • 125
  • 187
  • Did you hit conflicts? Did you `git rebase --continue`? Or `--abort`? – Edward Thomson Aug 29 '14 at 03:59
  • There don't seem to be any conflicts. (Updated question with what happens when I do --continue). – atp Aug 29 '14 at 04:08
  • Could you show the missing part of the conversation? – matt Aug 29 '14 at 04:11
  • Can you provide the graph log of your repository? `git log --graph --oneline --decorate` – Joël Salamin Aug 29 '14 at 04:24
  • 1
    @JoëlSalamin, added recent graph log commits. Does that help? – atp Aug 29 '14 at 04:33
  • The log shows your head is not detached. Git always says "detached head" _during_ the interactive rebase, but that's just temporary. Interactive rebasing _is_ performed in "detached head" state. This is so that if something goes wrong the original commits can be restored. But nothing went wrong, so at the end, you are out of detached head state again (and the new commits are used - you can tell this because the numbers have changed). – matt Aug 29 '14 at 04:34
  • Sorry, I was thoroughly confused and attached the log after I gave up and checked out master again. And I updated the description: the problem is it cannot detach the head as you said. Also updated the log. – atp Aug 29 '14 at 04:40
  • In the graph log i don't see where is your local master, that's the graph after a `git rebase -i` attempt? If so, can you provide the graph before starting the rebase process? – Joël Salamin Aug 29 '14 at 04:43
  • Have you got untracked files, or other stuff that would prevent a merge? That's usually the reason for being unable to detach head. – matt Aug 29 '14 at 04:46
  • Permission problems can also cause this... – matt Aug 29 '14 at 04:48
  • @JoëlSalamin, yep, added before & after. matt, none of the usual suspects. Not sure why the error message is so general though. – atp Aug 29 '14 at 05:05
  • Is your `master`still present on the graph after your attempt? I don't see it anymore in your graph sample – Joël Salamin Aug 29 '14 at 05:19
  • @JoëlSalamin, no, it is not present. I think with the error it makes sense because it leaves me with HEAD pointing to a commit instead of a branch. – atp Aug 29 '14 at 05:38

4 Answers4

17

The message could not detach HEAD comes from the interactive rebase script, specifically right here:

GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name"
output git checkout $onto || die_abort "could not detach HEAD"
git update-ref ORIG_HEAD $orig_head
do_rest

The curious part here is that the git checkout $onto step appears to have succeeded: right before the failure, you got:

HEAD is now at c9e9b62... Fixes super-important bug #123.

as output, and that's from the git checkout that happened here. Then, after apparently succeeding, git checkout apparently exited non-zero as if it had failed. This invoked die_abort which terminated the rebase attempt.

Now, the documentation claims that a post-checkout hook

cannot affect the outcome of git checkout.

However, in the source, near the bottom of switch_branches, we have:

    ret = post_checkout_hook(old.commit, new->commit, 1);
    free(path_to_free);
    return ret || writeout_error;

where post_checkout_hook runs the post-checkout hook and grabs its exit code. So there appear to be two ways for git checkout to return a failure status here: either there's an error writing the tree (e.g., the disk has filled up), or you have a post-checkout hook and it is exiting non-zero. The latter seems more likely. (And, I tested this and it does in fact cause the checkout to "fail". Since the source uses ||, any nonzero exit in the hook turns into an exit status of 1 for git checkout itself.)

Do you have a post-checkout hook? If so, what does it do?

(It's not clear which is wrong here, the documentation or the git checkout command, but at least one is wrong. I'd say probably the documentation, which should say something more like "cannot affect the files that git checkout will write, but can make the command itself return a failure status".)

torek
  • 448,244
  • 59
  • 642
  • 775
  • 1
    Thank you so much. I have a post-checkout hook, which strips error messages from ctags: `ctags -R maindir 2>&1 | grep -v "ignoring null"`. The exit status sure enough is `1`. The `grep` man page says, "The exit status is 0 if selected lines are found, and 1 if not found," and so reports 1 even though it IS matching and stripping out the `"ignoring null"` messages because `-v` reverses its functionality! So I added `exit 0;` to the hook and now rebasing works: `Successfully rebased and updated refs/heads/master.` I *never* would have solved this problem without your help. Thanks! – atp Aug 29 '14 at 08:37
  • Looks like yet another case of bad documentation on git hooks! When I first started writing git hooks (back in the days of git 1.5.x), it was really bad. It's still not so good... – torek Aug 29 '14 at 08:40
  • Thanks for the help. I would have spent a long time figuring this out on my own. – Tim D Mar 11 '15 at 18:29
  • `exit 0` on my post checkout (which runs `yarn install`) fixed this. Thanks @ash – Moak Dec 01 '17 at 16:42
1

This can also happen if a file that has been marked assume-unchanged or skip-worktree has also been modified:

  1. You won't see the changes in git status.
  2. You might be used to git rebase autostashing all changes for you but it doesn't seem to deal with these ones (as of Git 2.21 and no mention of it changing in subsequent release notes).

You can list such files if you want confirm this but the error message should already alert you to something strange because it will show a filename that doesn't appear in git status.

Sam Brightman
  • 2,831
  • 4
  • 36
  • 38
0

As the previous comments hint to, maybe you have a post-checkout hook that is making a non-zero exit. I had one where the last command could have a different exit based on an if switch:

if false
  command-that-exits-with 0
else
  command-that-exists-with 5
  # hook will exit with 5
end

# EOF

even though my script was still happy if the condition wasn't satisfied. And indeed:

$ .git/hooks/post-checkout
doing stuff!
$ echo $?
5

I added exit 0 at the end of the script, et voilà.

Jonathan Allard
  • 18,429
  • 11
  • 54
  • 75
0

Another possible reason is if moving around the commits create some inconsistencies... eg. let's say you have:

HASH2 change line in file b
HASH1 rename file a to b

where of course HASH1 is the oldest. If you try to move HASH2 before HASH1 you will get could not detach HEAD.

Of course it seems obvious here, but in a longer real-life example with multiple commits and squashes being rebased, you might overlook this type of inconsistencies!!

Stefano
  • 18,083
  • 13
  • 64
  • 79