Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
764b42f
BLENDER: Blender ID goth provider
brechtvl May 10, 2024
cfba2b4
BLENDER: Show warning for target branch switching
brechtvl May 10, 2024
f8d6ce7
BLENDER: Workaround LFS files not being available in pull requests
brechtvl May 10, 2024
36e447c
BLENDER: Don't allow assigning large teams as reviewers
brechtvl May 10, 2024
7612cd9
BLENDER: Support both exclusive and non-exclusive scope for labels
brechtvl May 10, 2024
b3ccfaa
BLENDER: Projects: button to show/hide issue details and closed issue
brechtvl May 10, 2024
fdbd7c0
BLENDER: Remember login for OAuth / Blender ID
brechtvl May 10, 2024
6fa471e
BLENDER: Always login with Blender ID, unless noredirect is specified
brechtvl May 10, 2024
9932e63
BLENDER: Allow non-local users to be renamed
brechtvl May 10, 2024
84234e3
BLENDER: Workaround internal server error comparing branches on some …
brechtvl May 10, 2024
951585a
BLENDER: Add docs explaining merge strategy
brechtvl May 10, 2024
abc65f4
BLENDER: Add Python for external renderering
bartvdbraak Dec 4, 2024
449ab8d
BLENDER: Fix RemoveUserBadges incorrect sql
komarov Apr 2, 2025
d7c9c8b
BLENDER: Sync user badges on sign-in
komarov Apr 2, 2025
4b22609
BLENDER: Add Spam Reporting
komarov Apr 11, 2025
894daf8
BLENDER: Add Line Length indicator for cursor
bartvdbraak May 2, 2025
7a8eea6
BLENDER: Fine tune pages considered expensive
brechtvl Jun 12, 2025
c2027b9
BLENDER: Edit file workflow for creating a fork and proposing changes
brechtvl Jun 18, 2025
e913a62
BLENDER: Custom locales for changing password/username
bartvdbraak Jul 17, 2025
36bdc9c
BLENDER: Only hide dropzone when no files have been uploaded
bartvdbraak Jul 24, 2025
d944a16
BLENDER: contributor agreement support
komarov Sep 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions BLENDER_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Blender Merges

Currently the process for merging upstream changes is to rebase, and keep
Blender modifications on top. This keeps a clear overview of the modifications
that were made.

When merging a major new release, cherry-pick all the Blender commits on
top of it. A simple `git rebase` will not work because the release and main
branches diverge.

First do changes in `blender-merged-develop`, and deploy on uatest. Then apply
the changes in `blender-merged` and deploy in production.
5 changes: 5 additions & 0 deletions Dockerfile.rootless
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ RUN apk --no-cache add \
gnupg \
&& rm -rf /var/cache/apk/*

# External renderers
RUN apk --no-cache add \
python3-dev \
&& rm -rf /var/cache/apk/*

RUN addgroup \
-S -g 1000 \
git && \
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-unidecode v0.2.0 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mozillazg/go-unidecode v0.2.0 h1:vFGEzAH9KSwyWmXCOblazEWDh7fOkpmy/Z4ArmamSUc=
github.com/mozillazg/go-unidecode v0.2.0/go.mod h1:zB48+/Z5toiRolOZy9ksLryJ976VIwmDmpQ2quyt1aA=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM=
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
Expand Down
2 changes: 1 addition & 1 deletion models/git/lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func CountLFSMetaObjects(ctx context.Context, repoID int64) (int64, error) {

// LFSObjectAccessible checks if a provided Oid is accessible to the user
func LFSObjectAccessible(ctx context.Context, user *user_model.User, oid string) (bool, error) {
if user.IsAdmin {
if user != nil && user.IsAdmin {
count, err := db.GetEngine(ctx).Count(&LFSMetaObject{Pointer: lfs.Pointer{Oid: oid}})
return count > 0, err
}
Expand Down
15 changes: 10 additions & 5 deletions models/issues/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,18 +184,23 @@ func (l *Label) BelongsToRepo() bool {
return l.RepoID > 0
}

// ExclusiveScope returns scope substring of label name, or empty string if none exists
func (l *Label) ExclusiveScope() string {
if !l.Exclusive {
return ""
}
// Return scope substring of label name, or empty string if none exists
func (l *Label) Scope() string {
lastIndex := strings.LastIndex(l.Name, "/")
if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 {
return ""
}
return l.Name[:lastIndex]
}

// ExclusiveScope returns scope substring of label name, or empty string if none exists
func (l *Label) ExclusiveScope() string {
if !l.Exclusive {
return ""
}
return l.Scope()
}

// NewLabel creates a new label
func NewLabel(ctx context.Context, l *Label) error {
color, err := label.NormalizeColor(l.Color)
Expand Down
16 changes: 16 additions & 0 deletions models/organization/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,19 @@ func IncrTeamRepoNum(ctx context.Context, teamID int64) error {
_, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team))
return err
}

// Avoid notifying large teams accidentally
func FilterLargeTeams(teams []*Team, err error) ([]*Team, error) {
if err != nil {
return nil, err
}

var smallTeams []*Team
for _, team := range teams {
if team.NumMembers <= 10 {
smallTeams = append(smallTeams, team)
}
}

return smallTeams, nil
}
2 changes: 1 addition & 1 deletion models/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ func (repo *Repository) AllowsPulls(ctx context.Context) bool {

// CanEnableEditor returns true if repository meets the requirements of web editor.
func (repo *Repository) CanEnableEditor() bool {
return !repo.IsMirror
return !repo.IsMirror && !repo.IsArchived
}

// DescriptionHTML does special handles to description and return HTML string.
Expand Down
22 changes: 16 additions & 6 deletions models/user/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,23 @@ func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error {
// RemoveUserBadges removes badges from a user.
func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
return db.WithTx(ctx, func(ctx context.Context) error {
badgeSlugs := make([]string, 0, len(badges))
for _, badge := range badges {
if _, err := db.GetEngine(ctx).
Join("INNER", "badge", "badge.id = `user_badge`.badge_id").
Where("`user_badge`.user_id=? AND `badge`.slug=?", u.ID, badge.Slug).
Delete(&UserBadge{}); err != nil {
return err
}
badgeSlugs = append(badgeSlugs, badge.Slug)
}
var userBadges []UserBadge
if err := db.GetEngine(ctx).Table("user_badge").
Join("INNER", "badge", "badge.id = `user_badge`.badge_id").
Where("`user_badge`.user_id = ?", u.ID).In("`badge`.slug", badgeSlugs).
Find(&userBadges); err != nil {
return err
}
userBadgeIDs := make([]int64, 0, len(userBadges))
for _, ub := range userBadges {
userBadgeIDs = append(userBadgeIDs, ub.ID)
}
if _, err := db.GetEngine(ctx).Table("user_badge").In("id", userBadgeIDs).Delete(); err != nil {
return err
}
return nil
})
Expand Down
50 changes: 50 additions & 0 deletions models/user/contributor_agreement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

// BLENDER: contributor agreement

package user

import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"

"xorm.io/builder"
)

type ContributorAgreement struct {
ID int64 `xorm:"pk autoincr"`
Slug string `xorm:"UNIQUE"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
Content string `xorm:"TEXT NOT NULL"`
}

type SignedContributorAgreement struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s)"`
ContributorAgreementID int64 `xorm:"UNIQUE(s)"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
Comment string `xorm:"TEXT DEFAULT NULL"`
}

type FindSignedContributorAgreementsOptions struct {
db.ListOptions
UserID int64
}

func (opts *FindSignedContributorAgreementsOptions) ToConds() builder.Cond {
cond := builder.NewCond()
if opts.UserID != 0 {
cond = cond.And(builder.Eq{"user_id": opts.UserID})
}
return cond
}

func init() {
// These tables don't exist in the upstream code.
// We don't introduce migrations for it to avoid migration id clashes.
// Gitea will create the tables in the database during startup,
// so no manual action is required until we start modifying the tables.
db.RegisterModel(new(ContributorAgreement))
db.RegisterModel(new(SignedContributorAgreement))
}
136 changes: 136 additions & 0 deletions models/user/spamreport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

// BLENDER: spam reporting

package user

import (
"context"
"fmt"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
)

// SpamReportStatusType is used to support a spam report lifecycle:
//
// pending -> locked
// locked -> processed | dismissed
//
// "locked" status works as a lock for a record that is being processed.
type SpamReportStatusType int

const (
SpamReportStatusTypePending = iota // 0
SpamReportStatusTypeLocked // 1
SpamReportStatusTypeProcessed // 2
SpamReportStatusTypeDismissed // 3
)

func (t SpamReportStatusType) String() string {
switch t {
case SpamReportStatusTypePending:
return "pending"
case SpamReportStatusTypeLocked:
return "locked"
case SpamReportStatusTypeProcessed:
return "processed"
case SpamReportStatusTypeDismissed:
return "dismissed"
}
return "unknown"
}

type SpamReport struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE"`
ReporterID int64 `xorm:"NOT NULL"`
Status SpamReportStatusType `xorm:"INDEX NOT NULL DEFAULT 0"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}

func (*SpamReport) TableName() string {
return "user_spamreport"
}

func init() {
// This table doesn't exist in the upstream code.
// We don't introduce migrations for it to avoid migration id clashes.
// Gitea will create the table in the database during startup,
// so no manual action is required until we start modifying the table.
db.RegisterModel(new(SpamReport))
}

type ListSpamReportsOptions struct {
db.ListOptions
Status SpamReportStatusType
}

type ListSpamReportsResults struct {
ID int64
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
Status SpamReportStatusType
UserName string
UserCreatedUnix timeutil.TimeStamp
ReporterName string
}

func ListSpamReports(ctx context.Context, opts *ListSpamReportsOptions) ([]*ListSpamReportsResults, int64, error) {
opts.SetDefaultValues()
count, err := db.GetEngine(ctx).Count(new(SpamReport))
if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err)
}
spamReports := make([]*ListSpamReportsResults, 0, opts.PageSize)
err = db.GetEngine(ctx).Table("user_spamreport").Select(
"user_spamreport.id, "+
"user_spamreport.created_unix, "+
"user_spamreport.updated_unix, "+
"user_spamreport.status, "+
"`user`.name as user_name, "+
"`user`.created_unix as user_created_unix, "+
"reporter.name as reporter_name",
).
Join("LEFT", "`user`", "`user`.id = user_spamreport.user_id").
Join("LEFT", "`user` as reporter", "`reporter`.id = user_spamreport.reporter_id").
Where("status = ?", opts.Status).
OrderBy("user_spamreport.id").
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
Find(&spamReports)

return spamReports, count, err
}

func GetPendingSpamReportIDs(ctx context.Context) ([]int64, error) {
var ids []int64
err := db.GetEngine(ctx).Table("user_spamreport").
Select("id").Where("status = ?", SpamReportStatusTypePending).Find(&ids)
return ids, err
}

type SpamReportStatusCounts struct {
Count int64
Status SpamReportStatusType
}

func GetSpamReportStatusCounts(ctx context.Context) ([]*SpamReportStatusCounts, error) {
statusCounts := make([]*SpamReportStatusCounts, 0, 4) // 4 status types
err := db.GetEngine(ctx).Table("user_spamreport").
Select("count(*) as count, status").
GroupBy("status").
Find(&statusCounts)

return statusCounts, err
}

func GetSpamReportForUser(ctx context.Context, user *User) (*SpamReport, error) {
spamReport := &SpamReport{}
has, err := db.GetEngine(ctx).Where("user_id = ?", user.ID).Get(spamReport)
if has {
return spamReport, err
}
return nil, err
}
5 changes: 4 additions & 1 deletion modules/git/repo_compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ func (repo *Repository) GetMergeBase(tmpRemote, base, head string) (string, stri
if tmpRemote != "origin" {
tmpBaseName := RemotePrefix + tmpRemote + "/tmp_" + base
// Fetch commit into a temporary branch in order to be able to handle commits and tags
_, _, err := NewCommand("fetch", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
// --no-write-commit-graph works around issue with commit-graph-chain.lock files that should not be there.
_, _, err := NewCommand("fetch", "--no-write-commit-graph", "--no-tags").AddDynamicArguments(tmpRemote).AddDashesAndList(base+":"+tmpBaseName).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
if err == nil {
base = tmpBaseName
} else {
logger.Trace("GetMergeBase failed to git fetch. Error: %v", err)
}
}

Expand Down
2 changes: 1 addition & 1 deletion modules/templates/util_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML {
locale := ut.ctx.Value(translation.ContextKey).(translation.Locale)
var extraCSSClasses string
textColor := util.ContrastColor(label.Color)
labelScope := label.ExclusiveScope()
labelScope := label.Scope()
descriptionText := emoji.ReplaceAliases(label.Description)

if label.IsArchived() {
Expand Down
4 changes: 2 additions & 2 deletions options/locale/locale_cs-CZ.ini
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ invalid_password=Vaše heslo se neshoduje s heslem, které bylo použito k vytvo
reset_password_helper=Obnovit účet
reset_password_wrong_user=Jste přihlášen/a jako %s, ale odkaz pro obnovení účtu je pro %s
password_too_short=Délka hesla musí být minimálně %d znaků.
non_local_account=Externě ověřovaní uživatelé nemohou aktualizovat své heslo prostřednictvím webového rozhraní Gitea.
non_local_account = You can manage your Blender ID password <a href="https://id.blender.org/settings/profile">here</a>.
verify=Ověřit
scratch_code=Pomocný kód
use_scratch_code=Použijte pomocný kód
Expand Down Expand Up @@ -775,7 +775,7 @@ new_password=Nové heslo
retype_new_password=Potvrdit nové heslo
password_incorrect=Zadané heslo není správné.
change_password_success=Vaše heslo bylo aktualizováno. Od teď se přihlašujte novým heslem.
password_change_disabled=Externě ověřovaní uživatelé nemohou aktualizovat své heslo prostřednictvím webového rozhraní Gitea.
password_change_disabled = You can manage your Blender ID password <a href="https://id.blender.org/settings/profile">here</a>.

emails=E-mailová adresa
manage_emails=Správa e-mailových adres
Expand Down
4 changes: 2 additions & 2 deletions options/locale/locale_de-DE.ini
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ invalid_password=Ihr Passwort stimmt nicht mit dem Passwort überein, das zur Er
reset_password_helper=Konto wiederherstellen
reset_password_wrong_user=Du bist angemeldet als %s, aber der Link zur Kontowiederherstellung ist für %s
password_too_short=Das Passwort muss mindestens %d Zeichen lang sein.
non_local_account=Benutzer, die nicht von Gitea verwaltet werden können ihre Passwörter nicht über das Web Interface ändern.
non_local_account = You can manage your Blender ID password <a href="https://id.blender.org/settings/profile">here</a>.
verify=Verifizieren
scratch_code=Einmalpasswort
use_scratch_code=Einmalpasswort verwenden
Expand Down Expand Up @@ -781,7 +781,7 @@ new_password=Neues Passwort
retype_new_password=Neues Passwort bestätigen
password_incorrect=Das aktuelle Passwort ist falsch.
change_password_success=Dein Passwort wurde aktualisiert. Bitte verwende dieses beim nächsten Einloggen.
password_change_disabled=Benutzer, die nicht von Gitea verwaltet werden, können ihr Passwort im Web-Interface nicht ändern.
password_change_disabled = You can manage your Blender ID password <a href="https://id.blender.org/settings/profile">here</a>.

emails=E-Mail-Adressen
manage_emails=E-Mail-Adressen verwalten
Expand Down
Loading