A Plugin to sync your gitlab instance or gitlab.com with a discourse forum.
Find a file
2026-01-15 20:39:27 +00:00
app first commit 2026-01-15 20:39:27 +00:00
config first commit 2026-01-15 20:39:27 +00:00
db/migrate first commit 2026-01-15 20:39:27 +00:00
.gitignore first commit 2026-01-15 20:39:27 +00:00
about.json first commit 2026-01-15 20:39:27 +00:00
CLAUDE.md first commit 2026-01-15 20:39:27 +00:00
LICENSE first commit 2026-01-15 20:39:27 +00:00
plugin.rb first commit 2026-01-15 20:39:27 +00:00
README.md first commit 2026-01-15 20:39:27 +00:00

Discourse GitLab Plugin

A Discourse plugin that integrates with GitLab (hosted or self-hosted) to provide linkbacks, badges, and permalinks. This plugin is adapted from the original discourse-github plugin.

Features

This plugin provides three main features for GitLab integration:

Linkback

Automatically creates comments (notes) on GitLab issues, merge requests, and commits when they're mentioned in Discourse posts. When users discuss GitLab content on your Discourse forum, a linkback comment is posted on GitLab with an excerpt and link to the discussion.

Example: If a user posts "Check out this fix: https://gitlab.com/myorg/myproject/-/merge_requests/42", the plugin will post a comment on MR #42 linking back to your Discourse discussion.

Badges

Automatically grants badges to Discourse users based on their contributions to tracked GitLab repositories. Distinguishes between:

  • Committers: Users who commit directly to repositories
  • Contributors: Users who contribute via merge requests

Badge Tiers:

  • Bronze: 1 commit/contribution
  • Silver: 25 commits/contributions (configurable)
  • Gold: 250 commits/contributions (configurable)

Automatically converts branch-based GitLab file links to permanent commit-based links after a 1-hour grace period. This ensures that links in your Discourse posts don't break when branches are updated or deleted.

Example: https://gitlab.com/myorg/myproject/-/blob/main/README.mdhttps://gitlab.com/myorg/myproject/-/blob/abc123def456.../README.md

Installation

Follow the standard Discourse plugin installation guide:

  1. Add the plugin to your app.yml:
hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          - git clone https://github.com/discourse/discourse-gitlab.git
  1. Rebuild your Discourse container:
cd /var/discourse
./launcher rebuild app
  1. Enable the plugin in your Discourse admin settings (Settings → Plugins → discourse-gitlab)

Configuration

Required Settings

  1. Enable the plugin: Check "enable discourse gitlab plugin" in settings
  2. GitLab Base URL: Set to https://gitlab.com for hosted GitLab, or your self-hosted URL (e.g., https://gitlab.example.com)
  3. Access Token: Generate a GitLab Personal Access Token with api scope

Generating a GitLab Access Token

  1. Log into your GitLab instance
  2. Go to User Settings → Access Tokens (or visit https://gitlab.com/-/user_settings/personal_access_tokens)
  3. Create a new token with these settings:
    • Token name: "Discourse Integration" (or any name you prefer)
    • Scopes: Check api (Full API access)
    • Expiration: Optional (recommended: set to 1 year and create calendar reminder)
  4. Click "Create personal access token"
  5. Copy the token immediately (it won't be shown again)
  6. Paste the token into the "gitlab linkback access token" setting in Discourse

Important: Keep this token secure. Anyone with this token can access your GitLab API.

Feature Configuration

Linkback Settings

  • Enable: Check "gitlab linkback enabled"
  • Projects: Specify repositories in pipe-separated format: myorg/repo1|myorg/repo2
    • Supports wildcards: myorg/* matches all projects under myorg
    • Supports nested groups: parent/child/project
  • Maximum Links: Limit links processed per post (default: 25)

Example configuration:

myorg/main-project|myorg/docs|otherorg/shared-lib

Badge Settings

  • Enable: Check "gitlab badges enabled"
  • Repositories: List repositories to track (pipe-separated): myorg/repo1|myorg/repo2
    • Format: namespace/project or full URL
    • Supports nested groups: parent/child/project
  • Thresholds:
    • Bronze: 1 commit (automatic)
    • Silver: 25 commits (configurable via "gitlab silver badge min commits")
    • Gold: 250 commits (configurable via "gitlab gold badge min commits")

User Matching: The plugin matches GitLab contributors to Discourse users by:

  1. OAuth account linkage (if users signed in with GitLab OAuth)
  2. Email address matching
  3. Parsing GitLab no-reply emails (12345-username@users.noreply.gitlab.com)

Example configuration:

myorg/main-project|myorg/api-server
  • Enable: Check "gitlab permalinks enabled"
  • Exclude Patterns: Files to skip (pipe-separated): README.md|*/test/*|*.lock
    • Supports wildcards: * matches any characters
    • Patterns match against file path or full project/path/to/file

Example configuration:

README.md|CHANGELOG.md|*.lock|*/test/*

Key Differences from GitHub Plugin

Architecture Changes

  1. Configurable Base URL: Supports both gitlab.com and self-hosted GitLab instances
  2. Merge Requests vs Pull Requests: GitLab calls them "merge requests" (MRs)
  3. Notes vs Comments: GitLab API uses "notes" for MR/issue comments, "comments" for commits
  4. IID-based Resources: GitLab uses project-scoped IIDs (internal IDs) for issues/MRs
  5. Project Identifiers: Uses namespace/project format, URL-encoded for API calls
  6. Nested Namespaces: GitLab supports subgroups like parent/child/project

API Differences

Feature GitHub GitLab
API Base Path / /api/v4/
Auth Header Authorization: token X PRIVATE-TOKEN: X
MR/PR Comments POST /repos/{owner}/{repo}/issues/{number}/comments POST /api/v4/projects/:id/merge_requests/:iid/notes
Commit Comments POST /repos/{owner}/{repo}/commits/{sha}/comments POST /api/v4/projects/:id/repository/commits/:sha/comments
Commit URL github.com/{owner}/{repo}/commit/{sha} gitlab.com/{namespace}/{project}/-/commit/{sha}
MR/PR URL github.com/{owner}/{repo}/pull/{number} gitlab.com/{namespace}/{project}/-/merge_requests/{iid}
Issue URL github.com/{owner}/{repo}/issues/{number} gitlab.com/{namespace}/{project}/-/issues/{iid}

Removed Features

None! All three main features from the GitHub plugin work with GitLab:

  • Linkback (comments on issues/MRs/commits)
  • Badges (grant badges based on commits)
  • Permalinks (convert branch links to commit links)

Enhanced Features

Contributor Detection: GitLab provides a /commits/:sha/merge_requests endpoint that allows accurate detection of whether a commit came through a merge request, improving contributor vs committer badge accuracy compared to the GitHub plugin.

Troubleshooting

Linkbacks not appearing

Symptoms: GitLab comments aren't being created when GitLab links are posted

Solutions:

  1. Verify access token has api scope (not just read_api)
  2. Check GitLab base URL is correct with no trailing slash
  3. Ensure project is in "gitlab linkback projects" list
  4. Check for wildcard match: myorg/* should match myorg/myproject
  5. Verify post was created/edited after plugin was enabled
  6. Check Discourse logs for errors:
    tail -f /var/discourse/shared/standalone/log/rails/production.log
    
  7. Verify GitLab project permissions allow API access

Common errors in logs:

  • 401 Unauthorized: Token is invalid or lacks required scope
  • 404 Not Found: Project path is incorrect or not accessible
  • 403 Forbidden: Token doesn't have permission to comment

Badges not being granted

Symptoms: Users aren't receiving GitLab badges despite having commits

Solutions:

  1. Ensure "gitlab badges enabled" is checked
  2. Verify access token has api scope
  3. Check repositories are correctly specified in "gitlab badges repos"
  4. Wait up to 4 hours for scheduled job, or trigger manually:
    # In Rails console: discourse/bin/rails c
    Jobs::GrantGitlabBadges.new.execute({})
    
  5. Verify user email matches GitLab commit email OR user has OAuth account linked:
    # Check user's linked accounts
    User.find_by_email("user@example.com").user_associated_accounts
    
  6. Check commits are being fetched:
    # View fetched commits
    DiscourseGitlabPlugin::GitlabRepo.first.commits.count
    
  7. Check badges were created:
    Badge.where("name LIKE '%GitLab%'").pluck(:name)
    

Common issues:

  • Email mismatch: User's Discourse email doesn't match their GitLab commit email
  • No commits fetched: Access token can't read repository
  • Wrong role_id: All commits showing as contributors (0) or committers (1)

Symptoms: Branch-based file links aren't being converted to commit permalinks

Solutions:

  1. Confirm "gitlab permalinks enabled" is checked
  2. Verify GitLab instance URL matches link format exactly
  3. Check file isn't in exclusion list (see "gitlab permalinks exclude")
  4. Remember: 1-hour grace period before conversion
  5. Ensure branch exists and API access is granted
  6. Check for errors in logs:
    tail -f /var/discourse/shared/standalone/log/rails/production.log | grep Permalink
    
  7. Verify link format matches: /-/blob/{branch}/ (GitLab uses /-/ separator)

Common issues:

  • Too recent: Post is less than 1 hour old (grace period)
  • Wrong URL format: Link doesn't match GitLab URL pattern
  • Branch doesn't exist: Branch was already deleted
  • Permission denied: Token can't access branch information

Self-Hosted GitLab Issues

Symptoms: Plugin doesn't work with self-hosted GitLab instance

Solutions:

  1. SSL Certificate Errors: Ensure your self-hosted GitLab has valid SSL certificates
    • Check certificate: curl -v https://gitlab.example.com
    • If using self-signed certificates, this plugin won't work (Discourse requires valid certs)
  2. Firewall: Verify Discourse server can reach GitLab instance
    • Test connectivity: curl -H "PRIVATE-TOKEN: your-token" https://gitlab.example.com/api/v4/version
  3. API Version: Plugin requires GitLab 13.0+ (API v4 support)
    • Check version: curl https://gitlab.example.com/api/v4/version
  4. Base URL: Ensure "gitlab base url" setting matches your instance exactly
    • Correct: https://gitlab.example.com
    • Incorrect: https://gitlab.example.com/ (trailing slash)
  5. Port Numbers: If using non-standard ports, include in base URL
    • Example: https://gitlab.example.com:8443

Development

Testing API Connectivity

From Rails console (discourse/bin/rails c):

# Test repository access
require 'excon'
url = "https://gitlab.com/api/v4/projects/namespace%2Fproject"
response = Excon.get(url, headers: { "PRIVATE-TOKEN" => "your-token" })
puts JSON.pretty_generate(JSON.parse(response.body))

# Test posting a note to merge request
url = "https://gitlab.com/api/v4/projects/namespace%2Fproject/merge_requests/1/notes"
response = Excon.post(url,
  body: { body: "Test comment from Discourse" }.to_json,
  headers: {
    "PRIVATE-TOKEN" => "your-token",
    "Content-Type" => "application/json"
  }
)
puts response.status  # Should be 201

# Test commit comment
url = "https://gitlab.com/api/v4/projects/namespace%2Fproject/repository/commits/abc123/comments"
response = Excon.post(url,
  body: { note: "Test commit comment" }.to_json,
  headers: {
    "PRIVATE-TOKEN" => "your-token",
    "Content-Type" => "application/json"
  }
)
puts response.status  # Should be 201

Manual Badge Granting

To immediately grant badges without waiting for the 4-hour scheduled job:

# In Rails console
Jobs::GrantGitlabBadges.new.execute({})

Checking Plugin Status

# Verify plugin is enabled
SiteSetting.enable_discourse_gitlab_plugin

# Check configuration
SiteSetting.gitlab_base_url
SiteSetting.gitlab_linkback_enabled
SiteSetting.gitlab_badges_enabled
SiteSetting.gitlab_permalinks_enabled

# View tracked repositories
DiscourseGitlabPlugin::GitlabRepo.repos

# Count fetched commits
DiscourseGitlabPlugin::GitlabCommit.count

# View commits for specific repo
repo = DiscourseGitlabPlugin::GitlabRepo.find_by(name: "myorg/myproject")
repo.commits.count
repo.commits.where(role_id: 0).count  # Contributors (via MR)
repo.commits.where(role_id: 1).count  # Committers (direct)

Debugging Background Jobs

# Check job queue
Sidekiq::ScheduledSet.new.each { |job| puts "#{job.klass} - #{job.args}" }

# Check failed jobs
Sidekiq::RetrySet.new.each { |job| puts "#{job.klass} - #{job.error_message}" }

# Manually trigger linkback
post = Post.last
Jobs.enqueue(:create_gitlab_linkback, post_id: post.id)

# Manually trigger permalink replacement
Jobs.enqueue(:replace_gitlab_non_permalinks, post_id: post.id)

Support and Contributing

This plugin is maintained as part of the Discourse ecosystem. For issues, feature requests, or contributions:

  • Report issues: GitHub Issues
  • Community support: Discourse Meta
  • Contributing: Pull requests welcome! Please follow Discourse coding standards.

License

MIT License - see LICENSE file for details.

Credits

This plugin is adapted from the discourse-github plugin originally created by Robin Ward and Sam Saffron.

Changelog

Version 0.1 (2026-01-15)

  • Initial release
  • GitLab linkback feature (issues, MRs, commits)
  • GitLab badges feature (committers and contributors)
  • GitLab permalinks feature (branch to commit conversion)
  • Support for self-hosted GitLab instances
  • Enhanced MR detection using GitLab-specific API endpoints