Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get remote repo branch added to local repo

newly created - by creating a folder & running the command git init in short creating a local git repo from the top.

I have here a local git repository (newly created) with 2 branches. Now these branches are just dummy ones which I created, nothing much important to it.

$ git branch
* repo2-branch1
  repo2-branch2

I also have here a remote repository (private) from Github with a branch "TLA1", now remember the newly created local repository I mentioned above with those 2 branches? What I wanted to do is to ADD this "TLA1" branch as one of the branches with repo2-branch1 & repo2-branch2 in my newly created local repository as I mentioned.

enter image description here

Let's say the "TLA1" branch has been added. So when I type git branch I would like to have it like this.

$ git branch
* repo2-branch1
  repo2-branch2
  TLA1

Of course when I type git log when I switch to "TLA1" I would also have the commits which is in the remote repository as you can see in the image, cause for me those commits are very important.

Solutions I have tried:

I have done many research and found this, I thought this was already it as it was similar to my goal. But when I tried it I get an error.

$ git checkout -b TLA1 origin/TLA1
fatal: 'origin/TLA1' is not a commit and a branch 'TLA1' cannot be created from it

I also have not tried this as this thing might do something to my remote repo git reset --hard <remote>/<branch_name> & it seems to be not the solution I am finding.

Any solutions to this? I'd really like to have this branch on my newly created repository.

like image 816
Ice Bear Avatar asked Dec 14 '22 07:12

Ice Bear


1 Answers

TL;DR

You will need to run git fetch origin before you can run git checkout TLA1.

Long

You are on the right track, but there is a lot to know—and a lot of wrong stuff people pick up that you should be careful of.

Things to unlearn

Before you can use Git skillfully, you need to un-learn a few things. These false claims go as follows:

  • "Branches matter": this isn't wrong, but this isn't right either. The first problem is the word branch, which is fundamentally ambiguous in Git. If we stick with the two-word phrase branch name, we get something much more useful. Branch names are important, but only to humans. Git uses them to help us find commits; it is the commits that actually matter.

  • "Remote branches": this two-word phrase is, if anything, worse than the word "branch" by itself. People use it to mean at least three different things. Let's avoid this phrase too. The Git documentation uses the term remote-tracking branch or remote-tracking branch name, which are the names listed by git branch -r. This phrase is not as bad, but the word branch in it is pointless. Let's just call this a remote-tracking name.

Things to learn

What matter in Git are the commits. Know these things about commits:

  • Each one has a unique hash ID. The hash ID of some commit means that commit. No other commit—anywhere, in any Git repository—will have that hash ID. That commit—anywhere, in any Git repository—will have that hash ID.

  • Commits are what get shared, across different Git clones. The branch names aren't shared. Your Git clone has your branch names, and some other clone has its branch names. You probably want to use the same names, to keep things straight, but that's up to you (though Git will assist here because that's such a common thing to want).

  • Each commit consists of two parts:

    • A commit's main data is a snapshot of all files. These files are frozen for all time: they are stored in a compressed, read-only, Git-only, and de-duplicated form. The de-duplication handles the fact that most of the time, most new commits mostly contain the same files as the previous commit. The fact that the files stored inside a commit are frozen and can't even be read (much less written) by programs that aren't Git is a problem, of course.

    • The other part of a commit is its metadata. This includes things like the name of the person who made the commit, their email address, and a date-and-time-stamp for when they made the commit. All of this stuff is read-only too. Crucially for Git itself, Git adds, to this metadata, the hash ID of some previous commit or commits. We call these the parents of the commits.

Commits form chains; branch names help us (and Git) find the commits

Given that we have some simple sequence of commits:

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

where each letter here stands in for an actual Git hash ID, we end up with a linear chain of commits that ends with commit H. If we know H's actual hash ID, we can have Git extract this commit (see below). Or, we can have Git read H's metadata and show us who made the commit ... or use it to find the actual hash ID of H's parent commit G.

Since G and H both hold snapshots, we can have Git compare the two snapshots. All the files that match are uninteresting, because they match. Any files that don't match are more interesting, and we can have Git figure out what's different in them and show us the difference. That way, we can see what we changed. Git doesn't store changes: it just stores snapshots. But we can see a snapshot as changes, because a commit has a parent.

We can have Git go back to G, too and use that to find F, and hence to view G as changes. From there, we can go back to F, and use that to find a still earlier commit, and so on. But to do all this, we need the actual hash ID of the last commit in the chain. This is where branch names come in: a branch name like repo-branch1 just stores some hash ID. The hash ID stored in the name is, by definition, the last commit in the branch. Git will start there and work backwards. It doesn't matter if there are later commits after that point either:

...--E--F   <-- br1
         \
          G--H   <-- br2

Here H is the last commit (after F and G for instance) in br2, while commit F is the last commit in br1. Commits up through F are in both branches, but br1 starts-or-ends (depending on how you look at it) at F and works backwards, while br2 ends at H and works backwards.

Extracted commits

Because commits are read-only, we can't actually work on or with them directly. We have to pick some commit and make it the current commit. When we do that, Git extracts all the files that go with that commit into a work area, which Git calls the working tree or work-tree. These are the files you can see and work with. They're ordinary everyday computer files, that every program on your computer can use. But they're not actually in Git.

We run:

git checkout br2

(or git switch br2 in Git 2.23 or later). Git uses the name br2 to find the last (or tip) commit of that branch (note ambiguous word, in this case meaning some set of commits ending at H). Git then extracts the files from that commit, so that we can see and work with them, and makes that commit the current commit while making that branch name the current branch. I like to draw that like this:

...--E--F   <-- br1
         \
          G--H   <-- br2 (HEAD)

The special name HEAD is attached to a branch name. This is what it means to be "on the branch": that the name HEAD locates the branch name br2. The branch name itself locates the commit, H, which is the one Git extracted.

If we make a new commit, Git will package up a snapshot, add the metadata, set the parent of the new commit to be the current commit H, and use all of that to write out the new commit. This assigns the commit its new, big ugly random-looking—but not actually random at all—hash ID, which I'll just call I. Since I's parent is H, I points back to H. Then Git simply writes I's hash ID into the current name, br2, giving:

...--E--F   <-- br1
         \
          G--H--I   <-- br2 (HEAD)

Hence the special feature of a branch name is that it automatically moves to point to a new commit when we make it. Git accomplishes this by attaching the name HEAD to the branch name.

Git has other names—such as tag names and remote-tracking names—that also point to commits (by storing commit hash IDs), but you can't attach HEAD to them.

Remote-tracking names and git fetch

Remote-tracking names have forms like origin/TLA1: they start with a remote name like origin. The remote name is the one you use when you use git remote add; origin is simply the first standard one. If you use git clone to run git init and git remote add and so on for you, git clone will use origin as the standard first remote name. Note: You didn't use git clone so the name will be up to you when you run git remote add.

As noted above, you can't attach HEAD to a remote-tracking name. Moreover, you normally don't create these names yourself. You can use git branch -r to list out the ones you have right now, but if you don't create them, how do you get them?

The answer to that last question is that the git fetch command creates them. The fetch command is tremendously complicated (for reasons both good and bad) and I definitely won't cover very much of it here, but we can describe it relatively simply like this: git fetch has your Git call up some other Git and get stuff from it:

  • First, your Git has their Git list out all of their branch names, tag names, and other such names. These come with hash IDs—mostly commit hash IDs, though tag names get a little more complicated sometimes.

  • Then your Git picks through these names and hash IDs. You Git can tell whether you have the commits, because every Git uses the same random-looking-but-not-random hash IDs for the same commits. So your Git immediately knows if you have the tip commits of their branches.

    If you don't, your Git asks their Git for their commits. They offer the commit's parents too, and your Git checks to see if you have those commits. Through this kind of have/want sequence (with some important optimizations that avoid having to list every hash ID every time), your Git figures out what commits they have, that you don't, that you'll need, and asks for them.

  • They they package up all these commits and send them to you. The details here can vary a lot, but in the usual case you see "counting" and "compressing" and so on, and then your Git receives a package full of commits and other internal Git objects. Your Git then saves this all away in your repository.

    You now have all of the commits you had before, plus any commits they had that you didn't (unless your Git doesn't want them, e.g., single-branch clones).

  • Last, your Git now creates or updates your remote-tracking names. For every branch name they have, your Git creates or updates the corresponding remote-tracking name for your repository.

What this means is that you never get their branch names directly. You get their branch names and change them into your remote-tracking names. This acts as your Git's memory of their branch names. These are created or updated by git fetch. Until you run git fetch, you won't have an origin/TLA1.

Conclusion

It's the commits that matter. Branch names and other names help you (and Git) find commits.

You get your remote-tracking names by running git fetch. You tell git fetch which remote to call up. Your Git calls up that remote and sees its branches and gets its commits, unless you already have them. Then your Git updates or creates the remote-tracking names as needed. (Side note: your Git won't delete a "dead" name here unless you tell it to, so once they delete some branch name, you'll be left with stale remote-tracking names.)

You can create your own branch names at any time, but to create a name, you must have a commit for it to point-to. So you'll generally want to get their latest ones first: git fetch, then some second Git command.

Aside: git pull means run git fetch, then run a second Git command. Since it takes the two commands to do useful stuff, people like git pull, which runs the two commands. I dislike git pull because I like to insert commands between these two, and maybe use something other than the relatively thin set of choices that git pull offers for the second command, but that's up to you.

like image 153
torek Avatar answered Dec 18 '22 00:12

torek