I can't push checkouts of previous commits to remote bare repo. Why?
Asked Answered
S

2

0

I have a local (and main) working repo. I have also a remote bare repo for deployment purpose (via post-receive hook).

When I push a fresh commit to remote repo, it works.

But I want to be able to roll back easily when a problem occurs in a fresh commit. For that I make a try in my local directory:

git checkout <sha1_of_previous_commit>
git push remote_repo master

Nothing changes. I also do:

git checkout <sha1_of_previous_commit>
git checkout -b branch_from_previous_commit
git push remote_repo branch_from_previous_commit

Nothing changes.

I also do that:

git checkout <sha1_of_previous_commit>
git checkout -b branch_from_previous_commit
// I modified a file.
git add .
git commit -m "a commit from branch_from_previous_commit"
git push remote_repo branch_from_previous_commit

Nothing changes.

Note: Git version is 1.9, and I try to upgrade it to above 2.4. But I don't know whether it will be ok.

Shagbark answered 12/3, 2017 at 9:35 Comment(10)
It seems you're only checking out an existing commit. Unless you're committing new commits, there is nothing to push. Can you explain more about what you want to do and what you expected to happen?Vidda
@LasseV.Karlsen I just want remote bare repo to run post-receive command for previous commit. So that remote /path/to/site directory will be checked out with previous commit. post-receive script is git --work-tree=/var/www/html/site.com --git-dir=/path/to/remote/bare checkout -fShagbark
@Shagbark no you don't want a post-receive hook, you said it yourself! stackoverflow.com/questions/21640123/… I quote "Isn't it possible without remote bare repo? I am the only developer, and my local repo is the only repo. I don't want to add complexity." See my answer belowPersevere
@Persevere True, but you said that it is impossible without a bare repo at the remote server. You said that --work-tree can't get remote path, but only local (or shared) directory. So, I try to find a solution by using a bare repo at remote server.Shagbark
@Shagbark I proposed then and propose below a clear alternative which does not involve a bare repo, does not involve --work-tree: simply push, and what you are pushing will be checked out.Persevere
@Persevere There is still a repo at server, is it a working repo at /www/public/folder?Shagbark
If your working tree is at /www/public/folder and you do have a /www/public/folder/.git subfolder, that makes /www/public/folder a non-bare repo to which you can push to directly (provided you have Git 2.4+ on that remote server, and configured it to use the "push to checkout" setup.Persevere
@Persevere I don't have a non-bare repo at remote server. I have a bare repo and a post-receive script at remote server at the moment. I mean, you said your solution below isn't using any remote repo. However, it seems your solution below also contains a remote repo.Shagbark
I suggest that you do, as it is an alternative to the complexity of setting up a post-receive hookPersevere
The remote repo I reference would be a non-bare repo at your serverPersevere
P
0

As I mentioned in my previous comments, if you want to push to a non-bare repo, you need, as describe in the "Git 2.4 — atomic pushes, push to deploy, and more" article, but also in "Can git push to the current branch of a remote repository?":

  • git 2.4 or more on the server side (where you are pushing to)
  • you need to setup, still on the server side:

    cd /path/to/target/repo
    git config --local receive.denyCurrentBranch updateInstead
    

As I mentioned here, you can see a concrete example in "Deploy a project using Git push"

This assumes you are pushing to a server you control (not github.com or bitbucket.org or gitlab.com)
You mentioned

I am the only developer, and my local repo is the only repo. I don't want to add complexity.

That is why pushing directly to a non-bare rpeo is OK in this instance.

Persevere answered 12/3, 2017 at 9:47 Comment(0)
B
0

As Lasse V. Karlsen said in a comment, nothing happens in the first case because you are not pushing any update to your remote.

When you run git push, you are telling your Git to call up another Git over the Internet-phone and give it some items and/or instructions. You had two examples: git push remote_repo master and git push remote_repo branch_from_previous_commit. Let's take the first one first.

The complete sequence you used was:

git checkout <sha1_of_previous_commit>
git push remote_repo master

The first step gets you what Git calls a detached HEAD, which just means that you are not on a branch now, so that any new commits you make will be made without changing any of your existing branches. You didn't make any, so this does not matter yet, and then you told your Git to tell their Git about your master branch, which means that whatever your current branch is—or in this case, isn't—still doesn't matter.

Your Git calls up their Git—your server's Git, in this case; let's call it S for server—and your Git tells S: "please set your, S's, master to commit somehash" (where somehash is whatever hash ID is stored in your name master). S checks his master and it's already set to that hash,1 so S does nothing at all and says "OK, all done!"

Your second sequence does a bit more:

git checkout <sha1_of_previous_commit>
git checkout -b branch_from_previous_commit
git push remote_repo branch_from_previous_commit

Here, your first command detaches HEAD as before, but your second command creates a new branch name in your own repository, pointing to the current commit, which is the one you checked out in your first command. (Incidentally, you can combine these two as git checkout -b <name> <hash>.)

This time, your Git calls up S as before, but instead of saying "please set your master to hash", it says "please set your branch_from_previous_commit to hash." Presumably, S doesn't have such a branch yet; because you are using Git version 1.9, or have configured your later Git to act like your older Git, your Git winds up telling S to create that branch. Equally presumably, S does create that branch, i.e., this push succeeds. Now we must see what happens on the other machine, i.e., what is happening from S's point of view.

(For more details that are mostly irrelevant to your immediate problem, see What does GIT PUSH do exactly?)


1I am making another assumption here, that you successfully pushed your master earlier, or have made no commits to your master.


As seen on S

Your server S is quietly humming along when the Git-phone rings.2 Ring-ring! Ring-ring! S answers, and it's a call from someone claiming to be you. This other Git says "Hello, please set your branch_from_previous_commit to somehash." You (S) check that this is OK, which it is, so you create a new reference, refs/heads/branch_from_previous_commit, set to somehash. You then see that you have a post-receive hook, so you run it, feeding it this line (all as one line—I broke it into three for display purposes) on its standard input:

0000000000000000000000000000000000000000
 somehashsomehashsomehashsomehashsomehash
 refs/heads/branch_from_previous_commit

The all-zeros value is an indicator: "this reference is new". The nonzero value is the new hash. The reference name, refs/heads/branch_from_previous_commit, is the full name of the branch: the post-receive script can tell that it's a branch name because the name starts with refs/heads/, and can get the branch name by stripping off that refs/heads/ part.

If your phone call had asked you to create a tag, the full name would be refs/tags/sometag; if it asked you to create six branches, update three more branches, create one tag, and delete two branches, you would feed all twelve names into the post-receive script, with their old and new hash IDs. The old hash ID is all-zeros if the name is new; the new hash ID is all-zeros if the name is being deleted; and otherwise both hash IDs are nonzero and they are the old and new values of an updated branch or tag.3

You have not shown us your script, so now I must guess Edit: you added a comment with your script, which reads:

git --work-tree=/var/www/html/site.com --git-dir=/path/to/remote/bare checkout -f

(without even reading from standard input). This script is wrong.4

The git checkout command takes options, including -f or --force, but also including branch names. What branch name does this script supply?

If a git checkout command is given no branch name, what branch does Git check out? Click on the documentation link to see the following text:

You could omit <branch>, in which case the command degenerates to "check out the current branch", which is a glorified no-op with a rather expensive side-effects to show only the tracking information, if exists, for the current branch.

What is the current branch? Remember, we are server S, working in our Git repository. We are not the client. We are answering the Git-phone and we have our own repository, with its own branches and its own HEAD. What is in our HEAD?

Probably, our HEAD contains the branch name master. So we will check out our master again, doing (as the documentation says) an expensive kind of no-op. The fact that we just received a request to create a new branch branch_from_previous_commit is quite irrelevant: we were told to check out the current branch, so we did.

Note that if we received a request to change our master to a new, different hash, we would have verified that this was OK to do, then done it (provided it was OK), and then run our post-receive script which would check out our (updated) master. That would actually do something. But that's not what we got, and not what we did.

Hence, what you will need to do is at least one of these things:

  1. Modify the post-receive script: understand what it means to deploy different branches. This is more difficult than it looks, because each Git repository has one main index, and Git assumes that the index matches the work-tree. If you set up multiple deployment work-trees, you must set up multiple index files for each such tree, or otherwise invalidate the index.

  2. Use a push that modifies the master branch.

  3. Change the current branch on the server. If the deployment command is git checkout without a branch name, it will deploy the current branch. If you somehow change that, it will deploy a different commit. How to have that happen is a different problem, but note that git checkout <branch-name> changes the current branch (while also checking out that branch's tip commit). This may affect you if you choose to fancy up the deployment script.

In any case, it's probably wise to fancy up the deployment script at least a little bit, to scan through which branch(es) and/or tag(s) are being updated, and run the checkout-to-deploy only if the to-be-deployed branch has changed. This is not required, but it is a good idea if your server will retain multiple branches and/or tags, only one or some of which are to be deployed.


2Actually, it's probably the ssh-phone, or the https-phone, and something else answers and eventually, after deciding that it is you, hands the phone off to Git. But we don't need to worry about that.

3Tag updates require --force, since Git 1.8.2 anyway. Branch updates require forcing if and only if they are non-fast-forwards. Besides these built-in rules, your pre-receive and update hooks can reject all (pre-receive) or selected (update) reference changes if you like.

4Or at least, sub-optimal, though it will get the job done initially.

Borek answered 12/3, 2017 at 11:30 Comment(2)
No that's not it: you are missing the context which triggered that question. stackoverflow.com/questions/21640123/…. This is about configuring properly a deploy on checkout. Nothing more.Persevere
Well, that is the answer given his current setup. I am not addressing alternatives here, just the reason that this particular deployment method is not working this way.Borek

© 2022 - 2024 — McMap. All rights reserved.