Szél Péter

If you're contributing to an open source project on Github you certanly have a fork which you have to keep up-to-date with the upstream repository. Synchronizing the fork is not complex, just a couple of manual steps, but if you have many branches, it can be quite boring and time consuming to do it by hand all the time. This is why I wrote a small Powershell script which does exactly what I needed here.

Before you start

One of the most useful advice one could give regarding Git is to work on your own local branch instead of the upstream tracking branch. E.g. If you are working on a new e-mail sending feature, instead of working directly on master branch, before you start you should create a new branch called something like feature-email, its actually extremely easy to do:

git checkout -b feature-email

Remember that branches in Git are really cheap, they are essentially just references to commit objects, so unlike in SVN or Perforce, where you had to copy your whole working directory to create a branch, Git only creates a 41 byte sized file on your system and that's all!

One main advantage for this is that synchronizing your Github fork can be fully automated because your upstream tracking branches won't contain local changes thus there won't be any conflict on merging.

The steps we need to automate for every branch

Let's create a list of the necessary steps which have to be run for every branch in your fork:

  1. If don't have a local tracking branch for the upstream one:
    1. Create a new branch and set up as a remote tracking branch
  2. Checkout the tracking branch
  3. Pull all changes from ustream branch to local
  4. Push all changes to your Github fork

Quite a list and I don't like doing all these steps by hand so let's create a Powershell script!

Let there be Powershell

Here is the full copy of the script:

$current_branch = git branch |? { $_[0] -eq '*' } | % { $_.trim("* ")};
git featch -p upstream
git branch -a |
? { $_ -match "remotes/upstream*" } | 
% { $_.split("/")[2] } | 
% { (((git checkout $_) -and (git pull -r upstream $_)) 
	-or (g checkout -b $_ -t upstream/$_)) 
    -and (git push origin $_) }
git checkout $current_branch

I know it's quite ugly, let's go line-by-line to make it more clear:

  • In the first line I'm saving the currenly active branch so that I can set it back in the last line. This is useful since chanses are that we're currently working on a local feature branch an we'd like to be on that branch after the script ran.
  • In the second line I'm updating my references from upstream.
  • In the third line I'm listing all branches and piping the result into the next command.
  • In the fourth line I'm filtering the branches and only keeping the upstream ones.
  • In the fifth line I'm stripping out the actual branch names by removeing hte upstream/ part.
  • In the 6th line we're checking out this local branch and pulling all changes from the upstream remote repository.
  • If this fails probably the local branch doesn't exist so let's create it (7th line).
  • If everything goes well we can push the changes to out fork repository (8th line).

Possible improvements

What if you don't need all branches available on upstream?

I understand that there might be some cases when you don't need all branches from the upstream repository, and you don't want to waste time updating these branches. E.g. you are not contributing to the documentation thus don't need the gh-pages branch. You can create an ignore list for these cases:

$ignore = ('gh-pages', 'another-useless-branch')

... // put this line after we have the branch names (after the fifth line)
? { !($ignore -contains $_) }  | 
...

Or we could have a sort of whitelist of branch names to include:

$include = ('master', 'stable')

... // put this line after we have the branch names (after the fifth line)
? { $include -contains $_ }  | 
...

I think you got this :)

Deleted upstream branch

If you are more familiar with Git you've probably noticed that I haven't covered the case when an upstream branch is no more there, i.e. it was deleted. In this case you should delete from your fork repository as well by hand:

git push origin :deleted-branch

I hope you found my blogpost helpful, if so please help me by sharing it! :) You can easily click one of the buttons beneath ↓