Contributing to Forgejo
Christopher Besch • 21st November 2025

Contributing to Forgejo

Why Forgejo?

  • Fully featured code forge
    (code development platform)
  • Fully Open-Source
    → Data sovereignty
    → We can add custom features

Why not GitHub?

  • Vendor lock-in:
    no license → no access to own data
    → No Data sovereignty
  • Microsoft/Atlassian push us in Cloud

Why not GitLab?

  • Only Open-Core
    → Same problem

Go is kinda weird...

  • Import things from Domains? "import forgejo.org/cmd"
  • Declare unit test with "_test.go" suffix
  • "func SomeFunc()" is exported but "func someFunc()" is not

Anatomy of a Go Project

                        
                        
                    
  • A single module
    (program or library)
  • Many packages (each with muliple .go files)

/go.mod

                        
                        
                    
  • go.mod defines a module (automatically generated)
  • github.com/hashicorp/go-version is dependency, download from GitHub
  • Fork, new maintainer, domain lost: replace

/main.go

                        
                        
                    
  • Function main in package main is entrypoint
  • Use cmd package in forgejo.org module
    This is not a web URL!

/cmd/main.go

                            
                            
                        
  • Package name must be same as parent directory name

/modules/log/init.go

                            
                            
                        

Forgejo's Layered Architecture

  • Lower Layer self-contained
    • /models: database abstraction
    • /modules: could be third-party library
    • Easier to reason about
  • Other layers only use code below
  • → Code in lower layers easier to reason about

Problem: Cyclic Imports

Logical Dependency

  • /services/automerge calls
    /services/pull
    (for mergeability check)
  • /services/pull calls
    /services/automerge
    (for noticing new PR reviews)
  • Go doesn't allow cyclic imports

Problem: Cyclic Imports

Actual Imports

  • /services/automerge calls
    /services/pull
    (for mergeability check)
  • /services/pull calls
    /services/automerge
    (for noticing new PR reviews)
  • Go doesn't allow cyclic imports
  • Solution: A third package:
    /services/notify

Problem: Cyclic Imports

Notify Topics

  • Go doesn't allow cyclic imports
  • Solution: A third package:
    /services/notify
  • Send/Receive message to/from PullRequestReview topic
  • Import for other direction
    (here hidden)
  • Easy to comprehend and extend system

Forgejo's Pub-Sub Pattern

User

                            
                            
                        

Broker

                            
                            
                        

Visualization

Forgejo Actions

  • CI/CD:
    Run code at every push/PR/...
  • Failure → developer needs to fix things
  • How does she notice failure?
    • Web UI (Pull) Already present
    • Mail (Push) My Job
    • Webhook (Push) My Job

How to implement Push Notification

  • Use pub-sub broker
  • /services/mailer and /services/webhook receive ActionRunNowDone message
  • Something sends ActionRunNowDone message
    • /models/actions notices action status changes
    • Problem: layered architecture doesn't allow importing
      /services/notify

#7510: Refactoring

  • Move code from /models/actions to /services/actions
    • UpdateRunJob changes database ← notices status change
    • CleanRepoScheduleTasks calls UpdateRunJob
  • Goal: change as little as possible

#7491: ActionRunNowDone Topic

  • Add ActionRunNowDone topic
  • Implement sendActionRunNowDoneNotificationIfNeeded
  • UpdateRunJob calls sendActionRunNowDoneNotificationIfNeeded
  • A lot of testing

#7509: Action Notification Mail

  • /services/mailer listens to ActionRunNowDone
  • Figure out who to send mail to
  • Template and send pretty email
  • A lot of testing

#7508: Action Notification Webhook

  • /services/webhook listens to ActionRunNowDone
  • Implement new Webhook type
  • Update webhook settings
  • A lot of testing

Visualization

  • Other PRs: #7697, #8066, #8250, #8227 and #8242
    • → cleanup, mail opt-in and resolve naming conflict
  • 70 files touched
  • 2423 lines inserted
  • 630 lines deleted

Lessons

  • Forgejo's Maintainers are amazing!
  • Code research important
  • Aim for smallest change
  • Split feature into multiple PRs
  • Testing, testing, testing
  • → Easier to reason about → easier review
  • Upstreamed → no fork needed

What we've Seen

  • Forgejo is cool
  • Go project structure
  • Layered architecture
  • Pub-sub pattern
  • Experience in contributing

There's More