How to Contribute
NB: This is a guide to contributing to many different projects in different languages. For most projects that link here, it should be fairly comprehensive, but make sure you check for a CONTRIBUTING.md in that project for anything that may contradict the general guidelines here.
See language-specific adjustments to these guidelines
behave
See our code of conduct for details of what that means.
reporting issues
Open anything on the forge you’re viewing the code on (most likely GitHub).
All contributions are welcome, we love improvements to docs and tests.
terminology
| generic | Haskell | Nix | Rust | versioned |
|---|---|---|---|---|
| repository | ✔ | |||
| project | Cabal project | flake | workspace | |
| package | Cabal package | derivation | crate | ✔ |
| component | Cabal stanza |
There is usually one project per repository, but sometimes a project may be split across multiple repositories. It’s unlikely that more than one project would be in the same repository.
In some languages, there is a notion of a “component”. Components all share a single version. In general, we put multiple components into separate packages – for example, in Haskell, a library and an executable each represent a component, but in order to allow them to be versioned independently, they’re separated. However, test suites for a library are separate components, but kept in the same package as the library1, because there’s no benefit to versioning them separately.
building
Especially if you are unfamiliar with the particular language ecosystem, there is a Nix build (both with and without a flake). If you are unfamiliar with Nix, Nix adjacent can help you get things working in the shortest time and least effort possible.
if you have nix installed
nix build will build and test the project fully.
nix develop will put you into an environment where the traditional build tooling works. If you also have direnv installed, then you should automatically be in that environment when you’re in a directory in this project.
development
environment
We recommend the following steps to make working in this repository as easy as possible.
direnv allow
This command ensures that any work you do within this repository is done within a consistent reproducible environment. That environment provides various debugging tools, etc. When you leave this directory, you will leave that environment behind, so it doesn’t impact anything else on your system.
git config --local include.path ../.config/git/config
This will apply our repository-specific Git configuration to git commands run against this repository. It’s lightweight (you should definitely look at it before applying this command) – it does things like telling git blame to ignore formatting-only commits.
making changes
embrace the graph
There is no rebasing here.. You branch where you branch and resolve conflicts in a merge commit.
all code is auto-formatted
Running nix fmt should keep your changes in line with the rest of the repository. CI will tell you if that hasn’t happened. We recommend ensuring that each commit is formatted, to keep reformatting noise out of other changes.
If you do need to bulk re-format (e.g., you have a long series of commits, and the work to modify them all, reformatting and resolving conflicts, would be onerous) then make a separate commit that only formats, then another small commit adding the previous SHA to "${repo_root}/.config/git/ignoreRevs".
be conscious of versioning
Breaking changes should be made in separate PRs from backward compatible changes as much as possible.
For example, if you are adding a new API that replaces an existing one, there should first be a PR that adds the new API alongside the old one, then a second PR that removes the old API. If there are name conflicts, defer them until the breaking change, making the work to adjust to the breaking change minimal. For example,
You have original code like:
Foo.MyAPI
call1
call2
then you want to have a new API that replaces call2 and replaces call1 with call3, so you add
Foo.MyAPI
call2'
call3
and mark call1 and call2 deprecated.
Then in the breaking change, you remove call1 and call2, then rename call2' to call2 and define call2' as a deprecated alias for call2. Then a later breaking change will remove call2'.
If the API is undergoing more significant changes, then rather than having them live in one place, you can put it in an adjacent module. Starting from the same original code, you add
Foo.MyAPI'
call2
call3
then in the breaking change, you remove Foo.MyAPI, rename Foo.MyAPI' to Foo.MyAPI, and make Foo.MyAPI' an alias to Foo.MyAPI with deprecated re-exports of everything it had. No future additions to Foo.MyAPI should be exposed via the Foo.MyAPI' alias.
If there are no conflicts in the changes, simply adding and removing, then there is no need for the third (future) PR. The initial two PRs will suffice.
See the README for more specific information on what kind of changes are considered “breaking”.
security fixes are an exception to semantic versioning
To help ensure security fixes are propagated quickly, they should
- ideally be handled as no more than a compatible (minor) change;
- be immediately backported to any major version chain where the latest release contains the security issue; and
- deprecate any releases that contain the security issue.
If it’s not possible to fix the security issue without breaking the API, it should still be released as a compatible change. This is an unfortunate situation, but it ensures that anyone who uses that functionality downstream faces breakage instead of insecure behavior.
documentation
correct is important, pretty is nice
Feel free to submit changes to documentation that include assumptions, etc. that may not be correct. That’s just more evidence that additional documentation is necessary. We’ll get to correct during code review.
However, while we might mention style issues in code review, fixing that is almost never a blocker for merging a change2.
The style is mostly adapted from, The Elements of Typographic Style, translated to low-fidelity tools (like Markdown). But you don’t need to be familiar with that to submit acceptable docs.
-
There’s actually a common problem that sometimes makes its difficult to keep some test suites in the same component, where you want a separately-versioned testing library, and so your test suites move into the testing library’s package instead of the tested library’s package. ↩
-
There are cases where formatting can block a merge. For example, if ASCII art isn’t in a code block, the rendering as re-flowed text could make it wrong or misleading. That would be a blocker. ↩