Contributor

Architecture

This document serves two purposes: it seeks to offer the reader a high-level grasp of how Forgejo operates, and it shows the reader where to find whatever sort of code. This is not a full explanation of how the code works; rather, it is intended to be concise.

It's encouraged to read this carefully, take a moment to let it sink in, find related code through your preferred symbol finder, and reach out when necessary.

Bird's-eye view

Forgejo serves as a Git server and frontend; users can pull and push to git repositories and view commits and their diff, Forgejo handles the authentication and executes actions based on the context of the request, such as sending a notification if the pushed commit was part of a pull request or getting a diff (and syntax-highlighting it) between two commits if the compare page is requested. Forgejo doesn’t touch the git repository itself; it leaves that to the git binary.

Forgejo also provides a software development environment that does not rely on or interact with Git for the most part, which means that Forgejo handles the logic and stores information in databases. The environment includes features such as user profiles, issue reporting, and packages.

Code map

models/

Files in this directory include type definitions for tables and helper functions that execute SQL queries to retrieve and update data from the database.

modules/

Examples of modules that reside in this directory are markdown rendering, caching, and logging.

services/

This directory contains advanced functions that require database and/or git code and implement a lot of non-database logic; it is a combination of the 'models' and 'modules' functions.

This category contains operations such as user deletion, Git repository mirroring, and mail.

web_src/

The sources used to generate static assets used by the web UI (such as JavaScript, CSS, and SVGs) resides in this directory.

routers/

Web and API routes are in separate files and don't share handlers with each other, but they do have common middleware. Handlers either retrieve data and prepare it for rendering or perform an action and report on its success.

templates/

This directory contains the HTML templates for the frontend.

These templates are server-side rendered and use the Go template syntax.

tests/

This directory contains integration testing code.

The files prefixed with api_ contain integration tests for API routes, while those without are mostly integration tests for web routes.

Integration tests ensure that the behavior of the entire program is correct, which usually means that they indirectly test the behavior of multiple functions and their logic together as one behavior.

Unit tests

Unit tests are implemented in <filename>_test.go for the corresponding <filename>.go code. Not all files will have unit tests.

They test the expected result and behavior of the function.

Code of Conduct and moderation team

Forgejo is committed to creating an inclusive community where everyone feels welcome and safe. The well-being of our members is important to us and if anyone feels unsafe or uncomfortable for any reason, we encourage you to reach out to the Moderation Team.

You can contact the Moderation Team via email at moderation@forgejo.org or via matrix at @forgejo-moderation:matrix.org. Your conversations will remain confidential and are only disclosed in accordance with our moderation process.

The Moderation Team is there to listen to your concerns and will do its best to find the best solutions, ensuring that everyone feels safe and respected in our community. Actions taken are documented in an anonymized way within the governance repository, ensuring transparency and accountability. The team is committed to addressing any behavior that goes against the Code of Conduct or the law.

Together, we can foster a positive and inclusive environment for all Forgejo community members.

Developer Certificate of Origin (DCO)

Contributions to Forgejo, in all the repositories in the Forgejo organization are accepted provided the author agrees to the following Developer Certificate of Origin (DCO).

By making a contribution to Forgejo, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the Free Software license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate Free Software
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same Free Software license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the Free Software license(s) involved.

Bugs, features and discussions

The Forgejo issue tracker is where bugs should be reported and features requested.

Dedicated repositories in the Forgejo organization cover areas such as:

Other discussions regarding all non technical aspects of Forgejo, happen in a dedicated issue tracker and in the matrix chatroom.

Security

The security team takes care of security vulnerabilities. It handles sensitive security-related issues reported to security@forgejo.org using encryption.

The security team also keeps the content of the security.txt file up to date.

The private GPG key for security@forgejo.org is shared among all members of the security team and not stored online.

Governance

The Forgejo domains are owned by the democratic non-profit dedicated to Free Software Codeberg e.V.. Forgejo is therefore ultimately under the control of Codeberg e.V. and its governance. However, although Codeberg e.V. is committed to use and host Forgejo, it is expected that Forgejo defines its own governance, in a way that is compatible with the Codeberg e.V. governance.

Forgejo Governance

Forgejo was bootstrapped in November 2022 and defines its governance in a dedicated repository.

Release management

See the release schedule.

Stable release support

Bug fixes and security fixes are backported to the latest stable release.

Long Term Support (LTS)

The first quarter release of the year is LTS. Critical bug fixes and security fixes are backported to the latest LTS release.

Experimental releases

Experimental releases are published daily in forgejo-experimental organization. They are built from the tip of the branch of each stable release. For instance:

Release numbering

The Forgejo release numbers are compliant with Semantic Versioning. They are followed by the Gitea release number with which they compatible. For instance:

The release candidates are built of the stable branch and published with the -test suffix:

Stable release management

Major and patch releases are published with the help of the release-manager.

LTS toolchain upgrades

The Forgejo toolchain relies on Go OCI images based on Alpine. Only the last two Alpine versions are supported and will be EOL before the Forgejo LTS. In order to receive security updates the toolchain needs to be upgraded to a supported Alpine and Go version because it goes EOL.

Automated experimental releases

The Forgejo development and stable branches are pushed daily to the forgejo-integration repository (latest workflow runs). It triggers the release build workflow (latest runs) which creates a new release for each updated branch, based on their latest commit:

Forgejo release mirror

The https://code.forgejo.org/forgejo/forgejo repository is a read-only mirror updated daily with the release assets and the branches from https://codeberg.org/forgejo/forgejo.

Forgejo Runner publication

The release is built on https://code.forgejo.org/forgejo-integration/runner, which is a mirror of https://code.forgejo.org/forgejo/runner.

The release is published by https://invisible.forgejo.org/forgejo/runner, which is a mirror of https://code.forgejo.org/forgejo-integration/runner. Its role is to copy and sign release artifacts.

Release signing keys management

A GPG master key with no expiration date is created and shared with members of the Owners team via encrypted email. A subkey with a one year expiration date is created and stored in the secrets repository (openpgp/20??-release-team.gpg), to be used by the release pipeline. The public master key is stored in the secrets repository and published where relevant (keys.openpgp.org for instance).

Securing the release token and cryptographic keys

For both the Forgejo Runner and Forgejo itself, copying and signing the release artifacts (container images and binaries) happen on a Forgejo instance not publicly accessible to safeguard the token that has write access to the Forgejo repository as well as the cryptographic key used to sign the releases.

Master key creation

Subkey creation and renewal

From the root of the secrets directory, assuming the master key for EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 is already imported in the keyring.

There are a lot of contradictory information regarding the management of subkeys, with zillions of ways of doing something that looks like it could work but creates situations that are close to impossible to figure out. Experimenting with the CLI, reading the gpg man page and using common sense is the best way to understand how it works. Reading the documentation or discussions on the net is highly confusing because it is loaded with 20 years of history, most of which is no longer relevant.

Here are a few notions that help understand how it works:

Create

mkdir /tmp/secrets && pushd /tmp/secrets
gopass list openpgp
gopass fscopy openpgp/20??-release-team.gpg 20??-release-team.gpg # for all years
docker run -v ${PWD}:/tmp/secrets -ti --rm data.forgejo.org/oci/debian:bookworm bash
apt update && apt install -y gnupg2
gpg --import 20*-release-team.gpg
gpg --expert --edit-key EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
  addkey
  key type: ECC (signature only)
  elliptic curve Curve 25519
  key validity: 18 months
gpg --list-key --with-subkey-fingerprints # set FINGERPRINT to the fingerprint of the new subkey
year=$(date +%Y)
gpg --export-secret-subkeys --armor "${FINGERPRINT}!" > $year-release-team.gpg
gpg --export --armor EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 > release-team.gpg.pub
gopass fscopy $year-release-team.gpg openpgp/$year-release-team.gpg
gopass fscopy release-team.gpg.pub openpgp/release-team.gpg.pub
popd && rm -r /tmp/secrets

Sanity check

mkdir /tmp/secrets && pushd /tmp/secrets
gopass fscopy openpgp/release-team.gpg.pub release-team.gpg.pub
year=$(date +%Y)
gopass fscopy openpgp/$year-release-team.gpg $year-release-team.gpg # year of the new subkey
docker run -e year=$year -v ${PWD}:/tmp/secrets -ti --rm data.forgejo.org/oci/debian:bookworm bash
apt update && apt install -y gnupg2
cd /tmp/secrets
export GNUPGHOME=/tmp/signrelease ; mkdir $GNUPGHOME ; chmod 700 $GNUPGHOME
gpg --import $year-release-team.gpg # year of the new subkey
find $GNUPGHOME/private-keys-v1.d # only has **one** file named after the keygrip
echo bar > /tmp/foo
gpg --detach-sig --output /tmp/foo.asc --sign /tmp/foo
export GNUPGHOME=/tmp/user ; mkdir $GNUPGHOME ; chmod 700 $GNUPGHOME
gpg --import release-team.gpg.pub
gpg --verify /tmp/foo.asc /tmp/foo
exit
popd && rm -r /tmp/secrets

Publish

mkdir /tmp/secrets && pushd /tmp/secrets
gopass fscopy release-team.gpg.pub release-team.gpg.pub
gpg --import release-team.gpg.pub
gpg --keyserver keys.openpgp.org --send-keys EB114F5E6C0DC2BCDD183550A4B61A2DC5923710
popd && rm -r /tmp/secrets

Update the GPG_PRIVATE_KEY secret in the runner and forgejo repositories dedicated to signing the releases with the new private sub-key:

Reminder

Users, organizations and repositories

Shared user: forgejo-cascading-pr

The forgejo-cascading-pr user opens pull requests on behalf of other repositories by way of the cascading-pr action. It is a regular user, not part of any team. It is only used for that purpose for security reasons.

Dedicated user: forgejo-backport-action

The forgejo-backport-action user opens backport pull requests on the forgejo repository. It is a member of the mergers team. The associated email is mailto:forgejo-backport-action@forgejo.org.

Shared user: release-team

The release-team user publishes and signs all releases. The associated email is mailto:release@forgejo.org.

The public GPG key used to sign the releases is EB114F5E6C0DC2BCDD183550A4B61A2DC5923710 Forgejo Releases <release@forgejo.org>

Shared user: forgejo-experimental-ci

The forgejo-experimental-ci user is dedicated to provide the application tokens used by the CI to build releases and publish them to https://codeberg.org/forgejo-experimental. It does not (and must not) have permission to publish releases at https://codeberg.org/forgejo.

Dedicated user: forgejo-renovate-action

The forgejo-renovate-action user opens renovate pull requests on the forgejo repository. It is a member of the mergers team. The associated email is mailto:forgejo-renovate-action@forgejo.org.

Integration and experimental organization

The https://codeberg.org/forgejo-integration organization is dedicated to integration testing. Its purpose is to ensure all artefacts can effectively be published and retrieved by the CI/CD pipelines.

The https://codeberg.org/forgejo-experimental organization is dedicated to publishing experimental Forgejo releases. They are copied from the https://codeberg.org/forgejo-integration organization.

The forgejo-experimental-ci user as well as all Forgejo contributors working on the CI/CD pipeline should be owners of both organizations.

Secrets

All Forgejo credentials are shared among the devops team in a private repository with encrypted content.

Get started

  1. Make sure you have a GPG Key, or create one
  2. Send someone else your public key and ask this person to add yourself as a recipient
# Commands for the other person
$ gpg --import public_key.asc
# The following command will open a prompt, with the available public keys.
# Choose the one you just added and all secrets will be re-encrypted with this new key.
$ gopass recipients add
  1. Install gopass

    :warning: When installing on Ubuntu or Debian you can either download the deb package, install manually or build from source or use our APT repository (github comment with more information).

  2. Clone this repo using gopass (the name and email are for git config)
$ gopass clone git@codeberg.org:forgejo/gopass.git
  1. Check the consistency of the gopass storage
$ gopass fsck

Get a secret

Show the whole secret file:

$ gopass show ovh.com/manager

Copy the password in the clipboard:

$ gopass show -c ovh.com/manager

Copy the user part of the secret in the clipboard:

$ gopass show -c ovh.com/manager user

Insert or edit a secret

$ gopass edit ovh.com/manager

In the editor, insert the password on the first line. You may then add lines with a key: value syntax (user: username for instance).

Debugging and manual git operations

The following command will show the location and status of the git repo (all git commands are available).

$ gopass git status

Web browser support

New and existing Forgejo web UI features that affect usability of Forgejo should rely on browser features and APIs available in both:

For example, forgejo/forgejo#8859 couldn't rely on the new closedBy property of HTMLDialogElement because it was re-implementing an existing significant feature (modal dialogs) and closedBy was not yet supported by Firefox ESR. So not implementing a compatible JS function would break the ability of users to close the dialog by clicking outside of it on a supported browser, which would have been considered a newly introduced bug in Forgejo v13.0.0.

The latest Firefox ESR version can be calculated differently:

New features that do not affect usability can rely on browser features up to Baseline of last year is acceptable.

Feature oldness and non-standard features

It's up to the contributor to work on additional support beyond the minimally required platforms as long as such compatibility doesn't introduce:

However we do not welcome additional code that only exists to maintain compatibility with browser engines that have been completely officially discontinued, such as:

Vendor prefixes should be used strictly when necessarily. When a property is supported without vendor prefixes on the minimally required platforms, vendor-prefixed properties should not be used.

code.forgejo.org

https://code.forgejo.org is a Forgejo instance running the latest stable version, starting with the release candidates. It is dedicated to hosting repositories dedicated to Forgejo development, among which:

Interface customization

This sections documents the Forgejo interface customization that requires an intimate knowledge of the underlying codebase. The user interface customization documented and supported for Forgejo admins to use is found in the corresponding administrator guide section.

Customizing Forgejo is typically done using the CustomPath folder - by default this is the custom folder from the working directory (WorkPath), but may be different if your build has set this differently. This is the central place to override configuration settings, templates, etc. You can check the CustomPath using forgejo help. You can also find the path on the Configuration tab in the Site Administration page. You can override the CustomPath by setting either the FORGEJO_CUSTOM environment variable or by using the --custom-path option on the forgejo binary. (The option will override the environment variable.)

If Forgejo is deployed from binary, all default paths will be relative to the Forgejo binary.

Application settings can be found in file CustomConf which is by default, $FORGEJO_CUSTOM/conf/app.ini but may be different if your build has set this differently.

If you are having difficulty with finding the CustomConf directory, you can identify the variable using forgejo help or in the logs. This variable can be overridden using the --config option on the forgejo binary.

[!note] Note Forgejo must perform a full restart to see configuration changes.

Serving custom public files

To make Forgejo serve custom public files (like pages and images), use the folder $FORGEJO_CUSTOM/public/ as the webroot. Symbolic links will be followed. At the moment, only the following files are served:

For example, a file image.png stored in $FORGEJO_CUSTOM/public/assets/, can be accessed with the url http://forgejo.example.com/assets/image.png.

To build a custom logo and/or favicon clone the Forgejo source repository, replace assets/logo.svg and/or assets/favicon.svg and run make generate-images.

assets/favicon.svg is used for favicon.png only, all others will be generated from logo.svg. SVG files will be optimized. All source images must also have square dimensions.

The above command will update below output files which you can then place in $FORGEJO_CUSTOM/public/assets/img on your server:

Customizing Forgejo pages and resources

Forgejo's executable contains all the resources required to run: templates, images, style-sheets and translations. Any of them can be overridden by placing a replacement in a matching path inside the custom directory. For example, to replace the default .gitignore provided for C++ repositories, we want to replace options/gitignore/C++. To do this, a replacement must be placed in $FORGEJO_CUSTOM/options/gitignore/C++.

Every single page of Forgejo can be changed. Dynamic content is generated using go templates, which can be modified by placing replacements below the $FORGEJO_CUSTOM/templates directory.

To obtain any embedded file (including templates), the forgejo embedded CLI can be used. Alternatively, they can be found in the templates directory of Forgejo source.

Be aware that any statement contained inside {{ and }} are Forgejo's template syntax and should not be touched without fully understanding these components.

Forgejo regularly makes backward incompatible changes to its own templates, which makes templates very likely to break when upgrading Forgejo.

Before deploying your changes to production or upgrading a modified Forgejo instance, we urge that you test your custom modifications in a testing environment first.

Warnings on registration and login pages

It is possible to add custom warnings to registration and login pages. For example, to warn about the scopes in which an instance with open registration should be used. Here's how they would look like:

Registration Login

Steps to add them

Customizing startpage / homepage

Copy home.tmpl for your version of Forgejo from templates to $FORGEJO_CUSTOM/templates. Edit as you wish. Restart the Forgejo instance to apply the changes.

If all you want is to add extra links to the top navigation bar or footer, or extra tabs to the repository view, you can put them in extra_links.tmpl (links added to the navbar), extra_links_footer.tmpl (links added to the right side of footer), and extra_tabs.tmpl inside your $FORGEJO_CUSTOM/templates/custom/ directory.

For instance, let's say you are in Germany and must add the famously legally-required "Impressum"/about page, listing who is responsible for the site's content: just place it under your "$FORGEJO_CUSTOM/public/assets/" directory (for instance $FORGEJO_CUSTOM/public/assets/impressum.html) and put a link to it in either $FORGEJO_CUSTOM/templates/custom/extra_links.tmpl or $FORGEJO_CUSTOM/templates/custom/extra_links_footer.tmpl.

To match the current style, the link should have the class name "item", and you can use {{AppSubUrl}} to get the base URL: <a class="item" href="{{AppSubUrl}}/assets/impressum.html">Impressum</a>

You can add new tabs in the same way, putting them in extra_tabs.tmpl. The exact HTML needed to match the style of other tabs is in the file templates/repo/header.tmpl.

Other additions to the page

Apart from extra_links.tmpl and extra_tabs.tmpl, there are other useful templates you can put in your $FORGEJO_CUSTOM/templates/custom/ directory:

Using Forgejo variables

It's possible to use various Forgejo variables in your custom templates.

First, temporarily enable development mode: in your app.ini change from RUN_MODE = prod to RUN_MODE = dev. Then add {{ $ | DumpVar }} to any of your templates, restart Forgejo and refresh that page; that will dump all available variables.

Find the data that you need, and use the corresponding variable; for example, if you need the name of the repository then you'd use {{.Repository.Name}}.

If you need to transform that data somehow, and aren't familiar with Go, an easy workaround is to add the data to the DOM and add a small JavaScript script block to manipulate the data.

Customizing Forgejo mails

The $FORGEJO_CUSTOM/templates/mail folder allows changing the body of every mail of Forgejo. Templates to override can be found in the templates/mail directory of Forgejo source. Override by making a copy of the file under $FORGEJO_CUSTOM/templates/mail using a full path structure matching source.

Any statement contained inside {{ and }} are Forgejo's template syntax and shouldn't be touched without fully understanding these components.

Customizing gitignores, labels, licenses, locales, and readmes.

Place custom files in corresponding sub-folder under custom/options.

[!note] Note The files should not have a file extension, e.g. Labels rather than Labels.txt.

gitignores

To add custom .gitignore, add a file with existing .gitignore rules in it to $FORGEJO_CUSTOM/options/gitignore

Labels

Starting with Forgejo 1.19, you can add a file that follows the YAML label format to $FORGEJO_CUSTOM/options/label:

labels:
  - name: 'foo/bar' # name of the label that will appear in the dropdown
    exclusive: true # whether to use the exclusive namespace for scoped labels. scoped delimiter is /
    color: aabbcc # hex colour coding
    description: Some label # long description of label intent

The legacy file format can still be used following the format below, however we strongly recommend using the newer YAML format instead.

#hex-color label name ; label description

Licenses

To add a custom license, add a file with the license text to $FORGEJO_CUSTOM/options/license

READMEs

To add a custom README, add a markdown formatted file (without an .md extension) to $FORGEJO_CUSTOM/options/readme

[!note] Note README templates support variable expansion. Currently there are {Name} (name of repository), {Description}, {CloneURL.SSH}, {CloneURL.HTTPS} and {OwnerName}

Reactions

To change reaction emoji's you can set allowed reactions at app.ini

[ui]
REACTIONS = +1, -1, laugh, confused, heart, hooray, eyes

A full list of supported emoji's is at emoji list

Customizing fonts

Fonts can be customized using CSS variables:

:root {
  --fonts-proportional: 'Proportional fonts here' !important;
  --fonts-monospace: 'Monospace fonts here' !important;
  --fonts-emoji: 'Emoji fonts here' !important;
}

Customizing the PWA (Progressive Web App)

Forgejo provides a Web application manifest, retrievable from the /manifest.json route. It is also included in every HTML response's <head> element.

By default, this manifest is configured to be very minimal, only setting the absolute required properties of name, shortname, start_url and two entries for icons.

The display property is omitted by default but can be set to "standalone" via the setting STANDALONE in the [pwa] section of app.ini.

If this is not enough for your usecase, Forgejo additionally looks for a manifest.json file in your custom public files to serve instead. (See Serving custom public files).

Dependency management

Forgejo relies on hundreds of Free Software components and they all need to be updated on a regular basis, with appropriate tooling and methods.

Releases

Software referenced by a release (even if such a release is the hash of a commit). They are listed in the dependency dashboard which is updated by renovate from the renovate.json configuration file.

Decision to upgrade

Development branch

An upgrade is justified if:

There is no need to upgrade if there is no indication that it is beneficial to Forgejo.

Stable branch

The dependency dashboard can be used to create a pull request for an upgrade on a stable branch. It is not done automatically.

An upgrade is justified if it follows the restrictions that apply to stable branches (not backporting features for instance).

Release notes

When an upgrade has a user facing impact on Forgejo, it should be included in the release notes.

See this pull request for an example.

Reviewing renovate pull requests

Pull requests are opened when an upgrade is available and the decision to merge (positive review) or not (request for change review) depends on what the upgrade offers. The history of past upgrades can be browsed by looking for PR with the same title (e.g. happy-dom upgrades).

Note that renovate will keep a few (see prConcurrentLimit in renovate.json) pull request open at any given time. If no decision is made, newer upgrades will accumulate in the backlog visible in the dashboard.

The release team looks after the pull requests, to the extent that they can be tested automatically. If manual testing is required (because there is no test coverage for the part of the code that would be impacted by an upgrade), a review will be requested from the people who have the required expertise to either improve the test coverage or come up with a manual test procedure to be repeated.

Tuning a software upgrade

There is no uniformity in how software is released and they call for different strategies to deal with upgrades:

Soft forks

Temporary

Development environment

This page lists a few options to set up a productive development environment for working on Forgejo.

VSCodium

VSCodium is an open source version of the Visual Studio Code IDE. The Go integration for Visual Studio Code works with VSCodium and is a viable tool to work on Forgejo.

First, run cp -r contrib/ide/vscode .vscode to create new directory .vscode with the contents of folder contrib/ide/vscode at the root of the repository. Then, open the project directory in VSCodium.

You can now use Ctrl+Shift+B to build the gitea executable and F5 to run it in debug mode.

Tests can be run by clicking on the run test or debug test button above their declaration.

Go code is formatted automatically when saved.

Emacs

GNU Emacs is an open source implementation of the Emacs text editor. The Emacs Go-mode, in combination with the Go language-server and a LSP client can be used to work on Forgejo's code base.

A prerequisite for working with the language server is installing gopls. After this, you'll want to install the necessary packages in Emacs using M-x package-install followed by go-mode and your preferred LSP client, so either lsp-mode or eglot.

After the installation, you'll need to activate the packages in your initialization file. Regardless of your choice of LSP client, you'll need to add:

(require 'go-mode)

If you would like a full list of the features of go-mode, have a look at this blogpost by it's creator.

Then, for lsp-mode, add:

(use-package lsp-mode
  :ensure
  :commands lsp
  :custom
  (add-hook 'lsp-mode-hook 'lsp-ui-mode) ;; Optional, see below
  (add-hook 'go-mode-hook #'lsp-deferred))

;; lsp-ui mode is optional. It shows inline hints.
(use-package lsp-ui
  :ensure
  :commands lsp-ui-mode
  :custom
  (lsp-ui-peek-always-show t)
  (lsp-ui-sideline-show-hover t)
  (lsp-ui-doc-enable nil))

;; Set up before-save hooks to format buffer and add/delete imports.
;; Make sure you don't have other gofmt/goimports hooks enabled.
(defun lsp-go-install-save-hooks ()
  (add-hook 'before-save-hook #'lsp-format-buffer t t)
  (add-hook 'before-save-hook #'lsp-organize-imports t t))
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

Or for eglot:

(require 'project)

(defun project-find-go-module (dir)
  (when-let ((root (locate-dominating-file dir "go.mod")))
    (cons 'go-module root)))

(cl-defmethod project-root ((project (head go-module)))
  (cdr project))

(add-hook 'project-find-functions #'project-find-go-module)

(require 'eglot)
(add-hook 'go-mode-hook 'eglot-ensure)

;; Optional: install eglot-format-buffer as a save hook.
;; The depth of -10 places this before eglot's willSave notification,
;; so that that notification reports the actual contents that will be saved.
(defun eglot-format-buffer-before-save ()
  (add-hook 'before-save-hook #'eglot-format-buffer -10 t))
(add-hook 'go-mode-hook #'eglot-format-buffer-before-save)

As additional quality of life improvements, you might consider installing company, flycheck and/or magit. Consider the package website for a complete explanation and installation instructions.

Here is an init example if you just want to use all three packages
;; company
(use-package company
  :ensure
  :custom
  (company-idle-delay 0) ;; how long to wait until popup
  (company-minimum-prefix-length 1)
  ;; (company-begin-commands nil) ;; uncomment to disable popup
  :bind
  (:map company-active-map
	("C-n". company-select-next)
	("C-p". company-select-previous)
	("M-<". company-select-first)
	("M->". company-select-last)
	)
  ;; Tab-override
  (:map company-mode-map
	("<tab>". tab-indent-or-complete)
	("TAB". tab-indent-or-complete)
	)
  )

(add-hook 'after-init-hook 'global-company-mode)


;; flycheck
(use-package flycheck :ensure)


;; magit
(use-package magit :ensure)

Vim

Vim has a Go plugin that can likely be used to work on Forgejo's code base. Do you know how to configure it properly? Why not document that here?

Neovim

Here's a minimal example that configures gopls and golangci_lint_ls using the Lazy.nvim plugin manager.

init.lua
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
	vim.fn.system({
		"git",
		"clone",
		"--filter=blob:none",
		"https://github.com/folke/lazy.nvim.git",
		"--branch=stable", -- latest stable release
		lazypath,
	})
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
	"neovim/nvim-lspconfig",
	{
		"nvim-telescope/telescope.nvim",
		branch = "0.1.x",
		dependencies = {
			"nvim-lua/plenary.nvim",
			{
				"nvim-telescope/telescope-fzf-native.nvim",
				build = "make",
				cond = vim.fn.executable("make") == 1,
			},
		},
	},
})

vim.g.mapleader = " "
vim.g.maplocalleader = " "

local on_attach = function(client, bufno)
	-- deprecated since neovim 0.10
	-- vim.api.nvim_buf_set_option(bufno, "omnifunc", "v:lua.vim.lsp.omnifunc")
	vim.api.nvim_set_option_value("omnifunc", "v:lua.vim.lsp.omnifunc", { buf = bufno })

	local ts = require("telescope.builtin")
	local opts = { buffer = bufno }

	vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float)
	vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
	vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts)
	vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
	vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
	vim.keymap.set("n", "gtd", vim.lsp.buf.type_definition, opts)
	vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
	vim.keymap.set("n", "gu", ts.lsp_references, opts)
	vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
	vim.keymap.set("n", "<leader>cl", vim.lsp.codelens.run, opts)
	vim.keymap.set("n", "<leader>r", vim.lsp.buf.rename, opts)
	vim.keymap.set("n", "<leader>f", function()
		vim.lsp.buf.format({ async = true })
	end, opts)
end

local capabilities = vim.lsp.protocol.make_client_capabilities()

require("lspconfig")["gopls"].setup({
	capabilities = capabilities,
	settings = {},
	on_attach = on_attach,
})

require("lspconfig")["golangci_lint_ls"].setup({
	capabilities = capabilities,
	settings = {},
	on_attach = on_attach,
})

Finding what to work on in Forgejo

Assuming that your development environment is ready and that you are aware of how you can reach out to the Forgejo community, this document shall describe how you can find Forgejo issues to work on.

good-first-issue label

Issues are marked using labels. The good-first-issue label is used to explicitly mark issues that may be appealing to potential new first time contributors. Content-wise, the issues can vary significantly, as they can range between simple fixes to user-facing strings, to features that primarily involve backend development or web development.

As such, do keep in mind that good-first-issue does not necessarily imply that fixing them will be easy or quick. Also, there is a possibility that an issue that you may choose may get too time-consuming. If that happens, don't fret! First, this is completely normal when trying to learn your way around a new free and open-source project. Second, you can always ask other contributors for help if you feel lost (and you should!). In the worst case, you will have learned a few things as a direct result of having been exposed to actual source code. Also, you can always choose to pick another issue, and circle back to the more difficult issue at a later point in time.

Click here to discover issues labelled using the good-first-issue label.

valuable code label

As most Forgejo contributors are volunteers, authors might not have the time to address review comments for their submitted pull request. "Work in progress" pull requests that have had unaddressed review comments for a longer period of time get closed, as it helps reviewers avoid checking whether any progress has been made repeatedly since their last review.

However, that does not mean that the features in the pull requests themselves are unwanted: More often than not, the pull requests are closed because of unaddressed review comments (which are sometimes easy to fix) or because of missing tests. The valuable-code is applied to such pull requests to ensure that they do not get lost. This label also intends to signal that there is a desire to have pull requests merged.

The valuable code way could be better tasked to people that have contributed to free and open-source projects before, and has the additional upside of helping you get acquainted with Forgejo's internals and review processes without having to learn everything at once (UI, backend, tests, documentation requirements submitting the PR). Before picking up on such tasks, we suggest pinging the original author to make sure that you would not be working on the same tasks simultaneously, yet separately.

Click here to navigate closed pull requests with the valuable code label.

Bug fixes

Another way of contributing to Forgejo is fixing confirmed bugs. Helping us improve Forgejo's stability and reliability is something that we can always use help with, and such efforts are immensely appreciated and prioritized by the reviewers. Fixing bugs is a great way to get acquainted with Forgejo's different components and development procedures without exposing yourself to a lot of complexity.

Click here to navigate confirmed bug reports.

In this context, confirmed means that an individual (other than the bug reporter) has managed to reproduce the bug described by the reporter. You can also help us with new bug reports by attempting to reproduce the bugs yourself.

Documentation

Administrators, (prospective) contributors and users of Forgejo all share one characteristic: Sooner or later, they will (most likely) have to rely on our documentation.

Improvements to our documentation help everyone involved, and it is unlikely that you will have to touch any code. Should you notice any inaccuracies or topics which are not covered by our documentation, then we encourage you to try authoring a Pull Request. You can also try working on issues that were opened by other community members.

Click here to view issues available in the forgejo/docs repository.

Further notes

Compiling from source

You should install go and set up your go environment correctly. In particular, it is recommended to set the $GOPATH environment variable and to add the go bin directory or directories ${GOPATH//://bin:}/bin to the $PATH. See the Go wiki entry for GOPATH.

Next, install Node.js with npm which is required to build the JavaScript and CSS files. The minimum supported Node.js version is 20.

[!note] Note When executing make tasks that require external tools, like make misspell-check, Forgejo will automatically download and build these as necessary. To be able to use these, you must have the "$GOPATH/bin" directory on the executable path.

[!note] Note 2 Go version 1.24 or higher is required. However, it is recommended to obtain the same version as the continuous integration.

[!note] Note 3 If you want to avoid installing build dependencies manually, you can also build the Docker image, which runs the build process in a Docker image containing all the required dependencies.

Download

First, we must retrieve the source code.

git clone https://codeberg.org/forgejo/forgejo

Decide which version of Forgejo to build and install. Currently, there are multiple options to choose from. The forgejo branch represents the current development version.

To work with tagged releases, the following commands can be used:

git branch -a
git checkout v7.0.3

To build Forgejo from source at a specific tagged release (like v7.0.3), list the available tags and check out the specific tag.

List available tags with the following.

git tag -l
git checkout v7.0.3

Build

To build from source, the following programs must be present on the system:

For testing, linting and other build configuration, the following dependencies are also required:

Also see the shell.nix and manifest.scm in the root of the forgejo repositories for Nix and Guix definitions of all dependencies.

There are a number of useful make targets, only some of which are documented here. They can all be displayed with:

$ make help
Make Routines:
 - ""                               equivalent to "build"
 - build                            build everything
 - frontend                         build frontend files
 - backend                          build backend files
 - watch                            watch everything and continuously rebuild
 - watch-frontend                   watch frontend files and continuously rebuild
 - watch-backend                    watch backend files and continuously rebuild
...

Depending on requirements, the following build tags can be included.

Using the bindata build tag is required for production deployments. You could exclude bindata when you are developing/testing Forgejo or able to separate the assets correctly.

To include all assets, use the bindata tag:

TAGS="bindata" make build

In the default release build of the continuous integration system, the build tags are: TAGS="bindata timetzdata sqlite sqlite_unlock_notify". The simplest recommended way to build from source is therefore:

TAGS="bindata timetzdata sqlite sqlite_unlock_notify" make build

The build target is split into two sub-targets:

If pre-built frontend files are present it is possible to only build the backend:

TAGS="bindata" make backend

Webpack source maps are by default enabled in development builds and disabled in production builds. They can be enabled by setting the ENABLE_SOURCEMAP=true environment variable.

[!note] Note If you have the libvips library installed globally, the frontend part of the build might fail with npm error sharp error messages. In order to fix this, set the SHARP_IGNORE_GLOBAL_LIBVIPS=true environment variable and rerun the build command.

Build the Docker image

To build Forgejo's Docker image, you need to have Docker and the Docker Buildx plugin installed. You can build the Docker image with:

docker buildx build --output=type=docker --tag forgejo:mybuild .

This will run the entire build process in a Docker container with the required dependencies. You can also supply a tag during the build process with the -t option, to make it easier to publish or run the image later.

Build using GNU Guix

A GNU Guix package manager manifest.scm file is also provided. It can be used to create a containerized environment that has all the needed build and test dependencies, via a single command:

guix shell -CNF --share=$HOME -m manifest.scm

It's important to set a few environment variables inside the guix shell environment for proper operation:

export GOTOOLCHAIN=local CC=gcc CGO_ENABLED=1

The GOTOOLCHAIN=local environment variable tells the Go build machinery to prefer the Go binary provided by Guix instead of an external copy fetched from the Go package server, which would fail to load needed libraries with messages like error while loading shared libraries: libgcc_s.so.1.

A few sample commands for building and testing in the Guix container are included in a comment in the manifest.scm file, for your convenience.

Testing

See the section dedicated to testing.

Forgejo Contributor Guide

This area is targeted to people who want to improve Forgejo and adjust it to their needs.

We encourage you to read our warm welcome first, then get involved.

When you've contributed something, you'll have learned new things in the process. Why not share those things with other contributors? Creating a personal article is an option to do so. Once you do, create a Pull Request on this page and extend the list.

Hardware infrastructure

Moved permanently to https://code.forgejo.org/infrastructure/documentation#readme. A mirror is available at https://git.pub.solar/forgejo/infrastructure-documentation.

Managing dependencies between repositories

Some Forgejo repositories that depend on each other have workflows that will trigger workflows on other repositories using the cascading-pr action.

Use cases and examples

Files copied from other repositories

setup-forgejo contains a copy of the forgejo-curl script. This script does not have numbered releases and the latest version is the one in the main branch.

forgejo-curl has a workflow that will open a pull request to setup-forgejo to update the forgejo-curl.sh script it contains. The forgejo-curl workflow will wait on the setup-forgejo workflow and fail if it does not pass, thus providing additional confirmation that the change under test won't break setup-forgejo once merged.

When the PR is merged in forgejo-curl, the corresponding PR in setup-forgejo is left open and ready to be merged with the latest version of the forgejo-curl.sh script.

Verifying an upgrade would work

setup-forgejo installs a runner by default, as specified in the action.yml file.

The runner has a workflow that will open a pull request to setup-forgejo to update the default version in the action.yml file. The runner workflow will wait on the setup-forgejo workflow and fail if it does not pass, thus providing additional confirmation that the change under test won't break setup-forgejo when it upgrades to using a release that contains the change.

When the PR is merged in forgejo-curl, the corresponding PR in setup-forgejo is closed. It is not meant to upgrade setup-forgejo because there is not yet a tag release published with this change.

Permissions

The cascading-pr action needs a token with write permissions on issues and repositories that will allow it to fork the destination repository and create a pull request from this fork. It also needs read permission on issues and pull requests for the origin repository.

The cascading-pr user is dedicated to providing such tokens.

For instance, a personal token named https://code.forgejo.org/forgejo/forgejo-curl/ was created by the cascading-pr user. This token was added as two secrets named CASCADING_PR_ORIGIN and CASCADING_PR_DESTINATION in the https://code.forgejo.org/forgejo/forgejo-curl/ repository.

Access to secrets

The workflow that contains the cascading-pr action needs access to the secrets of the repository and must run on.pull_request_target. For instance:

on:
  pull_request_target:
    types:
      - opened
      - synchronize
      - closed

Updating the workflow

When the cascading-pr workflow is added or updated in a repository, it must be done in a PR from a branch of the repository and not from a forked repository. It runs on.pull_request_target and if run from a fork it will use the content of the default branch instead of the proposed change.

Repository Migration

The sequence diagram below shows the different go packages in the top and bottom boxes and their functions within. You can find the relevant code mostly in the services/migrations folder. The arrows are to be read in sequence from top to bottom, while the description of each arrow shows the function and its simplified signature. Reference this diagram if you need a basic overview of how the packages in services/migrations are connected and how PR/Issue comments are filtered based on migration options.

diagram

The diagram was made with mermaid.js, you can find the documentation for it here.

Static pages

LXC container dedicated to hosting static HTML pages.

LXC container

See the static-pages section in the infrastructure documentation.

SSL on the LXC host

Each domain has a /etc/nginx/sites-available/f3.forgefriends.forgejo.org file similar to the following on the host where the LXC container resides.

server {
    listen 80;
    listen [::]:80;

    server_name f3.forgefriends.forgejo.org;

    location / {
        proxy_pass http://10.6.83.106:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Obtain the certificate:

ln -sf /etc/nginx/sites-available/f3.forgefriends.forgejo.org /etc/nginx/sites-enabled/f3.forgefriends.forgejo.org
sudo certbot -n --agree-tos --email contact@forgejo.org -d f3.forgefriends.forgejo.org --nginx

Creation in the LXC container

With the example of f3.forgefriends.forgejo.org and f3.forgefriends.org serving the same content.

login

From the LXC host:

lxc-helpers.sh lxc_container_run static-pages -- sudo --user $USER bash

nginx

$ cat /etc/nginx/sites-enabled/f3.forgefriends.org
server {
        listen 80;
        listen [::]:80;

        server_name f3.forgefriends.org f3.forgefriends.forgejo.org;

        root /var/www/f3.forgefriends.org;

        location / {
                try_files $uri $uri/ =404;
        }
}

clone

git clone https://code.forgejo.org/f3/html-documentation /var/www/f3.forgefriends.org

Update in the LXC container

Webhook

Create a POST webhook with the URL https://f3.forgefriends.forgejo.org/.well-known/forgejo/f3.forgefriends.org on https://code.forgejo.org/f3/html-documentation. It is expected to fail with 404, the information will be extracted from the web server logs.

To verify that it works:

Service

git pull on change

$ cat /usr/local/bin/static-pages.sh
##!/bin/bash

sudo tail -f /var/log/nginx/access.log | sed --silent --regexp-extended --unbuffered --expression 's|.*.well-known/forgejo/([^ /]+) .*|\1|p' | while read server ; do
    d="/var/www/$server"
    if test -d "$d" ; then
        echo "update $server"
        cd "$d"
        git pull
    else
        echo "unknown server $server"
    fi
done

service

$ cat /etc/systemd/system/static-pages.service
[Unit]
Description=Static pages

[Service]
User=debian
ExecStart=/usr/local/bin/static-pages.sh

[Install]
WantedBy=multi-user.target
$ sudo systemctl enable static-pages

Testing

If you want to contribute to Forgejo, keep in mind that we ask you to do a reasonable effort to test your changes.

Why would we do this? Your submission to Forgejo could be wasted if you add it, but no one checks that it still works in a few months, or a few years. We want your contribution to be meaningful and to last.

We cannot test all the behaviour of Forgejo. It is complex, and certain aspects are only important to a handful of users. To reduce the burden on the maintainers, you should do your best to make testing easy and automated.

What is a "reasonable" effort?

There is no simple answer to this, but we want to see that you gave your best. It will be decided on a case-by-case basis by the maintainers.

Some changes are easy to write a test for. Other tests can be really hard and challenging. This guide tries to provide you some starting points, and if you don't know how to continue, please submit your contribution and ask for help in writing a test. We will happily assist you.

How to write a good test?

Let's list some basic things when writing software tests. Creating good tests can be a creative challenge, and you'll benefit from experience (both from writing tests and working with the Forgejo codebase).

These basic rules should hopefully guide you the way.

Test the actual change

Some parts of the codebase don't bring extensive testing yet. Asking you to cover all the behaviour with tests would often be too much. If in doubt, try to focus on the change you made:

Take a look at this example. It tests a very simple change in diff generation. It does not add tests for all the (edge) cases of diff generation, but it provides a simple verification that the diff is generated on larger words, not letter-by-letter, by adding samples that are affected by the change.

Test the edge cases

If you test different input, consider the behaviour at edge cases. For example, what happens with an empty string? If you want to test a size limitation, do not only test content that is "way too large", also test content that is a perfect fit, and the first content size that should be rejected.

Get inspiration or help

Please don't hesitate to reach out for help or ask for feedback. In some cases, good knowledge of the codebase is helpful (e.g. to think of testing your feature's compatibility with another feature you never heard of).

We recommend that you take inspiration from existing tests. Take a look at the Git history around your files (or the kind of tests such as e2e/integration) to see who made recent changes. They have high chances of being more idiomatic and modern than the average test code, and feel free to ping people directly for help.

Getting Started

There are three kinds of tests in Forgejo.

Unit tests

These live next to the code in files suffixed _test.go (for backend code) or .test.js (for frontend code).

They test small portions of the code, mostly functions that process data and have no side-effects. Use these where possible.

Run these with:

make test

Integration tests

Find these in tests/integration. They test more complex behaviour of the app (such as performing an action and seeing that related activity is recorded). They are mostly useful when a behaviour is hard to test with isolated unit tests.

There is more detailed information in the integration test README.

End-to-end / browser tests

Find these in tests/e2e. They run real browsers and test the frontend of Forgejo. Use them to verify that things look like they should, or that pressing a specific button does what it is supposed to do. You can also run an automated accessibility analysis.

There is more detailed information in the e2e test README.

Interactive testing during development

During development, you'll probably want to interact with Forgejo to see if your changes work.

To run and continuously rebuild when the source files change:

TAGS='sqlite sqlite_unlock_notify' make watch

[!note] Note Do not set the bindata tag such as in TAGS="bindata" make watch. The browser may fail to load pages with an error like Failed to load asset.

It will create, in the source directory:

[!note] Note To enable the internal SSH server, manually add START_SSH_SERVER = true to the [server] section of custom/conf/app.ini, and ensure the SSH_PORT option is set to an unprivileged port (any port greater than 1024), such as 2222.

Automated tests

In the Forgejo repository

When a pull request (PR) is opened, it will run workflows found in the .forgejo/workflows directory.

A number of checks carried out by these jobs need to pass before a PR can be merged. The main part of these checks can be run locally by calling make pr-go. It is strongly recommended to do so before opening a PR, and the PR template reminds about it.

In the end-to-end repository

[!note] Note Do not confuse this kind of testing with the "e2e" (frontend) tests in the Forgejo repository.

Some tests are best served by running Forgejo from a compiled binary, for instance to verify the result of a workflow run by the Forgejo Runner. They can be run by adding the run-end-to-end-test label to the pull request. It will:

Debugging locally

A workflow can be run locally by installing the Forgejo Runner and using the command line:

forgejo-runner exec --workflows .forgejo/workflows/testing.yml

Manual testing

When the change to be tested lacks the proper framework, we sometimes add manual testing instructions as a last resort. The exact test steps must be documented in the description of the pull request.

Changes that are associated with manual tests must be labeled "test/manual".

Using gotestsum for summaries

By default, the make targets use the built-in go test command. However, it does not produce clear summaries of test results which are useful for understanding quickly the extent of failures. The provided Makefile supports switching to gotestsum using USE_GOTESTSUM=yes while running tests, for example:

USE_GOTESTSUM=yes make test

gotestsum behaves like the default go test except it reformats the test results so that they are clearer for human interpretation and include a helpful summary.

You need to have gotestsum installed (see the link above) to use it as it does not come built-in unlike go test.

Welcome to Forgejo

We are happy to welcome you to the Forgejo project. Please take the time to read this page to the end.

Quality standards

The Forgejo community is motivated to deliver a high quality product to all our users. Please be aware that we:

For you, this means that you:

Getting Started

These steps are recommended for all users:

Getting into the project

Made your first contributions? Consider yourself a part of the community. Hang around in the chats, have fun with others and feel free to share your feedback, even on things you didn't yet contribute to.

We recommend you make yourself familiar with our governance repository and watch the activity for decisions you might want to participate in. Check out the teams and consider applying to those who match your interests.

Weblate admin duties

Localization team members with admin rights on both Forgejo and Weblate are expected to make sure the translations land in the Forgejo repository and resolve conflicts when they arise.

Merging translations in Forgejo

Weblate is configured to propose pull requests to the Forgejo repository with new translations. These pull requests are normally merged with a merge commit into the Forgejo development branch as follows:

Merging a pull request that changes translations

When a Forgejo pull request modifies locale files other than base which is en-US, it must be merged after all pending changes in Weblate are merged, while the impacted components are locked.

Resolving failures

Weblate locked due to network error

Sometimes a connectivity error with Codeberg or it's unavailability can cause Weblate to lock. The lock error looks like this:

kex_exchange_identification: Connection closed by remote host
Connection closed by 217.197.91.145 port 22
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
 (128)

Weblate will retry connection attempts but it takes hours before it does that. If Codeberg is currently available and working, the project can simply be unlocked manually to allow the translators to keep working.

Commit changing non-base locales was merged before Weblate

If a commit changing translation files other than en_US.ini was merged before all changes from Weblate were merged, it could have caused Weblate to lock itself due to a failed rebase. If the rebase did succeed, everything is ok and no steps need to be taken, just be more cautious with merges next time. If Weblate failed to rebase, there are two ways to solve this issue.

Revert and merge in correct sequence

  1. Make a PR that reverts the commit that caused the breakage
  2. Merge this PR
  3. Make Weblate commit and push all changes
  4. Merge the Weblate PR
  5. Rebase the commit(s) of the PR that caused the breakage on top of the merged Weblate commit, open a new PR

Merge translations manually

  1. Fetch the branch that Weblate uses: git remote add weblate https://translate.codeberg.org/git/forgejo/forgejo git fetch -u weblate git checkout weblate/forgejo
  2. Checkout into a new branch: git checkout -b i18n-weblate-recovery
  3. Rebase it on top of latest commit in main Forgejo branch git rebase -i <latest-commit>
  4. Propose a new PR with fixed commit and merge it
  5. Close PR created by Weblate, reset Weblate

Alternatively to rebase you can also interactively cherry-pick the commit on top of a new branch based on latest.

Other merge strategies

Forgejo used to have different merge workflows based on squashing the PR. The old instructions are available in older version of the documentation.

English base localization

Forgejo base localization is English. This means that all translations are derived from it.

Managing strings

English localization strings are stored in two locations:

Strings are translated on Weblate and string management is partially done by it.

Additions and minor edits

Addition of new strings, deletion of obsolete strings and minor changes should be be submitted in pull requests as is. Edits are considered minor unless they include changes to handling of placeholders (%s, %[n]s, %d, %[n]d).

+ "new-string": "Hello, world!",
//
- "obsolete-string": "This string is no longer used",
//
- "edited-string": "Remove this runner",
+ "edited-string": "Remove runner",

When a new string is added, it must be double-checked that its key is unique and is not present already.

"repo.pulls.poster_trust_deny": "Deny",
// <1000 other lines>
"repo.pulls.poster_trust_deny": "Deny access", // Oh, no!

Changes to non-base files (translations) are must only be done on Weblate and not via individual pull requests to

When a UI change renders some string unused, it must be deleted.

Deletions

Unused strings must only be deleted in base language. They will disappear from Weblate after the PR is merged. Removal of orphaned translations from the files is handled by Weblate for JSON and manually by the Localization team for INI components.

Avoiding deletion in non-localization pull requests prevents merge conflicts with Weblate changes and during backports, and saves time for both authors and reviewers.

Incompatible edits

String placeholders cannot be changed as is.

// This change to base locale
- "initial-key": "Remove this runner?",
+ "initial-key": "Remove runner %s?",
// Breaks string rendering in all other locales
"initial-key": "Изтриване на изпълнител?",

If there's an intention to preserve existing translations, a migration can be prepared in coordination with the Localization team admins:

If it is deemed fine to replace the string, the key needs to be changed:

- "initial-key": "Remove this runner?",
+ "updated-key": "Remove %[1]s?",

This is the easiest option for those working on the UI changes, but the translators will have to re-do their work.

Refactoring keys

Sometime string keys are named poorly and need to be changed. In such cases they need to be mass-changed for all languages into which the strings have already been translated, so that the existing translations aren't lost. This includes merging the Weblate PR first and then re-applying the rename to avoid race conditions with changes from Weblate. It is a complicated process, so renaming keys is generally not recommended unless there's a good reason.

Localization style

Capitalization

All strings should have regular capitalization. Headers, labels, buttons and such should start with a capital letter. Only names, product names and such should be capitalized after that. Git/Forgejo specific measurement units should not be capitalized.

Follow these examples for string capitalization:

Context ❌ Bad ✅ Good
Button edit Edit
Header Manage Organizations Manage organizations
Option Use Custom Avatar Use custom avatar
Button Add Cleanup Rule Add cleanup rule
Label Integrate matrix into your repository. Integrate Matrix into your repository.
Label %s Commits %s commits

Other stylistic choices

Form labels should not end with any punctuation marks.

Follow these examples:

Context ❌ Bad ✅ Good
Form label Username. Username
Form label Username: Username

Ensuring good translatability

Strings expose the ability to edit the entire parts of text shown in the UI.

Using placeholders

When possible, strings should include placeholders for everything displayed within the given context. When there's more than one placeholder, it's best to use numbered placeholders.

Good example:

"event.user_pushed": "%[1]s pushed %[2]s on %[3]s"
{{ctx.Locale.Tr "event.user_pushed" .User .Commit .Commit.Time}}

Bad example:

In this example, the text is constructed in the template rather than with placeholders. This greatly reduces flexibility of translations and is confusing to translate.

"event.push.pushed": "pushed",
"event.push.on": "on"
{{.User}} {{ctx.Locale.Tr "event.push.pushed"}} {{.Commit}} {{ctx.Locale.Tr "event.push.on"}} {{.Commit.Time}}

Using complex plurals

For strings that include a number and an associated noun, a specific plural format must be used so the words in the translated text can be adapted to the appropriate plural forms, which vary in different languages.

Good example:

"n_commits_found": {
	"one": "%d commit was found.",
	"other": "%d commits were found."
}
{{ctx.Locale.TrPluralString .NumCommits "n_commits_found" .NumCommits}}

[!note] There's no dedicated zero case where placeholders would be unused like so:

"n_commits_found": {
	//...
	"zero": "No commits were found.",
}

Instead, use Go template logic to handle such cases if needed:

"no_commits_found": "No commits were found.",
{{if ne .NumCommits 0}}
	{{ctx.Locale.TrPluralString .NumCommits "n_commits_found" .NumCommits}}
{{else}}
	<!-- This could also be displayed differently. E.g. more like a placeholder -->
	{{ctx.Locale.Tr "no_commits_found"}}
{{end}}

Bad example:

In this example, no plural router is used. Such text can only be correctly displayed with all different numbers only in few languages.

"commits_found": "commits were found"
{{.NumCommits}} {{ctx.Locale.Tr "commits_found"}}.

Bad example:

In this example, simple plural router is used, but the strings include the numbers. This way, translated variants of the text can be displayed correctly in more languages than in previous example, but still in far from all languages.

"n_commits_found.one": "%d commit was found.",
"n_commits_found.few": "%d commits were found."
{{ctx.Locale.TrN .NumCommits "commits_found.one" "commits_found.few" .NumCommits}}

Using simple plurals

When strings should be written out differently depending on the number of objects they reference, but do not include the number, different variants should not be stored in the dict format as they would not benefit translatability in other languages.

Good example:

"config.parse_errors.one": "There was an error parsing the config:",
"config.parse_errors.few": "There were errors parsing the config:"
{{ctx.Locale.TrN (len .Errors) "config.parse_errors.one" "config.parse_errors.few"}}
<ul>
	{{range .Errors}}
		<li>{{.}}</li>
	{{end}}
</ul>

Bad example:

In this example, usage of dict format and TrPluralString provides no benefit to languages that use more than two plural variants.

"config.parse_errors": {
	"one": "There was error parsing the config:",
	"other": "There were errors parsing the config:"
}
{{ctx.Locale.TrPluralString (len .Errors) "config.parse_errors"}}

Formatting numbers

Large numbers can be formatted as pretty strings like so:

"n_commits_found": {
	"one": "%s commit was found.",
	"other": "%s commits were found."
}
{{ctx.Locale.TrPluralString .NumCommits "n_commits_found" (CountFmt .NumCommits)}}

Result: 1,000,000 commits were found.

Styling of placeholders

It is best to avoid wrapping placeholders in HTML tags. This makes translating unnecessarily harder.

Good example:

"n_commits_found": {
	"one": "%s commit was found.",
	"other": "%s commits were found."
}

For trusted content such as numbers, printf and TrustHTML can be used:

{{ctx.Locale.TrPluralString .CommitsCount "n_commits_found" (printf "<b>%d</b>" .CommitsCount | TrustHTML)}}

Good example:

"user_invited": "You're invited to team %s."

For untrusted content such as user provided strings, HTMLFormat must be used:

{{ctx.Locale.Tr "user_invited" (HTMLFormat "<b>%s</b>" .Team.Name)}}

Bad example:

In this example, HTML tags are polluting the translation file.

"n_commits_found": {
	"one": "<b>%s</b> commit was found.",
	"other": "<b>%s</b> commits were found."
}

Styling of text

When the intention is to highlight a part the string, or to make a link, tags are welcome in the strings. For links, storing the actual URLs in the strings must be avoided.

Good example:

"warning": "This change is <strong>irreversible</strong>. Proceed?",
"with_link": "See <a href=\"%s\">the documentation</a> for pattern syntax."
{{ctx.Locale.Tr "warning"}}
{{ctx.Locale.Tr "with_link" "https://forgejo.org/docs/latest/"}}

Bad example:

In this example, the word irreversible is moved out of the full string to avoid HTML tags in translations. However, this creates unnecessary complexity in form of multiple strings.

Additionally, the link is stored in the translations, making it more difficult to update.

"warning": "This change is %s. Proceed?",
"irreversible": "irreversible",
"with_link": "See <a href=\"https://forgejo.org/docs/latest/\">the documentation</a> for pattern syntax."
{{ctx.Locale.Tr "warning" (HTMLFormat "<strong>%s</strong>" ctx.Locale.Tr "irreversible")}}
{{ctx.Locale.Tr "with_link"}}

Parts that don't need to be translated

If something doesn't need to be translated, it is best to include it via placeholder.

Good example:

"unsupported_field.permissions": "The %s field is not supported."
{{ctx.Locale.Tr "unsupported_field.permissions" ("<code>permissions</code>" | TrustHTML)}}

Bad example:

In this example, the part that doesn't need to be translated is stored in the string along with tags. Translators will have to copy-paste it. Some will try to translate it.

"unsupported_field.permissions": "The <code>permissions</code> field is not supported."
{{ctx.Locale.Tr "unsupported_field.permissions"}}

Contributing

This section is to help you contribute to the English localization of Forgejo.

Suggesting improvements

You send bugs, suggestions and feedback via Forgejo issue tracker or Forgejo Localization Matrix chatroom. Comments on Weblate also might work but are not recommended because they're checked rarely and might not be noticed.

Proposing changes

Similarly to other projects you can propose changes via a pull request. Read the informative sections for string managing and style guidelines above. You can perform small changes via Forgejo web UI. Otherwise you can make a fork or use AGit workflow, clone, checkout a new branch, make your changes, commit, push and open a pull request. Put some useful explanation of the change in the PR description. Screenshots are also highly welcome.

If it's a small change to the string, you don't need to test it locally. However, if you want to make a larger text refactoring, consider verifying that everything looks good together in live by building and running Forgejo locally. See For contributors for more information how to do this.

If your refactor affects key names, make sure to perform a mass-rename of the affected keys in all code, templates and translations. Sometimes keys are generated in code with conditions. Please check for that as well. With such a refactoring you might be asked to resolve merge conflicts before your PR can be merged. If you have low availability, it is best to avoid renaming translation keys.

Localization

Forgejo is translated via Weblate, a libre web-based translation platform.

Translating via Weblate

The Forgejo project's localization project is publicly available via the Codeberg Translate Weblate instance.

Translation guidelines

  1. Only suggest changes that benefit all users of the translation. Please do not suggest changes that will only make the translation better in cases specific to any self-hosted Forgejo instance. Instead customize such instances separately from Forgejo upstream.
  2. Keep the translation as beginner-friendly as possible.
  3. Users are not obligated to complete any translation. When unsure about the translation, feel free to leave it for others to translate.

Discovering the translation

Go to the Project page for a list of languages that are currently available for translation.

From the language page it is possible to browse all translation strings, as well as untranslated, unfinished and failing translations.

Suggesting changes

Anonymous suggestions for changes and additions to the existing translation can be submitted by finding the string for which to suggest a change, typing the change in, and then clicking "Suggest".

All suggested change will be checked before being accepted. Since most localization members are likewise volunteers, this can take a while.

Making direct changes, accepting suggestions

Direct changes require a Codeberg account which can be used to access the Codeberg Translate account.

If the translation is not approved it is possible to edit the string again and use the "Save" button to save the change. Existing suggestions can either be applied, or rejected by optionally specifying the rejection reason.

Once the string is translated and approved it can only be changed by a Forgejo Localization Team member, though everyone else is still able suggest changes.

To protect the existing translations from vandalism, all strings imported from Gitea were automatically marked as approved.

Adding a new language

If your language is not present among languages in the project, it must be added before you'll be able to start translating.

New languages are added per-request. Make one by contacting the Localization team. The most reliable ways are listed in Discussions.

E-mail privacy

By default, Weblate will use your primary e-mail address for all contributions. If you want to adjust this behavior, go to Weblate settings - Account and select a different e-mail under the "Commit e-mail" section. Select a noreply address to avoid using a real one.

Tips

Finding strings through UI

It is possible to find keys corresponding to translation strings on a page quickly by appending a lang=dummy query parameter to the URL. It will make all texts on a page use their keys instead of the actual translations. For example:

# If no parameters are present:
- https://codeberg.org/user/settings
+ https://codeberg.org/user/settings?lang=dummy

# If other parameters are present:
- https://codeberg.org/translate?tab=stars
+ https://codeberg.org/translate?tab=stars&lang=dummy

Searching for strings on Weblate

The following applies to the forgejo component which uses the INI format.

When searching on Weblate, the first part of keys ("section") may need to be wrapped with []. For example, on Weblate, settings.enable_custom_avatar should be searched with [settings]enable_custom_avatar, issues -> [common]issues, and filter.public -> [common]filter.public. If you are getting no results, wrap the section and search again.

Instance with up-to-date translations

The most up to date translations can be seen in live on a dev instance: https://dev.next.forgejo.org. The translations here may lag behind Weblate for up to a month, but it may still better than a production instance for a quick overview.

Discussions

You can ask for help, discuss translations, make suggestions for source strings, or contact localization maintainers through appropriate channels:

Joining the Localization Team

Any Codeberg Translate user is able to suggest translations, translate new strings, add comments and discuss existing translations with our Localization Team.

If you would like to maintain the translation, join the Localization Team as a member by sending us an application.

However, before doing that, we recommend working on translations independently before applying. This allows time to get used to the workflow and collaborating within the Localization Team. Members are able to accept their own suggestions.

Applying

In order to become a member of the team apply by opening a new issue at forgejo/governance repository. See previous applications for inspiration.

In the application message, state the following:

The application process will take approximately two (2) weeks (or more) to complete.

Responsibilities

Becoming a part of any of our team comes with a couple of responsibilities:

Translations should aim to target people of different backgrounds across all reasonable end user locales.

Since text is highly subjective, this is simply a goal that should be striven for and not a measurable requirement. Remain receptive to creative feedback from the Localization Team members.

Troubleshooting

When having problems using Codeberg Translate, there are multiple support channels available:

Service Messages

Service Messages Feature Description

Mastodon instances like mastodon.art provide their community rules via a mandatory dialog that has to be accepted before sign up. Similarly Forgejo instances could benefit from a dedicated "Service Messages" Mechanism that allows organizations to prominently show their guidelines to users on certain occasions in the login flow. The requirement here is that agreement to the guidelines happens after sign up. Also this feature is a requirement for compliance within regulated entities. Here, regulators require that certain legal information reach the user and the user decision to accept the info needs to be recorded.

For example:

See uctkm002-admin-sets-service for an exemplary flow

Requirements

Feature Description

A Service Message is a notification to all users that can only be set by an instance admin. There is currently one kind of Service Message: "modal".

The management UI will provide a typed text input field with markdown and preview capability. Saving the field will immediately show the service message to any logged in user (also the admin) after their next request to the UI. With this behaviour we can also reach users who might be logged in for a longer period of time.

Technical Diagrams

The connection between templates and routers/web is described here uctkm002-frontend-backend-connection.

An initial technical view of how a confirmation may be saved: uctkm002-saving-confirmation.

ADR

Web Endpoints for Management

To avoid conflicts with the existing structure the service message was implemented using:

POST /admin/service_message/ # Create, Update POST /admin/service_message/delete_modal # Delete GET /admin/service_message/ # Show current service message

Logic for showing the service message

Uctkm002-1&2 have in common, that the user has not accepted an existing modal service message yet. Leveraging that fact, we just need to check the following:

If any of these questions are answered yes, the service message is shown.

Distinction by type

Considering the requirements and feature description, it becomes apparent that probably only few types of Service Messages will exist (and be shown at the same time).

So for now it makes sense to have typed input fields in the admin management UI. Those fields will then send a form POST to {{AppSubUrl}}/admin/service_message?sm_type="modal". This saves us creating and managing options while being clearer to the admin as well. Currently modal will be the only type. If more Service Messages need to be shown, the process could include adding types like banner.

Confirm button and saving a timestamp to database

The confirm button triggers a POST action against {{AppSubUrl}}/user/{username}/confirm?sm_type=sm.type. See uctkm002-saving-confirmation for the full flow.

It makes sense to save the confirmation in the user model for the following reasons:

  1. Saving the confirmation per user in ServiceMessage would require potentially hundreds of thousands of requests to the DB to a single table.
  2. Cleanup Jobs would need to be done to also delete users from the ServiceMessage table
  3. RAM usage will increase if we always have a huge list of users loaded when a ServiceMessage is loaded from DB
  4. The user/{username} endpoint requires an authenticated request, so we can make sure the user is already logged in.
  5. The User Model is already loaded and interacted upon when a user is logged in

Threat Analysis

Threat Analysis: Remote Login Propagation

See also blog: geballte sicherheit for getting an idea about the analysis.

Technical Background

Control Flow

diagram

Data transferred

# NodeInfoWellKnown
{"links":[
  {"href":"https://federated-repo.prod.meissa.de/api/v1/nodeinfo",
  "rel":"http://nodeinfo.diaspora.software/ns/schema/2.1"}]}

# NodeInfo
{"version":"2.1",
 "software":{"name":"gitea",
 ...}}

# LikeActivity
{"id": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1/outbox/12345",
  "type": "Like",
  "actor": "https://repo.prod.meissa.de/api/v1/activitypub/user-id/1",
  "object": "https://codeberg.org/api/v1/activitypub/repository-id/12"
  "startTime": "2014-12-31T23:00:00-08:00"
}

# Person
{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
 "type":"Person",
 "preferredUsername":"stargoose9",
 "name": "goose going to star the repo",
 "publicKey":{"id":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10#main-key",
		"owner":"https://federated-repo.prod.meissa.de/api/v1/activitypub/user-id/10",
		"publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBoj...XAgMBAAE=\n-----END PUBLIC KEY-----\n"}}

Data Flow

diagram

Analysis

Assets

  1. Service Availability: The availability of our or foreign servers.
  2. Instance Reputation: We hope our project does not live on a spam instance.
  3. Project Reputation: The reputation of an individual project.

Actors

  1. Script Kiddies: Bored teens, willing to do some illegal stuff without deep knowledge of tech details but broad knowledge across internet discussions. Able to do some bash / python scripting.
  2. Experienced Hacker: Hacker with deep knowledge.
  3. Hacker: Hacker with some knowledge.
  4. Malicious Fediverse Member: Malicious Members of the fediverse, able to operate malicious forge instances.
  5. Malicious Forge Admin: Admin of good reputation forge instance in the fediverse.
  6. Federated User: Members of good reputation forge instance in the fediverse.

Threat

  1. tbd

Mitigations

  1. tbd

DREAD-Score

Threat Damage Reproducibility Exploitability Affected Users Discoverability Mitigations
1. ... tbd
2. ... tbd

Threat Score with values between 1 - 6

Contributors

Reference