loading...
Cover image for A Fool-Proof Way to Keep Your Fork Caught Up in Git

A Fool-Proof Way to Keep Your Fork Caught Up in Git

jacobherrington profile image Jacob Herrington (he/him) Updated on ・4 min read

How do you keep a fork up to date with the main repository on GitHub?

It seems like quite a few people have asked me this over the last few months.

I'm not sure how many patterns there are for accomplishing this in git (I'd guess quite a few), but I have one that I use pretty much exclusively.

Let's identify why this is necessary, first. If you find an Open Source repository that you'd like to contribute to or keep a copy of, it's a common practice to fork that repository.

Forking a repository just means making a copy of it, presumably so that you can make changes to it without affecting the original repository. In many cases, that's because you don't have write access to the original repository.

On GitHub (where the term fork is commonly used) all you have to do is click the big fork button at the top of a repository. After a few seconds, you'll have your own copy of the original repository stored under your namespace.

If you intend to make changes to your fork, you'll most likely want to clone it to your local environment. For this article, I'll use my fork of the Solidus.io project (a project I help maintain on GitHub).

My fork is located at github.com/jacobherrington/solidus. To clone it to my local machine, I could run this git command:

git clone git@github.com:jacobherrington/solidus.git

Let's say I create this fork, clone it, then leave it alone for six months.

In six months, the original repository has changed substantially, and now my fork is outdated. The GitHub UI will give you an indication when this happens. That indication looks something like this:

This branch is 8 commits behind solidusio:master.

So let's get it caught up.

1. Create a new remote

We are going to use the git remote command to do that! A remote is pretty simple; you can think of it as a bookmark that points to a remote repository.

For example, if I run git remote -v (the -v flag stands for verbose) in my local copy of the Solidus fork that I created, I'll see the default remote called origin and where it points:

$ git remote -v
origin    git@github.com:jacobherrington/solidus.git (fetch)
origin    git@github.com:jacobherrington/solidus.git (push)

You can see that there is a fetch and a push remote. You can ignore those for now, focus on the URL-looking thing. That's the same address we gave to git when we cloned the fork.

We can use this remote to pull in new code or push our changes up. If I run git push, my code is going to be pushed up to this remote by default.

However, you can specify another address when you are pushing or pulling. That's what we need to do to catch up our fork.

The first step is to create a new remote:

$ git remote add upstream git@github.com:solidusio/solidus.git

This command adds a new remote named upstream (you can choose a different name, but that's the one I prefer), pointing to the original repository on GitHub. That is, the repository that I originally forked from.

2. Pull in the new changes

Now that I've created a remote pointing at the original repo, which I like to call the upstream repository, I can easily pull in changes from that repository and push them to my fork.

First, I make sure that I'm on the master branch locally and that I don't have any weird local changes. (Careful copy-pasters, this will delete any work you have locally!)

$ git checkout master && git clean -fd

Then, I'll pull the changes from the upstream repository:

$ git pull upstream master
remote: Enumerating objects: 148, done.
remote: Counting objects: 100% (148/148), done.
remote: Total 186 (delta 148), reused 148 (delta 148), pack-reused 38
Receiving objects: 100% (186/186), 40.44 KiB | 20.22 MiB/s, done.
Resolving deltas: 100% (148/148), completed with 125 local objects.
From github.com:solidusio/solidus
 * branch                master     -> FETCH_HEAD
 * [new branch]          master     -> upstream/master
Updating 29acc0d0b..20973340b
Fast-forward
... # some files that changed
 87 files changed, 180 insertions(+), 177 deletions(-)

In this case, you can see I've picked up about 180 lines worth of changes. To update my remote fork (the repository on GitHub at jacobherrington/solidus), I'll need to push these changes up!

3. Push the changes up to your remote fork

As long as my local master branch hasn't actually diverged, it's this easy:

$ git push

You'll get some console feedback that looks something like this:

Total 0 (delta 0), reused 0 (delta 0)
To github.com:jacobherrington/solidus.git
   29acc0d0b..20973340b  master -> master

And now your remote fork is caught up with the original repository!

This branch is even with solidusio:master.

That's it. 🤠

There's more...

I'm writing a lot of articles these days, I run a podcast, and I've started sending out a newsletter digest about all of the awesome stories I'm hearing.

You can also follow me on Twitter, where I make silly memes and talk about being a developer.

Discussion

pic
Editor guide
Collapse
kaushalgautam profile image
Kaushal Gautam

Hi Jacob! Brilliant article, I was dealing with the same thing today.
I have another problem tho. The repo I was contributing to did not have a lot of activity and I had to make many changes and many PRs (rule was one change per commit, one commit per PR). Now I made change 'ChA' and commited 'CmA'. Then I pushed and created a PR. But the problem I guess was that the PR was not accepted very fast (as would generally happen). I move on to next change, 'ChB', commit 'CmB' and push it. But now when I try to open a PR, I am shown the previous PR (which has not yet been accepted) with both commits 'CmA' and 'CmB'. How do I keep them separate?

Collapse
jacobherrington profile image
Jacob Herrington (he/him) Author

That sounds like you need to make each of those commits on different branches.

If you didn't make a new branch when you created 'CmB', then you might have accidentally added that commit to the same branch as 'CmA' which would put it in the same Pull Request.

Collapse
kaushalgautam profile image
Kaushal Gautam

Thank you for the reply, Jacob. Yes, these were all on the master branch. Do you suggest I make separate branches for each of those changes? How does that work for many changes?

Thread Thread
jacobherrington profile image
Jacob Herrington (he/him) Author

What I do, is create a branch for each new feature. I usually have quite a few commits in each PR, but they are all for the same feature on what most people refer to as a 'feature branch' (meaning a branch specifically for that feature).

So if I'm going to fix some documentation, I'd make a branch from master called documentation-fixes.

git checkout master
git checkout -b documentation-fixes
git commit -am "Some doc fixes!"

Then when I got to implement a feature, I'd make a new branch from master:

git checkout master
git checkout -b some-new-feature
git commit -am "Some feature!"
Thread Thread
kaushalgautam profile image
Kaushal Gautam

I understand now. Thanks for such detailed explanations! They really help a lot. And congratulations on the new job! :)

Collapse
glsolaria profile image
G.L Solaria

If I have local changes, I prefer to stash, change the tracking branch, pull, change the tracking branch back, re-apply stash, deal with any conflicts, push!!! It makes me feel comfy for some reason! I am scared of rebase.

Collapse
jacobherrington profile image
Jacob Herrington (he/him) Author

I used to be scared of rebase, but I got used to it and now it's one of my favorite git commands. It can be scary though.

Collapse
adam_cyclones profile image
Adam Crockett

A rebase a day keeps the doctor away.

Collapse
wrldwzrd89 profile image
Eric Ahnell

I didn't realize you could use remotes that way... but you are right - that makes keeping forks updated a LOT easier!