Say I forked the Upstream. My remote repo has a model-package branch.
when I do git fetch upstream model-package. It fetches from the upstream to the local repo right? What happens when I do git checkout model-package? Does it point to the remote model-package branch?
What happens when I do git checkout -B model-package upstream/model-package? Does it creates a new model-package branch on the local repo that keeps on sync with the upstream/model-package branch? If it's so will it replace the previous model-package branch?
I tested practically, but quite did not get it. Can someone clarify this? Thanks.
CodePudding user response:
Git Checkout Vs Git Checkout -B with upstream repo
git fetch upstream model-package
— This command downloads updates to model-package branch, and saves them to upstream/model-package.
You can compare changes to the local model-package branch via git diff model-package upstream/model-package
, and if you're happy with the changes, you can integrate them via git pull upstream model-package
, which will apply all the changes to your local model-package branch.
git checkout model-package
— This will move you from your current branch to the mentioned branch, model-package
. All your changes in this branch will be in your local until you push to the remote branch.
git checkout -B model-package upstream/model-package
— This will create a new model-package branch on the local repo that tracks the upstream model-package branch. It won't sync with your upstream remote branches until you push the changes to the remote branch. You can change remote-tracking branch (sync) at any time, with any remote, any branch, it's completely up to you.
Click here for more information on, how to change remote tracking branch.
Note: Correct me If any points that I have mentioned here are incorrect.
CodePudding user response:
Md Samiul Alim's answer is fine, and also has the virtue of being short—many of mine don't as I won't take the time required to make them short—but if branch names aren't "clicking" for you, the reason might be as simple as this: Branch names, in Git, don't matter.
We—as in people, documentation, etc.—often talk about Git commits as being "on a branch". This isn't wrong. The problem is that it isn't right either. The very notion that some commit is "on" some branch is mushy, muddle-headed, and misleading. Commits in Git are their own thing: Commits exist independently of branches. A commit either is there, in the repository, or it isn't and therefore it does not exist. Commits are Git's raison d'être. In an important sense, nothing else matters: only the commits matter. Git is all about the commits.
Commits are ...
Git's commits are:
Frozen for all time. This is an inherent property of all of Git's internal objects, though this post won't explain why. The thing to remember here is that once we make some commit, not even Git can change it. The new, unique commit we just made is stuck the way we made it, forever. (If it's bad, we can just let Git eventually "forget it", though again I won't go into any detail here.)
Numbered. Every commit has a unique number, expressed in hexadecimal, which Git calls a hash ID or object ID. These things are huge, ugly, and generally impossible for humans to remember:
5a73c6bdc717127c2da99f57bc630c4efd8aed02
for example. Git needs the commit number to do anything with the commit, including simply to find it.Snapshots. Each commit holds a full snapshot of every file, as of the form it had at the time you (or whoever) made the commit. The files inside any one commit are in a special form, readable only by Git itself and literally unwritable by anyone or anything (including Git itself). They're compressed and de-duplicated, so when most commits mostly re-use most of the files from other commits, the commits take almost no space because they're not actually storing the files again.
Nodes in a graph. This requires more explanation, but the way Git handles this is that besides storing a snapshot, each commit also stores some metadata, or information about the commit itself. That includes who made the commit and when. It includes a log message; you get to supply this log message at the time you run
git commit
, if you're the one making the commit. And, it includes the raw commit numbers—the hash IDs—of a list of earlier commits.
Most commits store, in their metadata, the raw hash ID of exactly one previous commit, which we call the parent of the commit. That means that this commit itself remembers which (single) commit comes just before it. We say that a commit points to its parent, and if we want to draw this, we can do it like this:
<-H
Here, H
stands in for the hash ID of the latest commit. It has an arrow coming out of it, pointing backwards to its parent commit:
<-G <-H
The parent of H
is G
. But G
is a commit too, so it has another arrow coming out of it, pointing to its parent F
:
... <-F <-G <-H
This repeats all the way back to the very first commit ever: commit A
in our example repository here, which therefore has just eight commits in it, A
through H
. This first commit doesn't point back, because it can't: its list of previous commits is empty. That gives programs like git log
, which work by chasing along the backwards-pointing arrows, permission to quit, so that they don't have to run forever.
This nodes in a graph thing, or pointing backwards, is what makes most of Git work. All Git needs to read every commit in this simple case is for us to supply to Git the raw hash ID of the last commit, H
. But where will we keep this last commit hash ID? Do we jot it down in the office whiteboard? Do we write it down on a slip of paper and carry that around in our pockets? We could do either of these, but that's pretty painful. We have a computer: why don't we have the computer store the hash ID, perhaps in a file or something?
Branch and other names help us, and Git, find the commits
This is where branch names come in. A branch name is just an entry in some file—in a database of some sort—in which we have Git store the hash ID of the latest commit.
More precisely, the branch name stores the hash ID of the latest commit that is to be considered "on" that branch. Let's take note of several things here:
Branch names owe their existence to commits. It's not the other way around. The branch name cannot exist without a commit. The name points to a commit. The commit has to exist!
The hash ID stored in a branch name is not permanent. We can update the name. Like erasing the hash ID written on a whiteboard, we can replace the hash ID stored in the name. This means branch names "move around", at least when we draw them like I do (you'll see this in a moment).
If we already have a hash ID, we don't need a branch name. If we find some commit's hash ID somehow—regardless of how—we can give that to Git directly and not bother with a branch name.
More than one branch name can hold the same hash ID.
Let me illustrate the last part, and then show branch names moving around. We'll start with our simple chain ending at H
. For laziness and ASCII-art-on-Stack Overflow purposes, I will stop drawing the arrows between commits, but remember that like all parts of any commit, they're frozen for all time, pointing backwards from child to parent:
...--F--G--H <-- main
Here, all I did was add a branch name, main
, pointing to (storing the hash ID of) commit H
. This means commit H
is the latest commit on branch main
. Git can find every other commit by starting here and working backwards, so all the commits are on main
, but main
points to H
. (We call this the tip commit of branch main
.)
Now let's add a new branch name, br1
, and make it point to H
too:
...--F--G--H <-- br1, main
This means H
is now the latest commit on branch br1
. But it's the latest commit on branch main
. So which branch is it on? Git's answer is that it, and all the other commits too, are on both branches at the same time. By creating a new branch name, we changed which branches the commits are on. That's entirely normal in Git! Nothing about the commits has actually changed, it's just that now we have two names by which to find them.
Now that we do have two names, we need a way to remember which name we are using. Git does that for us by attaching a special name, HEAD
—this isn't a branch name and it's very much a reserved name in Git1—to one of the branch names, like this:
...--F--G--H <-- br1, main (HEAD)
Here, we're "on" branch main
—the git status
command will say on branch main
—and hence we're using commit H
. If we run:
git switch br1 # or git checkout br1
we get:
...--F--G--H <-- br1 (HEAD), main
We're now "on" br1
, but still using commit H
, so nothing else changes. Git says to itself: Oh, you'd like to switch branches to br1
, hm, that's the same commit we're using now, I don't really need to do anything except update the HEAD
, so I'll do that and say all done.
1That's why you should be careful to spell it in all uppercase, even if lowercase sometimes works. Or use @
, which is a one-character synonym for HEAD
. The mechanism Git currently uses to store the branch name in HEAD
is to have a file, .git/HEAD
. If this file ever goes missing, Git stops believing that the Git repository is a repository. If your computer ever crashes, because the HEAD
file tends to be active, your computer might decide it's been corrupted and remove it. Sometimes this makes Git declare that your repository isn't a repository any more. Putting a HEAD
file back in makes Git happy and your repository works again. That's a handy trick to know, if your computer crashes a lot.