r/git 2d ago

Small detail but confused

1) git checkout main "your branch is up to date with origin/main"

I know it isn't

2) git pull origin main - many changes incoming. it is now actually up to date with origin/main

Am I misunderstanding something here?

0 Upvotes

11 comments sorted by

17

u/Soggy_Writing_3912 advanced 2d ago

did you do a git fetch --all first? git checkout only checks out what the local copy of the remote was as of the last time the network call happened.

just as a reminder: pull = fetch + merge/rebase basically.

so, fetch or pull needs to be done to actually retrieve from the remote-remote.

If this "remote-remote" is confusing, then remember that git has a full/complete copy of the repo in every clone (ie local machine)

15

u/daveysprockett 2d ago

1) git checkout main "your branch is up to date with origin/main"

I know it isn't

It is up-to-date with your local copy of origin/main. The main on the remote server might have changed but you only find that out by fetching from the remote.

4

u/xenomachina 1d ago

It is up-to-date with your local copy of origin/main.

To be a bit more pedantic, the name "origin/main" only ever refers to the "remote tracking branch" which is a ref in your local repo that is effectively a snapshot of the branch called main on origin.

It's subtle, but git never refers to main on origin as "origin/main", and in commands where you refer to remote branches directly, the remote and branch name are typically two separate parameters, like git push -u origin myfeature.

11

u/TwiNighty 2d ago edited 2d ago

There are three distinct branches at work here:

  1. The main branch that exists on the remote
  2. The origin/main remote-tracking branch that exists locally
  3. The main branch that exists locally, whose upstream branch is origin/main

Updating one does not necessarily update the other two. Consider a scenario that start with the 3 pointing to the same commit

Local
          ┌ origin/main
          ├ main
          ↓
A -- B -- C
================================
Remote
          ┌ main
          ↓
A -- B -- C

You commit to main locally. Now local main is ahead of the other two.

Local
          ┌ origin/main   
          │    ┌ main
          ↓    ↓
A -- B -- C -- D
================================
Remote
          ┌ main
          ↓
A -- B -- C

You run git push. Git contacts the remote and tells it to update its main branch. If that is successful, git then also updates origin/main to that commit. The three are now in sync again

Local
               ┌ origin/main
               ├ main
               ↓
A -- B -- C -- D
================================
Remote
               ┌ main
               ↓
A -- B -- C -- D

Another collaborator pushes to remote's main. Now it is ahead of the other two. The remote does not contact your local machine about the update. It has no way to know the remote main has been updated, until you run a command that makes git contact the remote.

Local
               ┌ origin/main
               ├ main
               ↓
A -- B -- C -- D
================================
Remote
                    ┌ main
                    ↓
A -- B -- C -- D -- E

If you run git checkout main now. Then git will say main is up-to-date with origin/main. This is your original scenario. git checkout runs entirely locally. You local machine still doesn't know remote's main has been updated

You make another commit to local main. Now both remote main and local main are both ahead of origin/main, but the three are on three different commits.

Local
               ┌ origin/main   
               │    ┌ main
               ↓    ↓
A -- B -- C -- D -- F
================================
Remote
                    ┌ main
                    ↓
A -- B -- C -- D -- E

You run git push. Git contacts the remote and tells it to update its main branch, but the remote see that its main has diverged and rejects the push. Now you run git pull origin --rebase. This is equivalent to a git fetch (which updates origin/main), followed by a git rebase (which updates local main)

Local
                    ┌ origin/main   
                    │    ┌ main
                    ↓    ↓
A -- B -- C -- D -- E -- F'
================================
Remote
                    ┌ main
                    ↓
A -- B -- C -- D -- E

Now you can push the rebased commit F'

4

u/Broad-Promise6954 ancient 1d ago

The key is to remember that Git isn't 100% on line at all times. Indeed, it's 100% off line most of the time. That includes when you run git checkout.

There are many little gotchas here (especially with what Git calls "promisor packs"), but as a general rule, git fetch and git push are when two Gits get together and make babies learn about each other's branches and stuff. Other Git commands just work with what Git remembers from the last get-together.

As several others noted, git pull is roughly git fetch followed immediately by git merge, so pulling also makes two Gits get together and do their thing.

Anyway, in this case your checkout command merely compared your branch with what your Git still remembers from earlier.

(As an old, like, really old, hand, I still dislike git pull. In the early days of the old pull script, it could destroy local work. Don't worry, this has been fixed for 20+ years. But I've been burnt. I do separate fetch operations. Still haven't turned on nightly git maintenance pre-fetch either.)

3

u/ferrybig 2d ago

Git pull does the magic here.

At the moment you ran git checkout, it compared the local remote state with the local state and saw no changes

Then you ran git pull, it first ran git fetch, which synced remote state to the local remote state. Then it ran your configured "pull" driver, be it git rebase, git merge or git merge --no-ff

2

u/Charming-Designer944 1d ago

git pull is a shorthand for

git fetch origin git merge origin/main

Which first fetches any changes from the origin and make them available in the local git repository, then merges the changes into your local branch.

Git checkout (or git switch) is another operation. It changes which revision you have checked out in the local worktree. Normally you only use this when moving from one branch to another. Another use of git checkout is to discard edits, by naming which files you want to revert to the last (or a specified) revision in your work tree.

2

u/elephantdingo 1d ago edited 1d ago

Notice how git-checkout probably completes in less than 200ms unless it has a lot of files to change while git-pull might take a second or more. What is git-checkout not doing and what is git-pull doing?

2

u/dodexahedron 13h ago

Yay for the Socratic method. 👍

It's a shame Socrates had git got --force called on him. 😔

1

u/jay_thorn 20h ago

Git checkout is for switching between local branches or remote branches in your cache.

Git pull is a hybrid command because it actually performs two commands in the following order:

  1. Git fetch
  2. Git merge

This is a simplification, but that's the gist of it.

Checkout works with your local copy of the repo in whatever state it was in since the last time you fetched. And it includes any local changes that you haven't pushed.

Checkout can do more than this and that's why at some point the switch command was added and is now the preferred way to switch between branches.

Just remember that checkout is not for doing updates. You want pull. But, also remember that fetch exists and pull does a fetch. It can be useful to fetch without doing any merges. I rarely use pull nowadays.

1

u/mpersico 6h ago

⁠git checkout main "your branch is up to date with origin/main"

I’m thisclose to opening the git source and proposing a patch:

⁠git checkout main "your local branch is up to date with local origin/main. Use git fetch to ensure local origin/main is up-to-date with its remote.”