1

My organization has prevented pushing to master. I instead need to create a separate branch on GitHub explicitly and push my code to that branch and then create a pull request.

I have my code in a local repo. The commands I have run:

git init git add . git commit -s -m <my-commit-message> git remote add origin <repo-URL> git branch -M main git pull // not sure if this is necessary git checkout -b feat/dev // the remote branch name git push -uf origin:feat/dev main 

What happens:

Error: feat/dev does not seem to be a local repository

What I did:

git push -uf origin main

What happens:

Code gets pushed to a new branch named main and I cannot open a pull request for the 2 branches have entirely different commit histories.


What have I done wrong?


EDIT:

Adding the GitHub Steps(?) required by my organization here:

  1. Create your project locally. Run it, debug it and make it so that it runs without bugs. (Use git(locally!!) if you feel like it; I always do :D )
  2. Raise a ticket (an internal portal for requesting a GitHub repository)
  3. Specify the name of the repository you require
  4. This is not something I have to do but adding it here: Run a create new repository workflow. This creates a repo with a Readme.md and a license pre-chosen by the ticket approver. The default branch is (still) master.
  5. Create a new branch named <whatever-you-want>
  6. Push your code to that branch.
  7. Raise a Review request (this is on JIRA and not GitHub)
  8. Once the code is reviewed, create a new pull request by choosing to compare between my created branch and master.

What I have always done for my personal projects:

  1. Create the Project and well, test it.
  2. run git init
  3. run git add
  4. run git commit
  5. Open GitHub and create a new repo with the master branch.
  6. run git branch -M main locally
  7. run git remote add origin
  8. run git push -u origin main

This always pushed the code to the master branch and, well worked fine.

2
  • you should git clone the repo from origin instead of creating a new repo on your local machine. Commented May 30, 2022 at 12:10
  • 1
    The push syntax is push <remote> <localbranch>:<remotebranch> or short push <remote> <branch> if local and remote name is the same. origin:branch doesn't make sense. Commented May 30, 2022 at 16:18

1 Answer 1

5

I have my code in a local repo ...

You also have an existing GitHub repository with some set of commits in it? (This is a question, but I'm going to assume that the answer is "yes" based on the rest of the text above this point.)

Because a Git repository is a collection of commits, and your goal is going to be to add commits (not make independent ones), you will need to start by cloning the existing repository, not making a new empty one. So this part is wrong:

git init 

as that's used to make a new empty repository. (It's not completely wrong, nor irrecoverably wrong: git clone starts by running git init! We'll have a look in a bit at what you can to do recover.)

Meanwhile, I'll answer this question first:

What have I done wrong?

git add . git commit -s -m <my-commit-message> 

This part is OK, except for one problem: if you created a new, empty repository with the git init step, this made a single initial commit in this new, previously-empty repository. That's not what you wanted. You wanted to add a new commit to an existing repository.

git remote add origin <repo-URL> 

(This is also something git clone would have done for you. You do need this step, but using git clone would make it easier.)

git branch -M main 

This is only necessary if you made a new, empty repository earlier and when you made your first (and now only) commit, Git chose to create the branch name master, but you want to use the name main. You probably don't want any of this, really; when you use git clone, you'll get one branch of your own, and it will be based on the branch names in the repository you're cloning, which will take care of all of this.

git pull // not sure if this is necessary 

What git pull does is two-fold:

  1. It runs git fetch. That, too, is something git clone would have done for you. This is appropriate to do.

  2. It runs a second Git command to merge or rebase your work (your new commits) with / onto some existing work ("their" commits as obtained by the git fetch step). The default action here is git merge and that's not what you wanted, but in any case, pull requires an upstream setting and you won't have one, so this second command just fails entirely. That means it causes no harm either.

git checkout -b feat/dev // the remote branch name 

The git checkout -b option creates a new branch name without using any remote-tracking names. This, too, is not what you want. This created a new branch name for your lone root commit: you now have main and feat/dev.

git push -uf origin:feat/dev main 

This won't work at all: the syntax for git push is:

git push <remote> <refspec> 

and in your case, the remote part is just plain origin. That's the name you gave to your git remote add above.

The somewhat scary term refspec is Git jargon, and for most users of git push, is overkill: what goes here is simply a branch name.

When that didn't work ...

git push -uf origin main 

Be careful with -f! This will overwrite any existing branch on the other (GitHub) end, if they accept the force option. If this is your own fork of some GitHub repository, though, you won't actually damage anything other than your own fork. (But you might damage your fork: if there was a main there before, now it's gone, replaced with your lone commit.)

Code gets pushed to a new branch named main and I cannot open a pull request for the 2 branches have entirely different commit histories.

Right: this sent, to your GitHub repository, your lone commit on main.

Fixing the mess

The exact fix will depend on several things:

  • Do you have your own fork?

    • If not, was there a main in the GitHub repository before?
    • If so, did you wreck it? 😀😱

    If there was a main and you wrecked it, and it was your own fork, you can definitely recover.

    If there was a main and you wrecked it and this was a shared GitHub repository, someone else may need to fix it. We'll leave the "shared repository, someone else needs to fix it" out entirely.

In any case, let's start by making a new clone. This isn't really required—we could do all the fixing-up in your existing clone—but it's probably the best way to go. To make a new clone of your GitHub fork, change to a new empty directory / folder on your laptop (or whatever computer you're using) and run:

git clone <github-url> 

This does the git init / git remote add / git fetch stuff for you, creating a new Git repository by taking the URL and stripping off any final .git and leading slash stuff. By doing the clone into an empty directory, when the clone finishes, you'll have just one sub-directory here with one name, and that will be the new clone, so it will be really easy to find. (You can move it afterward.)

Your new clone has copied all the commits from the GitHub repository, which I'm assuming here is your own fork. Now, if you wrecked your fork's main, the next step is to fix that, and to do that we need to use git remote add to add the shared repository as a second remote. This part gets a little complicated and is only going to be used here for the fixing purpose:

git remote add r2 <github-url> # using the URL for the *shared* repo git fetch r2 # get all of THEIR commits git push -f origin r2/main:main # replace your fork's main with r2's main git remote rm r2 # clean up to remove complications 

(If you don't need to fix anything up, you can skip this set of commands entirely. The git remote rm r2 step above tore down all the extra stuff and your fork should be back to the way it was before you wrecked it. If you wrecked a shared GitHub repo, get someone else to fix it as there's no guarantee that any of your own repositories have the right stuff in them any more, but someone else is almost certain to have what's needed.)

Now that you have a good clone, you can get to work

What you need to know:

  • A Git repository is basically a big collection of commits.
  • Each commit stores a full snapshot of every file (frozen for all time so that one can get them all back, provided you have the right hash ID for the commit), plus some metadata, or information about that commit itself, such as who made it and when.
  • A Git repository also contains a collection of names: branch names, tag names, and something Git should call remote-tracking names. (Git actually calls them remote-tracking branch names, but the word branch here is, I think, just a bad idea.)

Git actually finds commits by their big, ugly, random-looking hash IDs. These are impossible for humans to work with, so we just don't. We use things like branch names. What the branch names do—what Git gives us when we use them—is that the name turns into the right hash ID. Git needs the hash ID. We give it a name (main or feat/dev) and Git looks up the hash ID from that.

Now, the tricky part with git clone is this:

  • git clone creates a new, empty repository (git init) that has no commits and no branch names;
  • then git clone adds a remote, named origin, and does a git fetch, which gets all of some other repository's commits; but
  • git clone does not create the same branch names in the new repository!

Instead, Git takes each of their (the remote's) branch names, like main and develop and feat/ure, and renames them. Your own Git repository, on your laptop, winds up with names like origin/main and origin/develop and origin/feat/ure. Each of these is a remote-tracking name and it's just a renamed copy of the other guy's branch names.

When you run git fetch, your Git (your Git software running with your repository) reaches out to their Git (their software on their repository, whoever "they" are over at origin) and finds any new commits they have that you don't. Your Git brings those into your repository. Every commit gets a unique hash ID, so these have new unique IDs, never used before; they will never be used for any future commit either, so that the IDs Git sees here now are the right IDs forever. Your Git stuffs the new commits into your repository and updates your remote-tracking names.

This way, your origin/main holds a memory of where their main was, the last time your Git reached out to their Git. Your remote-tracking names remember their branch names: both the name (with origin/ stuffed in front) and the hash ID. But these are not your branch names. That means you can create a branch name and not worry that it will get overwritten if someone else creates the same name in their repository.1 Their branch hello becomes origin/hello, which can't possibly collide with your branch hello. Just don't name any of your branches origin/whatever.2

Having run the initial git init, git remote add, and git fetch, your git clone now does one last thing: it creates one branch name for you. The branch name that git clone creates, in your new clone, is the one you specify with your git clone command's -b option, when you run:

git clone -b xyz <url> 

But, wait a minute, we didn't run with a -b option. Now what?

Well, absent the -b option, your Git software asks their (origin's) software which branch name their repository suggests. That's almost always main these days (and used to be master). So that's what comes out as the recommendation, and that's the one branch name your Git creates, in your repository.

A branch name must select some particular commit. So the commit your Git chooses is the one named by your remote-tracking name that corresponds to the branch name here. If they recommended main, you have an origin/main remote-tracking name, remembering their main. That name specifies one particular commit. That's the one particular commit your Git uses to make your name main.

The end result is that you get one branch in your new clone, matching one branch in the repository you cloned. It's all a very roundabout and complicated way of doing something ridiculously simple—which is a good description of a whole lot of things that Git does.


1When using GitHub forks, this is not a real problem. But if you use Git the way it was before GitHub came along, it is a real problem: you might pick a branch name like fix-xyz-command, and Bob might pick the same name for a different fix for that command. So in real world situations, this was a problem, and remote-tracking names solved it. Within the GitHub world, it almost never comes up, though there are more complicated situations where it still can.

2This is technically not a problem either, because your local branch names are in a separate name space. But while Git won't get confused if you name a local branch origin/main, you probably will, so just don't do it.


Grabbing the code you worked on earlier

You're probably ready now to create your feature branch in your new clone:

git switch -c feat/dev 

or:

git checkout -b feat/dev 

These both do exactly the same thing: they create a new name, feat/dev, that selects the same commit you have out right now (the one from main, probably). Then, having created this new name, they switch to that branch.

Now you need to get the files from the other repository you made. There are several ways to do that, but one somewhat magical one is this:

git --work-tree=/path/to/wrong/repo add . 

A more direct way is to just copy the files from the "wrong" repository's working tree into the new one's working tree, and then run git add . as you did earlier.

I always like to run:

git status 

and then:

git diff --staged 

at this point, to check that I have the right files "staged for commit", and see what I have changed. Only then, having reviewed my work and checked it over for any obvious errors, do I run:

git commit 

(or git commit -s3) and write up a commit message. This updates the current branch (now feat/dev) with the new commit, and now I can run:

git push -u origin feat/ure 

to create branch feat/ure in my GitHub fork and make a pull request on GitHub (using gh, the GitHub CLI, or using the web site).


3The -s option makes a signed commit. With any Git commit you make, or that anyone makes, anyone can claim to be anyone they want. I can make commits that claim to be authored by Barack Obama or Volodomyr Zelenskyy. Some people like to add digital signatures, using PGP or GPG or ssh digital signature techniques. These can (in the absence of quantum computers at least) serve to verify that these are in fact by you and not by some imposter. However, I find signed commits to be serious overkill and a pain; I prefer the Git project's method of simply signing official release tags.

Sign up to request clarification or add additional context in comments.

10 Comments

This answer is excellent! In fact so nice that this might just be turned into a community doc for recovering from git problems!
Ok, now coming to the part where I clarify what I did, the GitHub repo was created afterwards. I originally had a local directory. All the commands till git remote add origin; they were being done by me for the past 2 weeks (not the git init part of course; that was only once).
Secondly, before pushing to master on GitHub was prohibited, this is how I always pushed stuff to GitHub. It gave me a two-fold advantage. It helped maintain the structure I wanted locally as well as differentiate it from the one expected on the remote. In fact, I have been doing this very thing for the past 2 years! (Of course, then I did not use branching and stuff a lot, but still...)
Please take a look at the EDIT in the post. There I specify the steps we require to create a new repo on GitHub). I had posted it as a comment initially but now added in the post itself.
I find signed commits to be serious overkill and a pain that makes 2 of us, but org policies....
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.