As you imply in your question, the most confusing part of this is understanding the impact on your local repo compared to the remote repo, and partly also the impact on everyone else working with the remote repo.
First off, as the other answers indicate, git pull
is literally the same as git fetch
+ git merge
. Both of these commands are from the perspective of your local repo pulling / fetching + merging from the remote to the local repo. This occurs from the same branch you have checked out locally on the remote to that branch locally. I'll discuss those first, then squashing and rebasing.
Git Pull / Git Fetch + Git Merge
Let's assume you have master
checked out locally, and you will then be fetching master
from the remote (unless you use some more advanced syntax or flaggs on these commands). Let's call the remote branch as origin/master
.
Fetching from origin/master
means you want to get new commits from origin/master
and update your local tracking reference for origin/master
to match where master
actually is on the remote. If master
has diverged between local and remote, then there will be commits branching out from your master branch until they reach the current location of the origin/master
reference. Your recent commits that haven't been pushed will be on a local master
branch that may be different.
Then when the merge happens, you are basically asking to merge origin/master
into your local master
branch. If you do a normal merge command, that will cause a new commit to your local master
branch that pulls in all of the recent changes from the origin/master
into your local master
branch. If there are conflicts you will resolve them first before committing. However, if you haven't committed anything locally, then the merge can just fast-forward to the new origin/master
location without needing to do anything else (your master
will just jump forward to match origin/master
).
At this point, if you push before anybody else pushes anything more to the remote, then your new merge commit will be pushed with both your separate commits (they will look like a separate, unnamed branch), and the original remote commits, plus a new merge commit at the end.
What about Squashing?
You did ask about this, but if you merge with --squash then it will add the merge commit, but remove the other local commits, so that there is no extra unnamed branch in the history. Some people prefer this as it's "more clean", while others prefer to see all the history (i.e. that the code diverged and what exactly was brought together in the merge commit).
Rebasing
When you rebase, you are basically taking all of your local commits, and replaying them onto the remote master branch, so they don't show up as a separate branch. This keeps the local history intact, without showing that the code diverged, which, again, some people prefer and consider it to be more "clean". This is independent of whether any of that history is important, etc.
What if someone else committed since I fetched?
If someone else ninja committed (well, just committed...) since you fetched, then your push will fail. You can use --force
, but then you would completely overwrite their commit, so that is generally frowned upon, as it creates all sorts of problems for other people. In this case, if everyone agreed that his commit should go first, then you would need to go through the process again (if there are no conflicts, then this is quick and easy), but in general, the team needs to have a policy on this type of thing, and often pull requests and reviews are used to manage pushes in a more orderly fashion, once the team gets big enough.
Another way to think about this
It might be easy to understand what's happening by thinking about your local master branch as a separate branch from the remote - that's basically what's going on from Git's perspective.
In fact, in my mind you are always better working in your own separate branch until you are ready to merge something to a remote branch. Git makes it so easy, quick, and painless to have a separate branch, that's what I do.