In this post I describe my experience of making my first contribution to a project on SourceForge, using the Mercurial version control system. It's sometimes easy to forget there's a world outside of Git and GitHub, and it was interesting dipping a toe in!

The motivation: improving .NET Standard support

At Elevate Direct, we use a library called Sasa quite extensively in our .NET projects. It contains a variety of utility types, but we use it primarily for the implementation of functional concepts like Option<>. The library has been around for a long time (since 2013), and at the start of 2019, Sandro Magi added .NET Standard support.

He did a great job supporting a wide range of platforms by targeting .NET Standard 1.3. Unfortunately, if you're using a platform that supports netstandard2.x, then referencing a netstandard1.x package can result in a huge dependency tree being dumped in your folder output.

To work around this issue for consumers, library authors can add an additional netstandard2.x target to their library. Unfortunately that can cause issues for people targeting net461. If you follow the official advise on cross-platform targeting to its conclusion, eventually you end up targeting at least four frameworks:

  • netstandard1.x for maximum compatibility
  • netstandard2.x to avoid large dependency trees in .NET Core 2.x etc
  • net461 to avoid issues caused by net461's "fake" .NET Standard 2.0 support
  • net472 to "override" the previous net461 target, as .NET Framework 4.7.2 includes real .NET Standard 2.0 support.

If you already have a library targeting netstandard1.3, then adding the extra targets is pretty easy: just change this line in your .csproj project file:

<TargetFramework>netstandard1.3</TargetFramework>

to this (note the s in TargetFrameworks)

<TargetFrameworks>netstandard1.3;netstandard2.0;net461;net472</TargetFrameworks>

Given all the hard work had been done to convert Sasa to use .NET Standard 1.3, I thought I'd help out by making the update, and sending a PR. I envisioned a 20 minutes piece of work, tops.

Then I realised the project was hosted on SourceForge, and uses Mercurial for version control.

Forking a project on SourceForge

I've pretty much gone my whole life barely using SourceForge. I'm sure I've downloaded a few things here and there, but I've certainly never hosted, contributed, or even really looked at any projects on there.

Unlike GitHub, where the code is front-and-center when you view a repository, it feels like you have to go hunting a bit further on SourceForge. The project landing page is much more focused on downloads and project activity (and Ads 🤮) but overall the process of submitting a patch should be quite familiar in principal if you're used to GitHub.

Start by clicking the Code tab in the project, which for the Sasa project takes you to https://sourceforge.net/p/sasa/code/. This is similar to the default GitHub view, and is where you can browse the code, view branches and commits, and fork the project.

The code page for Sasa on SourceForge

Just as with Git and GitHub, if you want to send a patch to a project on SourceForge, you first need to fork the code (i.e. create a "personal" copy of the project). After clicking Fork from the code page, you'll be taken to your fork of the project. After a few moments, the clone will be complete and you'll see a copy of the code in your account:

The upstream copy after forking

I assume the u/ prefix is to indicate that this is a clone of an existing project, but I'm not sure. It also mentions that the project is a clone of Sasa in the left sidebar.

Once the project is forked, it's time to download the code and make the fix. Sasa uses Mercurial source code management (rather than Git), so you need to install and use the hg tool. I've used SVN before, but never Mercurial so was interesting to give it a try.

The nerd in me loves that their tool is hg, the chemical symbol for mercury). But I'm not sure about having a version control system who's name means "subject to sudden or unpredictable changes of mood"!

Installing Mercurial

After a brief read of the Mercurial about page, it actually looks pretty interesting. It sounds very much like Git in a lot of ways, and was started about the same time. It's a distributed version control system, and branching and merging are cheap, just like Git. It does have some interesting extra features, but frankly it seems like Git has already won, so I probably won't be looking into any more than the basics 😉

You can download Mercurial from their website. As it's mostly written in Python, cross-platform installers are available for loads of different operating systems (compare that to Git, where Windows definitely used to feel second-class!) Version 4.9.1 was the latest at the time I downloaded the Inno Setup installer - x64 Windows, though I see version 5.0 is out now.

Once downloaded and installed, you can test everything is working correctly by typing hg at the command line:

> hg
Mercurial Distributed SCM

basic commands:

 add           add the specified files on the next commit
 annotate      show changeset information by line for each file
 clone         make a copy of an existing repository
 commit        commit the specified files or all outstanding changes
 diff          diff repository (or selected files)
 export        dump the header and diffs for one or more changesets
 forget        forget the specified files on the next commit
 init          create a new repository in the given directory
 log           show revision history of entire repository or files
 merge         merge another revision into working directory
 pull          pull changes from the specified source
 push          push changes to the specified destination
 remove        remove the specified files on the next commit
 serve         start stand-alone webserver
 status        show changed files in the working directory
 summary       summarize working directory state
 update        update working directory (or switch revisions)

(use 'hg help' for the full list of commands or 'hg -v' for details)

Glancing through those commands as a Git user should look vaguely familiar - push, pull, merge, status - they're definitely similar in many ways. Just as with Git, you need to setup your username locally. create a new file at %USERPROFILE%/mercurial.ini with the format shown below and add your username and email:

[ui]
username = Andrew Lock <[email protected]>

At this point, Mercurial is installed and ready to go, but we don't have any code on our machine yet.

Cloning the repository and committing a change

Cloning the repository creates a copy of it on your local machine, including all the branches and history, just like with Git. The code page in SourceForge shows the command you need to run. In my case, it was

hg clone ssh://[email protected].code.sf.net/u/andrewlock/sasa u-andrewlock-sasa

This command clones the repository into a folder called u-andrewlock-sasa. You'll need to confirm the authenticity of the SourceForge host, and enter your password:

> hg clone ssh://[email protected].code.sf.net/u/andrewlock/sasa u-andrewlock-sasa
The authenticity of host 'hg.code.sf.net (216.105.38.18)' can't be established.
ECDSA key fingerprint is SHA256:FeVkoYYBjuQzb5QVAgm3BkmeN5TTgL2qfmqz9tCPRL4.
Are you sure you want to continue connecting (yes/no)?
Please type 'yes' or 'no':
Password:
remote: Warning: Permanently added 'hg.code.sf.net,216.105.38.18' (ECDSA) to the list of known hosts.
requesting all changes
adding changesets
adding manifests
adding file changes
added 2168 changesets with 7095 changes to 956 files (+8 heads)
new changesets 69e92c5f6c58:86c9bcbc342c
updating to branch default
223 files updated, 0 files merged, 0 files removed, 0 files unresolved

At this point you'll be checked out on the default branch in the working directory. Rather than mess with branches, I decided to commit straight to the default branch, as I was working in a clone of the real project anyway.

I edited each of the .csproj project files and replaced

<TargetFramework>netstandard1.3<TargetFramework>

with

<TargetFrameworks>netstandard1.3;netstandard2.0;net461;net472</TargetFrameworks>

Once I confirmed everything was building correctly, I was ready to commit the changes. You can check the current status of the working directory by running hg status. This shows the files modified, added, and deleted.

> hg status
M Sasa.Binary\Sasa.Binary.csproj
M Sasa.Collections\Sasa.Collections.csproj
M Sasa.Concurrency\Sasa.Concurrency.csproj
M Sasa.Linq.Expressions\Sasa.Linq.Expressions.csproj
M Sasa.Linq\Sasa.Linq.csproj
M Sasa.Mime\Sasa.Mime.csproj
M Sasa.Net\Sasa.Net.csproj
M Sasa.Numerics\Sasa.Numerics.csproj
M Sasa.Parsing\Sasa.Parsing.csproj
M Sasa.Reactive\Sasa.Reactive.csproj
M Sasa.Web\Sasa.Web.csproj
M Sasa\Sasa.csproj

To commit the changes, use the command hg commit, and provide a message.

hg commit -m "added additional target frameworks"

Note that unlike Git, there's no "staging" area or "index". You just commit the files that have changed. Even though I use the staging area quite a lot, that definitely seems like a plus for users who are new to source control.

At this point, the changes are committed locally, but not yet pushed to the server. You can push using the command hg push:

> hg push
pushing to ssh://[email protected].code.sf.net/u/andrewlock/sasa
Password:
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 12 changes to 12 files
remote: <Repository /hg/u/andrewlock/sasa> refresh queued.

Checking on SourceForge, you should be able to see your new commit alongside the changed files:

SourceForge showing the new commit

Now that the code is on SourceForge, you can create a "merge request", to merge the code back into the original project.

Creating a Merge Request

Merge Requests are the SourceForge equivalent of GitHub's Pull Requests (PRs). It's a notification sent to the owner of the project that you have changes for them to review and merge.

Choose Request Merge from the left hand sidebar of your clone. This takes you to the Request merge page, where you can enter a title, chose the source and destination branches, and enter a description. This should be very familiar if you're used to GitHub, though the description text isn't markdown unfortunately.

Creating a merge request

After creating the merge request, the owner of the repository will hopefully receive a notification, and they can review (and hopefully merge) your code! You can track the merge request in the original project's repository (i.e. in Sasa's repository in my case).

It took a while (thanks to a lack of notification from SourceForge it seems), but my contribution was merged! Success 🙂

Trying out both SourceForge and Mercurial for the first time was interesting. Mercurial was super easy to use, and looks like it would generally be easier to get into than Git for newcomers. And I didn't even get to the really interesting parts, like preserving history on file moves or the built in web-server(!). Unfortunately, while Mercurial is in use at some very notable places (e.g. Facebook) and notable projects, it feels like Git has probably won the hearts-and-minds.

And I think part of that has to be thanks to GitHub. It's become somewhat of the de-facto location for open source projects. And after using SourceForge, albeit briefly, I'm not surprised. SourceForge feels like a website from 10 years ago. It's not especially welcoming to newcomers, and was hard to get my head around. I'm just glad the fact the fork/merge request paradigm mirror's GitHub's terminology. If it didn't, I doubt I would have ever figured it out before I got frustrated and gave up.

Still, it was worth it in the end. If you haven't already, check out the Sasa library on NuGet, it has a whole variety of useful utility implementations.

Summary

In this post I described the process I went through of submitting a change to a project hosted on SourceForge, using the Mercurial source control system. I described the process of forking a repository, installing Mercurial, cloning a repository, and pushing your changes. Finally, I described how to create a merge request.