Git remote, fetch, pull and rebase

Developers are often asked to rebase their git branch. There is documentation that describes a lot, but it is rather complicated for people new to git.

GitHub’s documentation describes interactive rebase: About Git rebase - GitHub Docs

However, something that commonly goes wrong is that people rebase on the wrong branch and end up with incorrect histories.

First of all, let’s talk about the environment. You should understand that git has remotes. By default when you clone a repository it creates a remote named origin:

$ git clone https://github.com/theforeman/foreman
Cloning into 'foreman'...
remote: Enumerating objects: 155898, done.
remote: Counting objects: 100% (416/416), done.
remote: Compressing objects: 100% (252/252), done.
remote: Total 155898 (delta 174), reused 292 (delta 145), pack-reused 155482
Receiving objects: 100% (155898/155898), 135.10 MiB | 23.52 MiB/s, done.
Resolving deltas: 100% (104333/104333), done.
$ cd foreman
$ git remote -v
origin	https://github.com/theforeman/foreman (fetch)
origin	git@github.com:theforeman/foreman (push)

You can see I have a different URL to push and pull. That’s an irrelevant detail now, but Git - git-config Documentation exists in case you’re interested.

For now the important part is: there’s a remote named origin that points to GitHub’s theforeman/foreman.

Then you fork it, and add it as a new remote:

$ git remote add ekohl https://github.com/ekohl/foreman
$ git remote -v
ekohl	https://github.com/ekohl/foreman (fetch)
ekohl	git@github.com:ekohl/foreman (push)
origin	https://github.com/theforeman/foreman (fetch)
origin	git@github.com:theforeman/foreman (push)

We can now see there’s a remote named ekohl which points to my fork.

While not needed, it’s also possible to rename origin to something else:

$ git remote rename origin upstream
Renaming remote references: 100% (44/44), done.
$ git remote --verbose
ekohl	https://github.com/ekohl/foreman (fetch)
ekohl	git@github.com:ekohl/foreman (push)
upstream	https://github.com/theforeman/foreman (fetch)
upstream	git@github.com:theforeman/foreman (push)
$ git status
On branch develop
Your branch is up to date with 'upstream/develop'.

nothing to commit, working tree clean

Note how the branch tracking also changed to upstream/develop. This reference is important. To update the references from remote, git fetch is used.

$ git fetch
...
$ git status
On branch develop
Your branch is behind 'upstream/develop' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

You can see our branch is now behind upstream/develop. If we don’t pull often enough, we fall behind. Now if you work on a branch and rebase on top of develop, you will rebase on the version that’s behind.

One solution to this is to update your develop branch before you rebase.

$ git checkout develop
Switched to branch 'develop'
Your branch is behind 'upstream/develop' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ git pull
Updating f2b840906..9b31c9d6b
Fast-forward
 webpack/assets/javascripts/compute_resource/ovirt.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)
$ git status
On branch develop
Your branch is up to date with 'upstream/develop'.

nothing to commit, working tree clean
$ git checkout - # go back to the previous branch
Switched to branch 'my-branch'
$ git rebase develop
Successfully rebased and updated refs/heads/my-branch.

Another approach is to first fetch and then rebase on the remote develop branch.

$ git fetch upstream
...
$ git rebase upstream/develop 
Successfully rebased and updated refs/heads/my-branch.

Note in my examples I used upstream as the remote, but if your remote is named origin that should be used. Check git remote --verbose to know what to use.

Earlier I mentioned branch tracking. With git push --set-upstream you can set up the branch tracking during push. git branch --set-upstream-to=<upstream> can be used to change existing tracking.

4 Likes