Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I push to a checked out branch of a non-bare repository?

I am confused regarding a scenario that I created. I created a repository on Github (Lets call it A) and pushed code to it. After that I cloned that repository to my local (Lets call it B) such that origin of my local was remote repo A.

Now I cloned from my local B to create another local instance C. Now I had remote origin of C as repo B and upstream of C was A.

A → B → C

This is similar to forking but here I created clone on client side instead of server side.

Now if I tried to use push from C to its origin B:

git push origin 

then I received an error stating that I cannot push to non-bare repositories. I understand that pushing to non-bare repositories can result in loss of commits in remote not present in local.

However is this case not similar to the one where i push my code from B to A ?

I am confused if B to A is possible then why not C to B.

For merging to A we can push to upstream as:

git push upstream
like image 851
harsrawa Avatar asked Feb 07 '23 02:02

harsrawa


1 Answers

Some basic stuff

  • Whenever you git clone, from a developer point of view, you'll want to work on the code that you just cloned. So GIT gives you a "working tree" to work upon. It's called a tree because it resembles one when you consider all the commits and branches you made and put on a graph.

  • The cloned repository is called non-bare repository. To create a non-bare repository, you do a simple git init - which is what the original programmer did to start tracking the cloned code with GIT. You could also clone it into a bare repository but the details and usefulness of it should be an answer of its own in a proper question regarding it.

  • A bare repository does not contain a working tree. It is only meant to store your code - a server for code managed by GIT, if you prefer. To create a bare repository, you do a simple git init --bare name_of_repository.git. It will create a directory named name_of_repository.git containing all the needed files by GIT. The git extension is just a convention used; it isn't required and could be anything or nothing at all.

  • There is something like a pointer in GIT that is called HEAD. It points to the latest commit that is active in the branch you work on, be it a bare or a non-bare repository.

  • Branches are like 'different copies' of the code you just pulled from a remote repository (that might have different modifications or not). It has whatever names the developer thought to be proper. They are useful because you can work on different functions or fix different problems without worrying about the current code being developed by you or others. Later, you can always merge everything into the main branch - usually master - and then delete those merged branches that are not necessary anymore.

  • GIT tries its best to avoid problems between versions of files of different locations or branches. So he won't allow you to git push in some cases it determines to be chaotic to say the least. GIT is never wrong because it asks you to check, change or force what you are doing. So any error won't be GIT's fault but yours only.

Understanding the situation

Let's consider the following:

  • The A repository is a bare repository. Both B and C repositories are non-bare repositories. This means A has no working directory and is used for storage only. B and C are used for work you need to do.
  • Generally speaking, you (usually) have branches. Normally a beginner will not create branches because he is learning and might not even know about branches yet - even though they are useful for many reasons. So he will almost always be on a 'master' branch - the default branch.

That being said, let's say you modify some files in B. You do git commit as many times you want and even a git push at the end. Or you don't do anything at all. But you are on master branch.

Later on, you modify files in C. And you do commit and tries to push to A. Remember: you are on master branch of C. The git push works!

Then, you try pushing C to B as well. It doesn't work.

Result: GIT will (not) literally scream, warning about the fact you are trying to defile (update) the master branch of the non-bare repository B which its HEAD points to another commit! If he lets you do the push, you will mess the history tracked by GIT on repository B. It won't know what happened to B anymore! You might even overwrite modification on that branch with the same name residing on B! So no, you can't push to B from C if both are non-bare repositories!

What now?! Will my world end like this?! What could the great I have done?! How possibly could GIT ignore his master's wishes?! This is pure heresy!

Solution

1 - Have two branches on B - the master and a temporary one. And make the head points to the temporary branch. Example:

cd B                  # change to B's working directory
git branch temp       # create 'temp' branch
git checkout temp     # change from master branch to branch temp

2 - Now, move to C working directory (wd for short) and pull with the contents of B. Note that I'm considering that B is a remote of C (as you have mentioned in your case):

cd ../C               # change to C's working directory
git pull B master     # pulls B's modifications to C

3 - Modify your files in C. Note that you are on C's master branch. Then, after committing modifications of C, push it to B's master:

git push B master     # pushes C's mods to B's master branch

4 - Now go back to B wd and make the HEAD point back to the master branch:

cd ../B               # change to B's working directory
git checkout master   # change from temp branch to branch master

5 - You can delete the temporary branch if you won't be using it anymore:

git branch -d temp    # delete branch temp

6 - If you are doing new modifications in C, you don't need to do both steps 4 and 5. If you do, any time you wish to do modifications in C, you will need to do both steps 1 and 2 beforehand.

And this solves your issue! Probably...

Clarifications & Reinforcements

  • git branch name_of_the_branch creates a new branch;
  • git checkout name_of_the_branch makes the HEAD points to this new branch;
  • git checkout -b name_of_the_branch creates a branch and makes the HEAD point to it in one single command. I used the longer method because you should know the longer method as well;
  • as said previously, don't delete the branch if you are gonna use it later on. But I do recommend doing so to avoid problems with temporary branches in both repositories while pulling/pushing or even merging. Create the temporary branch as needed basis - quite easy with terminal history - and delete it afterwards;
like image 83
José Avatar answered Feb 08 '23 15:02

José