kokito kokito - 4 months ago 13
Git Question

why did I have to run this git command?

After checking out the branch of a colleague, at some point
I couldn't

push
or
pull
from
remote
.
Each time I ran
git push
or
git push origin branch_name
or those variant, I always got a
everything up to date
even though I had committed changes. Ultimately, what did the trick was
git push -f origin HEAD:origin/branch_name
. So my question is, why did I have to run this? what did I do to get there?

the fatal error I got was:
fatal: You are not currently on a branch.To push history leading to current (detached HEAD) state now, use: git push origin HEAD:<name-of-remote-branch>

Answer

You should probably edit (parts of) your last two ... er, now, three—comments into your question, but for now, I'll just quote them here:

I found where I might have off road. When I checked-out the branch I was going to work on, I got this message (let's called the branch b1:

Branch b1 set up to track remote branch b1 from origin.
Switched to a new branch 'b1'.

I checked-out the branch this way:

git checkout b1.

Then when I tried to push I did:

git push --set-upstream origin b1.
Branch b1 set up to track remote branch b1 from origin.
Everything up to date

OK, in that case you are probably fine.

git checkout

The problem you are running into here (well, in my opinion it's a problem) is that git checkout crams three or four (or more, depending on how you count) different things into one command.

Normally you would use:

git checkout somebranch

to check out (switch to) your local branch somebranch. Of course, that's great for branches you already have, but no good for branches you don't have locally yet. So Git crams another, different, command into git checkout: the "create new branch, then switch to it" command.

Normally this command is spelled git checkout -b newbranch. The -b flag means "create new". It's pretty reasonable to have this command spelled the same as git checkout, with just a flag, since we're switching to this newly created branch, but it probably would be clearer if it were a separate command, such as git create-and-then-switch-to-new-branch.

Now, pretty often, when you are creating a new local branch, you are doing so with the intent of having it "track" (as its "upstream") some existing remote-tracking branch, origin/b1 or whatever. In this case, you originally had to type in:

git checkout -b b1 --track origin/b1

which is still really obvious: create new branch b1, base it off remote-tracking branch origin/b1, and make it track (have as its upstream) origin/b1. Again, this might be clearer as git create-and-then-switch-to, but it's not so bad to have it shoved into git checkout, plus checkout is a lot shorter to type!

Now, many years ago (back in 2013 or so), the Git folks saw that this action was pretty common, and decided to make Git do it "magically" for you under certain conditions. If you write:

git checkout b1

and you don't currently have a branch b1 and you do currently have an origin/b1 and you don't have any other remote-tracking branch whose name ends in b1, then (and only then!), git checkout uses the create-and-then-switch-to-new-branch command, with the --track flag. So:

git checkout b1

means

git checkout -b b1 --track origin/b1

in this one special (but common) case.

--track means "set upstream"

Each branch (well, each named, local, branch) can have one (1) "upstream" set. The upstream of a branch has a bunch of uses: git status, git fetch, git merge, git rebase, and git push all look at the upstream. Having an upstream set makes git status show how many commits you are ahead and/or behind of the current branch's upstream. And, it lets you run all the other commands with no additional arguments: they can figure out where to fetch from, or what to merge or rebase, or what to push, using the current branch's name and its upstream setting.

Branches do not have to have an upstream, and a new branch you create doesn't, by default. Moreover, if it's a "very new" branch—one that does not exist on origin, for instance—there's no origin/name yet to have as an upstream in the first place. But when you create a new branch from a remote-tracking branch, setting the remote-tracking branch as the upstream of the new branch is almost always the right thing.

(Note: having an upstream also affects git pull because git pull is just git fetch followed by either git merge or git rebase. Until they are very familiar with Git, I think most people are noticeably better off running git fetch first, then git rebase or git merge second, depending on which one they want—and more often, that's actually git rebase, which is not the default for git pull: git pull defaults to using git merge second. Once they are familiar with the fetch-and-whatever sequence, then people can configure git pull to do the one they intend, and use git pull as a shortcut to run both commands. However, there are times to keep them separate anyway.)

For completeness

The other things that git checkout can do, that don't change the current branch, are:

  • copy files from the index to the work-tree: git checkout -- <paths>, including the special git checkout --ours and git checkout --theirs variations
  • copy files from specified commits to the index and then on to the work-tree: git checkout <tree-ish> -- <paths>
  • restore conflicted merge files to their conflicted state: git checkout -m -- <paths> (as with --ours and --theirs, this only works during a conflicted merge)
  • interactively patch files: git checkout -p (this variant gets especially complicated and I'm not going to address it further)

The checkout command can also "detach HEAD" (see previous answer below) and create "orphan" branches, but both of these effectively change your current branch, so they don't fall into this special "non-branch-changing" class of checkout operations.

So what happened with the first git push

When you ran:

git push --set-upstream origin b1

you told your Git to contact the other Git on origin, give it the commit ID to which your branch b1 pointed, see if it needed any of those commits and files (it didn't), and then ask it to set its branch name b1 to point to the same commit.

Now, you'd just created your own local b1 from your origin/b1, which you recently git fetch-ed from origin, so your b1 already pointed to the same commit (you did not make any new commits) and hence your git push gave them their own commit hash back and they said "Well, gosh, that's what I already have! We're all up-to-date!" And then their Git and your Git said goodbye to each other, and your Git carried out your last instruction: --set-upstream.

This changed the upstream of your local branch b1 to origin/b1. Of course, this was already the upstream for your local branch b1, so that was not a big change. :-) But your Git did it anyway, and then reported that everything was still up to date.

Earlier answer (was mostly done when you added your third comment)

... I had one file committed with changes. git status didn't return nothing after I committed.

git status should always print something, such as (two actual examples):

On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

or:

HEAD detached at 08bb350
nothing to commit, working directory clean

In this second case, you are in "detached HEAD" mode. This special mode means you are not on a branch—or it may be more accurate to say that you are on the (single, special-case) anonymous branch that goes away as soon as you get on any other branch.

Any commits you make while in detached HEAD mode are perfectly fine, normal commits—except that there is no branch name that will make them permanent. This means these commits can go away once you switch to some other branch.

All my attempts to push were unsuccessful.

Again, actual git push output might help here, so one could tell why the push failed or did nothing. If it complained about being in "detached HEAD" mode, that would be significant: we would know that you were in the detached HEAD mode.

git fetch -a didn't return nothing as well.

(Just an aside: It's a bit odd to use -a here: that flag is almost never useful to humans and is intended more for scripting purposes. It just makes your fetch append to FETCH_HEAD rather than overwriting it. The special FETCH_HEAD name is meant for scripts to use.)

the last step I had were to git checkout origin/branch_name.

This will take you off whatever branch you were on before, and put you in "detached HEAD" mode. If you used to be in "detached HEAD" mode, it will abandon your previous anonymous branch and put you on a new, different anonymous branch.

Any commits you made while on the previous anonymous branch are ... well, not lost, precisely, but are now difficult to find (and set to expire after 30 days).

When I tried to push from there I got the following message:

fatal: You are not currently on a branch.
To push history leading to current (detached HEAD) state now, use: 

    git push origin HEAD:<name-of-remote-branch>.

Which I did [... HEAD:origin/branch_name] and it worked

Yes, but alas, this did nothing useful, because you are now on an anonymous branch that is exactly the same as origin/branch_name. This ends up asking the remote to create—on the remote, as a local branch there—the name origin/branch_name. That's probably not a good idea, since having a local branch named origin/branch_name is like having a bicycle named "motorcycle". If you also have a motorcycle and ask your friend to bring you "motorcycle", which will he bring you, the bicycle named "motorcycle", or the motorcycle?

Comments