Skip to main content

Contributing

This page is the repository's CONTRIBUTING.md; the rest of this guide explains each step in more depth: Building, Code style, Testing, Dependencies (lock files and checksums after a version change) and Releases (commit messages and branch names).

Thank you for helping! This document explains how to propose changes.

Licensing of contributions​

njMule is licensed under the GNU General Public License v3.0 or later. By contributing you agree that your contribution is licensed under the same terms (inbound = outbound); no copyright assignment or CLA is required.

Every commit must be signed off according to the Developer Certificate of Origin 1.1, certifying that you wrote the change or have the right to submit it under the project license:

git commit -s -m "feat(kad): add routing zone splitting"

which adds Signed-off-by: Your Name <you@example.org> to the message. Use your real name.

Only add third-party code or assets whose license is compatible with GPL-3.0 (MIT, BSD, Apache-2.0, LGPL, GPL-2.0-or-later, …), keep their original notices, and record them in REUSE.toml and NOTICE.

Workflow​

  1. Open an issue first for anything larger than a small fix.
  2. Create a branch named <type>/<short-kebab-description> (e.g. fix/server-udp-min-length).
  3. Commit with Conventional Commits 1.0.0 messages: <type>[scope][!]: <description> — types feat, fix, build, ci, docs, perf, refactor, style, test, chore, revert.
  4. Run ./gradlew build (formatting, Checkstyle, Error Prone, tests, coverage, SpotBugs, and the interface's type check, tests and Prettier check). A new or updated dependency also needs its lock files and checksums rewritten (see Dependencies).
  5. Open a pull request whose title is a Conventional Commits header; fill in the template. PRs are squash-merged.

Code style​

  • Google Java Style formatted by palantir-java-format (PALANTIR style: 4-space indent, 120 columns): run ./gradlew spotlessApply.
  • Always qualify instance fields with this., and call the object's own methods without it (both enforced by Checkstyle).
  • LF line endings, UTF-8, the SPDX header from config/license/header-java.txt on every Java file.
  • The interface (njmule-ui) is formatted with Prettier (npm run format there), the desktop window's Rust code (njmule-desktop/src-tauri) with cargo fmt.
  • Comments explain why, not what; keep them short.
  • Optional hook: git config core.hooksPath .githooks.

Tests​

New behaviour needs tests (JUnit Jupiter, AssertJ; jqwik for codecs and parsers; Vitest for the interface; cargo test for the desktop window). Never commit personal data: copies of a real eMule profile, captures or screenshots belong in the git-ignored fixtures-private/ folder.

Tests follow the given/when/then form of Gherkin inside plain JUnit classes (no Cucumber):

  • each test method has a meaningful name that states the expected behaviour, and its body only calls step methods: the given...() steps, if any, then one when...(), then one or more then...();
  • a scenario has one action: a test of several independent cases becomes several tests (or a parameterized one), and the earlier stages of a longer flow become given...() steps;
  • the steps are private methods of the test class, named as sentences in lowerCamelCase and taking parameters when a step is reused with other values; the state they share lives in fields;
  • a when...() step that can fail keeps the exception in a field, and thenExceptionOccurred(...) or thenNoExceptionOccurred() checks it.
class PathSanitizerTest {

private String networkName;
private String safeName;

@Test
void shouldEscapeAReservedNameHiddenByTheLengthCut() {
givenANameFromTheNetwork("CON" + " ".repeat(300) + "x");

whenTheNameIsMadeSafe();

thenTheSafeNameIs("_CON");
thenTheSafeNameFitsTheLengthLimit();
}

private void givenANameFromTheNetwork(String name) {
this.networkName = name;
}

private void whenTheNameIsMadeSafe() {
this.safeName = PathSanitizer.fileName(this.networkName);
}

private void thenTheSafeNameIs(String expected) {
assertThat(this.safeName).isEqualTo(expected);
}

private void thenTheSafeNameFitsTheLengthLimit() {
assertThat(this.safeName.getBytes(StandardCharsets.UTF_8)).hasSizeLessThanOrEqualTo(200);
}
}

How main is protected​

Since 0.1.0 every change reaches main through a pull request; nobody commits to main directly. A ruleset on GitHub protects it:

  • a change needs a pull request, and the CI result check of the Build workflow (the job that sums up all the others) must be green;
  • pull requests are squash-merged only, keeping a linear history; force pushes and deleting main are refused, and nobody bypasses the rules;
  • no approval is required, the maintainer being the only reviewer.

The squash commit takes the pull request's title, a Conventional Commits header, and the messages of its commits with their Signed-off-by lines; the merged branch is deleted. So:

  1. Start a branch <type>/<description> from the latest main (Releases names the types).
  2. Make ./gradlew build pass locally; add a line to [Unreleased] in CHANGELOG.md for a change users will notice, and update its page of this documentation.
  3. Open the pull request with a Conventional Commits title and the template below; it is merged once its checks pass.

Dependabot's pull requests are completed on their own branch, with the lock files and checksums rewritten (Dependencies), and squash-merged like the others.

The pull request template​

Every pull request answers these points:

Summary​

Checklist​

  • ./gradlew build passes locally
  • Tests added or updated
  • Commits follow Conventional Commits and are signed off (git commit -s)
  • New files carry the SPDX header (or are listed in REUSE.toml)
  • CHANGELOG.md updated (user-visible changes)
  • No personal data (eMule profiles, user hashes, IPs, captures)

Security​

Interoperability​

Code of conduct​

njMule adopts the Contributor Covenant, version 2.1 as its code of conduct. In short:

  • Be respectful and welcoming; disagree on ideas, never attack people.
  • No harassment, discrimination, sexualised language or imagery, trolling or doxxing.
  • Respect privacy: do not publish other people's personal data, including data from their file-sharing clients (user hashes, IP addresses, file lists).
  • Assume good faith and help newcomers.

The rules apply to every project space (issues, pull requests, discussions) and to anyone representing the project.

Reporting​

Report unacceptable behaviour privately to the maintainer through GitHub (https://github.com/salvatore-coppola). Reports are handled confidentially; the maintainer may warn, temporarily ban or permanently ban participants who violate this code, following the enforcement guidelines of the Contributor Covenant 2.1.