Warm tip: This article is reproduced from serverfault.com, please click
git

Git 'Everything up-to-date' push again

发布于 2020-11-28 14:54:05

I know there are lots of questions that are asking the same but I haven't found the accurate answer for my specific issue.

I just set up a new remote git repo on my server and used:

git push --all origin

However, not everything was working on the remote server and I would like to push it again. No matter what I tried I always get the same message:

Everything up-to-date

EDIT:

I got this for git status: HEAD detached at f6b6299 nothing to commit, working tree clean.

As I mentioned above I messed something up on the remote (hint: The 'hooks/post-receive' hook was ignored because it's not set as executable.).

The first push was successful: Enumerating objects: 121, done. Counting objects: 100% (121/121)...

However, after I fixed the above error and I tried to push again I got the Everything up-to-date message. I want to push it again, but it doesn't seem to be possible.

What am I missing here and what to do to push it again to the remote?

Questioner
Radical_Activity
Viewed
0
torek 2020-11-29 00:51:17

Aha, this is quite significant:

I got this for git status:

HEAD detached at f6b6299
nothing to commit, working tree clean.

The way git push works is pretty simple, but there are a lot of details to manage. You most likely want to create a new branch name, or update some existing branch name, in your own repository to identify commit f6b6299. It's hard to give a more specific answer without knowing much more about your particular situation.

As I mentioned above I messed something up on the remote (hint: The 'hooks/post-receive' hook was ignored because it's not set as executable.)

This is a separate problem, that has to be addressed separately. There's no way to fix this directly within Git itself.

Long: What to know about git push

We start with the most basic thing to know about Git itself, which is: Git is all about commits. Those new to Git often think it's about files. It's not: it's about commits. Commits contain files, which is where the files enter the picture, but the focus is on the commits. Or, they might think that Git is about branches. It's not: it's still about commits. Branches allow us to find commits, and that's where branch names enter the picture. Being able to find the commits is pretty important of course! But it's still really all about the commits themselves.

What to know about commits

Every Git commit is numbered. Each one gets a unique number, but the number isn't sequential. We can't just count them: we can't list them as commit #1, commit #2, and so on. Instead, we get these big ugly hash IDs. They look random, but are in fact completely deterministic. A string of digits and letters like f6b6299 (but longer: this one has been abbreviated to a unique prefix of the full unique number) is actually a hexadecimal number, giving the raw number of this particular commit.

This number is the same in every Git repository, so I can tell if I have your commit by looking to see if I have a commit whose number starts with f6b6299 (it has to exactly match the whole thing, so to be sure, I'd need the longer version). Any Git repository can tell whether it has this commit or not by checking in its database. The main database in a Git repository is full of stuff indexed by these numbers: that's how your Git stores your commits (and the supporting objects that go with them).

Every once in a while, you connect your Git to some other Git. The two Gits have a sort of Git-sex where they exchange commits (unidirectionally though: one Git is the sender, the other is the receiver). These are where git push and its opposite, git fetch, come in, but we'll get to those later.

Anyway, back to the commits: each one stores two things:

  • A commit stores a full snapshot of every file that Git knew about at the time you, or whoever, made the commit. These files are stored in a special, read-only, Git-only, compressed and de-duplicated format. The de-duplication takes care of the objection that people have, that making a new commit when you've only changed one file out of thousands, would waste a ton of space. It would, except that if there are 5000 files and you changed one, the new commit just re-uses 4999 files.

    This is completely safe because all files in a commit are forever read-only. No part of any commit can ever be changed. That's because the unique hash ID for a commit incorporates every single bit of the commit's data and metadata. If you actually do extract a commit, modify something, and put it back, what you get is a new commit, with a new unique hash ID. The existing commit remains unchanged.

  • Meanwhile, each commit also stores some metadata, i.e., information about the commit itself. The metadata include your name—or the name of whoever made the commit—and email address and some date-and-time-stamps and so on. It includes the log message you see with git log, which is your chance to explain why you made the commit.

    Crucially for Git itself, though, Git adds something of its own to this metadata. Each commit stores the raw hash ID of some set of earlier commits. Most commits just store one hash ID, of their immediate parent. (We won't cover the exceptions to this rule here.)

What this means for us here is simple enough: it means each commit, in effect, points backwards to the immediately-previous commit. Let's draw this, using single uppercase letters to stand in for the actual, random-looking hash IDs:

... <-F <-G <-H

Here, H is the hash ID of the latest commit. It might be f6b6299, for instance. Whatever it is, we could jot it down on paper, or on a whiteboard, or something, so that we know it. Then we can type that big ugly hash ID in to various Git commands, such as git log. Git will use that hash ID to look up, in its big database of "all Git objects including all commits", to see if it has that (and to check that it's a commit, too, as there are some other object types).

Of course, your Git repository really does have this commit, so Git finds it. Git can therefore extract all the files that go with that commit: they're the data in the commit. Meanwhile, Git can also extract all the metadata, so it can show that you made the commit, and when; and it can show your log message. But most importantly for Git, it can find the actual hash ID of the commit that comes just before this commit, which we're calling G.

Using the hash ID of G, Git can extract commit G. This gets it the full snapshot of all files—which Git can compare to the snapshot in H, for instance, to see what changed. This also gets Git the metadata for G though, and that includes the hash ID of still-earlier commit F.

Using the hash ID of F, Git can read that commit. Git can keep doing this for every commit in the chain, working backwards from the latest, to the very beginning of history. Each of these commits, as it traverses this backwards-looking chain, is the history, as stored in the Git repository. And this all works by commit hash IDs: we just need to kick the whole thing off somehow by saving the latest hash ID somewhere.

Branch names

This is where branch names come in. Given that we can find every commit if we write down the last one's hash ID, well, why don't we have the computer write it down? Let's save the number—the hash ID—of the last commit in a file or something. In fact, let's just make another little database, which holds names like develop and feature/tall. In this database, let's have Git store the hash ID of the last commit in some chain.

We can draw that like this:

...--F--G--H   <-- main
         \
          I--J   <-- develop

That is, our names database, which holds things like branch names (but also tag names and other names), has in it the branch name main, which holds the hash ID of commit H. By definition, that's the last commit on that branch. It also holds the name develop which holds hash ID J, which by definition is the last commit on branch develop.

We say that the names point to the commits, and the commits point to their parents. So main points to H, which points back to G, and so on. Likewise, develop points to J, which points back to I, which points back to G. Commits up through G are on both branches, while commit H is only on main. If we want to emphasize this point, we can draw this same set of commits like this:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop

This represents the same Git repository, with the same commits and names; we've just drawn it slightly differently, to make it clear where the branching branched off, and which commits are on both branches.

(Note how we're using the word branch rather loosely here. See also What exactly do we mean by "branch"?)

HEAD, or, how can we know which branch we're using?

Let's say we do have this:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop

If we run git checkout main or git switch main, we'll be using the name main, and the commit whose hash ID is H. If we run git checkout develop or git switch develop, we'll be using the name develop, and the commit whose hash ID is J. We'd like a nice way to remember which name to use. Let's do this:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

Here, the special name HEAD tells us which branch name we are using. We simply attach it to some branch name. That's the name we're using! If we have this setup, and make a new commit, Git will handle that process by writing out the new commit, giving it a new unique hash ID, and stuffing that hash ID into the name main. The new commit will point back to commit H, because that's the commit we started with in order to make the new commit, and we will now have:

          H--K   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

Here, HEAD is still attached to main, but now the name main means commit K.

The ideas to take away here are:

  • branch names find commits;
  • the special name HEAD finds a branch name; and
  • as we add new commits, they point back to the commit we used to make the new commit, and Git writes the new commit's ID into the branch name.

This is what it means to be on a branch: that the special name HEAD is attached to that branch name.

Detached HEAD

What if, for whatever reason, we'd like to look at some historic commit? Given:

          H--K   <-- main
         /
...--F--G
         \
          I--J   <-- develop

we might want to inspect commit G. One way we could do that is to attach a new branch name to commit G:

          H--K   <-- main
         /
...--F--G   <-- inspect
         \
          I--J   <-- develop

and we could then git checkout inspect or git switch inspect, and we'd be on commit G and could look at it, and even make new commits:

          H--K   <-- main
         /
...--F--G   <-- inspect (HEAD)
         \
          I--J   <-- develop

Once we make a new commit, it gets a new hash ID:

          H--K   <-- main
         /
...--F--G--L   <-- inspect (HEAD)
         \
          I--J   <-- develop

with the branch growing in the usual way.

That's fine and is easy to do in Git, but Git doesn't require that we actually make this new name. We can, instead, just make HEAD point directly to some commit. That is, we can do this:

          H--K   <-- main
         /
...--F--G   <-- HEAD
         \
          I--J   <-- develop

If we do, and if we make a new commit now, we get this:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD
         \
          I--J   <-- develop

This mode of operation, in Git, is called a detached HEAD. You can work this way, but it gets annoying and painful, because branch names are where we normally store the last commit hash ID.

You don't normally want to work in detached HEAD mode

Suppose we've made commit L and haven't written down the hash ID on paper or whatever, and decide we want to look at develop and run git checkout develop or git switch develop. We'll get this:

          H--K   <-- main
         /
...--F--G--L   ???
         \
          I--J   <-- develop (HEAD)

The special name HEAD no longer contains a raw hash ID. It's not detached any more. So ... how will we find commit L?1


1Without getting into a lot more "here's how you fix a mistake" parts, we just won't. The commit will vanish from view and we will never see it again. Eventually, Git will throw it away. That's why we don't normally work in "detached HEAD" mode—except, that is, during an in-progress rebase. But that's for other StackOverflow answers.


If you're in this mode and want to save a commit, give it a name

Let's say we are in this mode:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD
         \
          I--J   <-- develop

and we'd like to have Git remember the hash ID of commit L. The obvious thing to do is to make a branch name that points to commit L. We can do that with any number of commands:

git branch save-me

or:

git checkout -b save-me

or:

git switch -c save-me

for instance. All three create a new branch name, in this case save-me, that points to commit L. The git branch command does that, but leaves HEAD detached:

          H--K   <-- main
         /
...--F--G--L   <-- HEAD, save-me
         \
          I--J   <-- develop

while the checkout and switch commands do that and then attach HEAD to that name:

          H--K   <-- main
         /
...--F--G--L   <-- save-me (HEAD)
         \
          I--J   <-- develop

So they're all more or less equally good, but if you'd like to be "on" your new branch, you can use the one that does that.

If there's already a branch name here, you can just use it

Suppose we are in this situation for some reason—we won't worry about why, just that we are:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop, HEAD

We can get back "on" branch develop, i.e., attach HEAD to it, without changing commits. The name develop selects commit J; so does the name HEAD. So:

git checkout develop         # or git switch develop

will give us:

          H   <-- main
         /
...--F--G
         \
          I--J   <-- develop (HEAD)

Our HEAD is now attached again, and we're on branch develop, using commit J. The only thing that changed is that we went from detached-HEAD mode to attached-HEAD mode, so that a new commit K will now extend the branch develop, by writing the new commit's hash ID into the branch name.

We're finally ready to talk about git push

Now that you know how branch names work, we can look at what git push really does. We have our Git call up some other Git, and the two Gits have that Git-sex I mentioned earlier. Let's say that in both repositories, we both started out with the same set of commits, and some similar branch names. They had:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop

When we ran git clone to copy this entire repository, we got all their commits and none of their branches:

          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

These funny origin/* names are remote-tracking names rather than branch names. They let our Git find the commits, but they don't let us get "on" them. If we try to use git checkout with these names:

git checkout origin/develop

Git will put us into "detached HEAD" mode.

Now, before git clone finishes, it goes on to make one branch name in our new repository. The name it makes is the one we tell it to make with the -b option, when we use git clone -b develop:

          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- develop (HEAD), origin/develop

If we don't tell it something with -b, the one branch name our Git makes is based on whatever the other Git recommends. Typically that's master (current Git and GitHub as of last year) or main (GitHub as of today, future Git perhaps as well), but if you're using GitHub, it's something you set using the web interface there. So maybe we get:

          H   <-- main (HEAD), origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

We can now create our own branch names, such as inspect pointing to G, or whatever: our only constraints are that we have to pick some existing commit whenever we ask our Git to create a new branch name.2 We find those existing commits using branch names—which find last commits—or remote-tracking names like origin/develop, which were copied from that other Git's branch names; or we just run git log or git log --all --decorate --graph --oneline or whatever, and cut-and-paste raw commit hash IDs. We create and destroy branch names as we like, with the one constraint: we have to pick some existing commit.

We can, of course, create new commits. Once we create them, they exist in our Git repository. We can now create new names that pick out these new commits. We can do that by creating a new branch name pointing to some existing commit:

git checkout -b newbranch origin/develop

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- newbranch (HEAD), origin/develop

and then make new commits:

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop
              \
               K--L   <-- newbranch (HEAD)

Note that there is no origin/newbranch, because their Git, over on GitHub or whatever, doesn't have a branch name newbranch. They don't have commits K-L either.

So, now we run git push origin newbranch. This directs our Git to call up their Git. The short name origin, which Git calls a remote, stores the URL our Git uses to send messages to their Git, so as to make things happen with their repository. Our Git calls up their Git; they look in their repository; we and they see that they don't have commits K-L; and we've told our Git to send these new commits, so our Git does just that. Our Git packages up commits K and L, along with any files that we have that they don't.

Git figures all this out on its own, using the hash IDs. Since hash IDs are universal across all Git repositories, and the fact that they do have commit J means that they have all the commits leading up to J too, they must have all the files that go with all those commits. Our Git can send them something as small as possible to let them build commits K and L that store all the same files and metadata as our commits K and L, and will therefore have the same hash IDs.

Having sent over those commits, our Git then asks their Git to create or update their name newbranch so that it points to commit L. Assuming they obey this request—our Git sends it as a polite request, not a forceful command, by default—they will now have:

          H   <-- main (HEAD)
         /
...--F--G
         \
          I--J   <-- develop
              \
               K--L   <-- newbranch

in their repository. They have our new commits and they have a name newbranch. Our Git finishes up our git push action by seeing that they agreed to create newbranch, so our Git creates our own origin/newbranch to remember their newbranch, and now we have:

          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop
              \
               K--L   <-- newbranch (HEAD), origin/newbranch

2There's a special mode with git checkout --orphan that we won't get into here, where we don't pick an existing commit.


git push with detached HEAD doesn't work very well

Suppose that, for whatever reason, we have a detached-HEAD situation like this:

            K--L   <-- HEAD
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

When we run git push origin, we'll ask their Git to take new commits and set branch names. But we don't have any branch name to identify commit L. So we won't even send them commits K-L unless we direct our git push specifically to do that. Such a specification takes a form like this:

git push origin HEAD:refs/heads/newbranch    # don't do this

It's rarely a good idea to use this construction. It does work! It's just that this creates, in their Git repository, the new branch, but doesn't create any branch name in our repository. It's a recipe for confusion, in other words. We stay in our detached-HEAD mode and if we make more commits, they're not on any branch.

It's much more sensible, in this situation, to create a new branch name, or update some existing branch name. For instance, we could just create the new name newbranch now and get on it:

            K--L   <-- newbranch (HEAD)
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

Now we can just git push origin newbranch as usual, and we and they will use the same branch name, newbranch, to identify commit L. Ourselves from next year, or whoever else might work on/with these repositories, will be much happier with ourselves from today.

Note that if we want the other Git to use the new commits, we might need to put them on whatever branch name they will actually use.3 For instance, maybe we need to update their main. If that's the case, we should probably first update our main:

git branch temp

will create a temporary name to hold commit L's hash ID:

            K--L   <-- temp, HEAD
           /
          H   <-- main, origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

after which we can git checkout main and do a fast-forward merge4 like this:

git switch main
git merge --ff-only temp

Since this all went well,5 we can now delete the name temp entirely and draw this:

            K--L   <-- main (HEAD)
           /
          H   <-- origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

Now we can safely run git push origin main, to send commits K-L to them and ask them, politely, to update their branch name main to point to commit L. If that all goes well, we have:

          H--K--L   <-- main (HEAD), origin/main
         /
...--F--G
         \
          I--J   <-- origin/develop

and, because they're deploying the main branch—this assumes that's what they are deploying—they are now using the same commit, and hence the same files, as we were using.


3Ask yourself which branch name(s) they use, how, and why. Each Git repository can use whatever names it likes, for whatever purposes; and different hosting systems, such as GitHub, GitLab, Bitbucket, and so on, provide different automated systems for using branches. There are myriad possibilities. Git provides tools and mechanisms, not pre-packaged solutions.

4A fast-forward merge is not a merge at all, but is often the right thing to do here.

5If it didn't go well, we're in good shape, because everything is saved permanently in commits, and we have names by which we can find all our commits. Obviously we'll have to figure out what went wrong and fix that, but because commits are permanent—as long as we can find them anyway—and safe like this, we'll be able to proceed.