I've looked through 4-5 other threads about this, but haven't been able to pinpoint where I went wrong. here's the issue:
I have a remote branch, in-progress-show-hide-countries
.
I logged into another PC that doesn't have the remote branch tracked yet.
I run something, probably git pull
, and it fetches some things, but my memory gets fuzzy around here. I notice that my local files don't have the changes that remote has.
Here are some of the things I've tried:
$ git status
On branch in-progress-show-hide-countries
Your branch is up to date with 'origin/in-progress-show-hide-countries'.
nothing to commit, working tree clean
git diff shows no code differences, but the changes are definitely visible on my github.
$ git diff in-progress-show-hide-countries...origin/in-progress-show-hide-countries
Git log shows that I'm.. probably?? tracking the remote branch
$ git log
commit 4624760c5d66df430fc3d91fc718ffd04f27e292 (HEAD -> in-progress-show-hide-countries, origin/in-progress-show-hide-countries, main)
But maybe I don't know what I'm saying, so here's my git remote -v
$ git remote -v
origin [email protected]:XXX/fullstackopen.git (fetch)
origin [email protected]:XXX/fullstackopen.git (push)
This thread ( How to compare a local Git branch with its remote branch ) suggests that I can use git diff <local branch> <remote>/<remote branch>
to see what the difference between my local and the remote branch looks like, so I try:
Empty output
$ git diff in-progress-show-hide-countries origin/in-progress-show-hide-countries
Any clues? I realize I can just delete everything locally and re-clone, but that wouldn't help my learning very much. Appreciate any advice.
CodePudding user response:
You should make sure that you are on the correct branch, which you can change using:
git checkout -b in-progress-show-hide-countries
If you have no real changes on your system, you can just use
git reset HEAD --hard
which will set you back to the most recent commit on the Github repository. Do not use lightly! The work that is reset can't be recovered.
CodePudding user response:
The trick—well, okay, one of many tricks—with Git is to realize that branches don't mean anything. You use branch names in your Git to keep track of commits. It's not the branches that matter, it's the commits.
Those SHA IDs, 4624760c5d6...
, are what matter. Those are what Git needs. You can have any number of names attached to any one of these:
commit 4624760c5d66df430fc3d91fc718ffd04f27e292 (HEAD -> in-progress-show-hide-countries, origin/in-progress-show-hide-countries, main)
This shows that there are three names for 46247...
, namely in-progress-show-hide-countries
, origin/origin/in-progress-show-hide-countries
, and main
. The special name HEAD
is "attached to" the name in-progress-show-hide-countries
, meaning in-progress-show-hide-countries
is your current branch name.
The use of a branch name allows you to avoid typing in 4624760c5d66df430fc3d91fc718ffd04f27e292
every time. (I can't type that in correctly every time: I used cut-and-paste with the mouse. I have enough trouble with in-progress-show-hide-countries
, which I also cut-and-paste.) Besides that, by selecting a name like main
or in-progress-show-hide-countries
as your current name, you arrange for Git to update the hash ID stored in the name whenever you make a new commit. So your name always remembers the latest commit. A nice, short, easy to type, easy to remember name, like main
or wip-shc
for work-in-progress on show-hide-countries, is a good idea ... for you. Git doesn't care about the names; Git cares about the hash IDs.
Commits themselves also remember commit hash IDs for you. So whenever you make a new commit, the new commit remembers, for you, which commit was the current commit at that time. Make a second new commit, and the new commit remembers the first new commit.
If we replace each commit hash ID with a simple uppercase letter:
... <-F <-G <-H <-- wip-shc (HEAD), main
we can see how attaching HEAD
to and making new commits work. Remembering that commits always point backwards like this, we make a new commit I
, whose parent is the current commit H
:
... <-F <-G <-H <-- main
\
I <-- wip-shc (HEAD)
The name main
did not move, but because we were "on" branch wip-shc
, the name wip-shc
did move. Make another new commit J
and we get:
... <-F <-G <-H <-- main
\
I <-J <-- wip-shc (HEAD)
The name always picks the last commit, from which Git works backwards. There are now two ways to get to commit H
, in this drawing (and three ways to get there in your existing repository) but all we need is some way, any way, to get there in order to find it. Git will automatically work backwards when it needs to. You just have to supply it the starting hash ID—for which you use a branch name like wip-shc
or main
, or a remote-tracking name like origin/in-progress-show-hide-countries
.
Your Git will automatically create or update the remote-tracking names—the origin/in-progress-show-hide-countries
style names—when your Git reaches out to their repository, over at origin
,1 and has them list out their branch names. If they have in-progress-show-hide-countries
pointing to commit H
, your Git creates or updates your origin/in-progress-show-hide-countries
to point to commit H
.
If your name wip-shc
points to the same commit as their name in-progress-show-hide-countries
which is your origin/in-progress-show-hide-countries
, those two names are synchronized with each other, and you are "up to date". That's all this means.
1Confusingly, Git calls origin
a remote, and origin/main
and the like are thus remote-tracking branch names. They're not actually branch names once they're in your repository though. So I drop the word branch and call them remote-tracking names. More confusingly, Git uses the word track in at least two or three different ways. A branch can "track" a remote-tracking [branch] name, and files are either "tracked" or "untracked", and every one of these mean something different.
What about when your files don't match
The files you can see and work on / with, in any clone of any repository, are not in the repository. The files that are in the repository are stored in a special, read-only, Git-only, compressed and de-duplicated form, that only Git can read and literally nothing—not even Git itself—can overwrite. These files are entirely useless for getting any new work done. So these files are not used for getting new work done.
Instead, when you select some commit to work on—with git checkout
or git switch
, usually—you're directing Git to copy the files out of that commit. The files come out of the commit and are de-compressed back to useful form (causing duplication, if they have duplicates). The expanded-out files go in your working tree (sometimes shortened to work-tree). These files are not in Git, even if they came out of Git.
As you work on those files, nothing happens inside Git. Those files aren't in Git, so changing them—or adding new files, or removing files; you can do whatever you want here—has no effect on Git.
At some point, though, you presumably want to use these updated files to make a new commit. To do that, you must use git add
2 followed by git commit
. That makes the new commit, which then updates your current branch name.
Note that your branch names are yours. They are not in any other Git repository. If you let someone clone your Git repository, your branch names become someone else's remote-tracking names (so now they can see your branch names), but they're still your branch names, which aren't someone else's branch names. Someone else's branch names are probably your remote-tracking names.
Once you make a new commit, you may or may not be using similar branch names, but now they won't be in sync. You synchronize by sending your new commit to them, and that's where git push
would come in.
2You can sometimes get away with git commit -a
to avoid the git add
step. But doing this to avoid learning about Git's index is a bad idea: Git will eventually whack you over the head with its index, forcing you to learn about it. Learn about it before it's an emergency. Git is a pain sometimes, yes.
Names do not have to match
There are good reasons to use the same name on both "sides", as it were: it becomes pretty crazy to try to remember, e.g., my xyzzy is Fred's plugh, but René uses the name vue and Sasha calls it супеp. If everyone uses the same name, your origin/main
and your main
at least have the same purpose, even if the hash IDs get de-synchronized. But in some cases that's literally impossible: you might have two people you work with who both call their (different) branches tall
, and hence two remotes batman
and robin
and you can't necessarily just one branch name on your side called tall
, so you end up with tall-batman
and tall-robin
, or whatever.
When you use git fetch
, the names don't have to match, because your Git is going to create or update remote-tracking names. You'll have origin/whatever
for their branch whatever
. You can use any name you like on your side.
When you use git push
to send commits to origin
, though, you have to end your git push
with a request that they set one of their branch names. If you have branch names that don't match, like the batman
and robin
case, you do this with, e.g.:
git push robin robin-tall:tall
Here you write your branch name on the left side of the colon :
, and their branch name on the right. (The robin
in the middle here is the remote: a short name for the URL where you're sending the commits; this is the same name you use with git fetch robin
, and that is where names like robin/tall
comes from: your Git pastes the remote name in front of their branch name.3) If your branch names do match up, though, a simple:
git push origin somebranch
suffices: this means use the same name on both sides, i.e., send them my latest somebranch
commit(s) and then ask them to incorporate those commits into their branch named somebranch
.
They may refuse to update their branch. In this case, we get into more complexities: we have to find out why they refused and figure out what to do about that. But this, so far, is the simple part of git push
. It means send them my new commits that they don't have yet, then ask them to set one of their branch names. You must pick the commits to send—that's from the left side of the colon—and the name, from the right side. If you use the same name on both sides, you get to leave out the colon and type less.
3It's actually quite a bit more complicated internally—this is a Git tradition after all, nothing can be as simple as it looks—but that's the end effect.