Skip to content

Commit 88b8fe5

Browse files
authored
Merge branch 'main' into lunny/refactor_feed
2 parents cc1d86e + 713364f commit 88b8fe5

File tree

21 files changed

+286
-173
lines changed

21 files changed

+286
-173
lines changed

MAINTAINERS

+1
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ Tim-Niclas Oelschläger <[email protected]> (@zokkis)
6363
Yu Liu <[email protected]> (@HEREYUA)
6464
Kemal Zebari <[email protected]> (@kemzeb)
6565
Rowan Bohde <[email protected]> (@bohde)
66+
hiifong <[email protected]> (@hiifong)

custom/conf/app.example.ini

+14
Original file line numberDiff line numberDiff line change
@@ -1944,6 +1944,13 @@ LEVEL = Info
19441944
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
19451945
;MINIO_SECRET_ACCESS_KEY =
19461946
;;
1947+
;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.
1948+
;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables
1949+
;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,
1950+
;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),
1951+
;; or the DefaultIAMRoleEndpoint if not provided otherwise.
1952+
;MINIO_IAM_ENDPOINT =
1953+
;;
19471954
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
19481955
;MINIO_BUCKET = gitea
19491956
;;
@@ -2688,6 +2695,13 @@ LEVEL = Info
26882695
;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio`
26892696
;MINIO_SECRET_ACCESS_KEY =
26902697
;;
2698+
;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.
2699+
;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables
2700+
;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,
2701+
;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),
2702+
;; or the DefaultIAMRoleEndpoint if not provided otherwise.
2703+
;MINIO_IAM_ENDPOINT =
2704+
;;
26912705
;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio`
26922706
;MINIO_BUCKET = gitea
26932707
;;

models/organization/team_repo.go

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"code.gitea.io/gitea/models/db"
1010
"code.gitea.io/gitea/models/perm"
1111
repo_model "code.gitea.io/gitea/models/repo"
12+
"code.gitea.io/gitea/models/unit"
1213

1314
"xorm.io/builder"
1415
)
@@ -83,3 +84,16 @@ func GetTeamsWithAccessToRepo(ctx context.Context, orgID, repoID int64, mode per
8384
OrderBy("name").
8485
Find(&teams)
8586
}
87+
88+
// GetTeamsWithAccessToRepoUnit returns all teams in an organization that have given access level to the repository special unit.
89+
func GetTeamsWithAccessToRepoUnit(ctx context.Context, orgID, repoID int64, mode perm.AccessMode, unitType unit.Type) ([]*Team, error) {
90+
teams := make([]*Team, 0, 5)
91+
return teams, db.GetEngine(ctx).Where("team_unit.access_mode >= ?", mode).
92+
Join("INNER", "team_repo", "team_repo.team_id = team.id").
93+
Join("INNER", "team_unit", "team_unit.team_id = team.id").
94+
And("team_repo.org_id = ?", orgID).
95+
And("team_repo.repo_id = ?", repoID).
96+
And("team_unit.type = ?", unitType).
97+
OrderBy("name").
98+
Find(&teams)
99+
}

models/organization/team_repo_test.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package organization_test
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/models/db"
10+
"code.gitea.io/gitea/models/organization"
11+
"code.gitea.io/gitea/models/perm"
12+
"code.gitea.io/gitea/models/repo"
13+
"code.gitea.io/gitea/models/unit"
14+
"code.gitea.io/gitea/models/unittest"
15+
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestGetTeamsWithAccessToRepoUnit(t *testing.T) {
20+
assert.NoError(t, unittest.PrepareTestDatabase())
21+
22+
org41 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 41})
23+
repo61 := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 61})
24+
25+
teams, err := organization.GetTeamsWithAccessToRepoUnit(db.DefaultContext, org41.ID, repo61.ID, perm.AccessModeRead, unit.TypePullRequests)
26+
assert.NoError(t, err)
27+
if assert.Len(t, teams, 2) {
28+
assert.EqualValues(t, 21, teams[0].ID)
29+
assert.EqualValues(t, 22, teams[1].ID)
30+
}
31+
}

models/repo/user_repo.go

-52
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"code.gitea.io/gitea/models/unit"
1212
user_model "code.gitea.io/gitea/models/user"
1313
"code.gitea.io/gitea/modules/container"
14-
api "code.gitea.io/gitea/modules/structs"
1514

1615
"xorm.io/builder"
1716
)
@@ -146,57 +145,6 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
146145
return users, nil
147146
}
148147

149-
// GetReviewers get all users can be requested to review:
150-
// * for private repositories this returns all users that have read access or higher to the repository.
151-
// * for public repositories this returns all users that have read access or higher to the repository,
152-
// all repo watchers and all organization members.
153-
// TODO: may be we should have a busy choice for users to block review request to them.
154-
func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) ([]*user_model.User, error) {
155-
// Get the owner of the repository - this often already pre-cached and if so saves complexity for the following queries
156-
if err := repo.LoadOwner(ctx); err != nil {
157-
return nil, err
158-
}
159-
160-
cond := builder.And(builder.Neq{"`user`.id": posterID}).
161-
And(builder.Eq{"`user`.is_active": true})
162-
163-
if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
164-
// This a private repository:
165-
// Anyone who can read the repository is a requestable reviewer
166-
167-
cond = cond.And(builder.In("`user`.id",
168-
builder.Select("user_id").From("access").Where(
169-
builder.Eq{"repo_id": repo.ID}.
170-
And(builder.Gte{"mode": perm.AccessModeRead}),
171-
),
172-
))
173-
174-
if repo.Owner.Type == user_model.UserTypeIndividual && repo.Owner.ID != posterID {
175-
// as private *user* repos don't generate an entry in the `access` table,
176-
// the owner of a private repo needs to be explicitly added.
177-
cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID})
178-
}
179-
} else {
180-
// This is a "public" repository:
181-
// Any user that has read access, is a watcher or organization member can be requested to review
182-
cond = cond.And(builder.And(builder.In("`user`.id",
183-
builder.Select("user_id").From("access").
184-
Where(builder.Eq{"repo_id": repo.ID}.
185-
And(builder.Gte{"mode": perm.AccessModeRead})),
186-
).Or(builder.In("`user`.id",
187-
builder.Select("user_id").From("watch").
188-
Where(builder.Eq{"repo_id": repo.ID}.
189-
And(builder.In("mode", WatchModeNormal, WatchModeAuto))),
190-
).Or(builder.In("`user`.id",
191-
builder.Select("uid").From("org_user").
192-
Where(builder.Eq{"org_id": repo.OwnerID}),
193-
)))))
194-
}
195-
196-
users := make([]*user_model.User, 0, 8)
197-
return users, db.GetEngine(ctx).Where(cond).OrderBy(user_model.GetOrderByName()).Find(&users)
198-
}
199-
200148
// GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository
201149
// If isShowFullName is set to true, also include full name prefix search
202150
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {

models/repo/user_repo_test.go

-43
Original file line numberDiff line numberDiff line change
@@ -38,46 +38,3 @@ func TestRepoAssignees(t *testing.T) {
3838
assert.NotContains(t, []int64{users[0].ID, users[1].ID, users[2].ID}, 15)
3939
}
4040
}
41-
42-
func TestRepoGetReviewers(t *testing.T) {
43-
assert.NoError(t, unittest.PrepareTestDatabase())
44-
45-
// test public repo
46-
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
47-
48-
ctx := db.DefaultContext
49-
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
50-
assert.NoError(t, err)
51-
if assert.Len(t, reviewers, 3) {
52-
assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
53-
}
54-
55-
// should include doer if doer is not PR poster.
56-
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
57-
assert.NoError(t, err)
58-
assert.Len(t, reviewers, 3)
59-
60-
// should not include PR poster, if PR poster would be otherwise eligible
61-
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
62-
assert.NoError(t, err)
63-
assert.Len(t, reviewers, 2)
64-
65-
// test private user repo
66-
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
67-
68-
reviewers, err = repo_model.GetReviewers(ctx, repo2, 2, 4)
69-
assert.NoError(t, err)
70-
assert.Len(t, reviewers, 1)
71-
assert.EqualValues(t, reviewers[0].ID, 2)
72-
73-
// test private org repo
74-
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
75-
76-
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 1)
77-
assert.NoError(t, err)
78-
assert.Len(t, reviewers, 2)
79-
80-
reviewers, err = repo_model.GetReviewers(ctx, repo3, 2, 2)
81-
assert.NoError(t, err)
82-
assert.Len(t, reviewers, 1)
83-
}

modules/setting/storage.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type MinioStorageConfig struct {
4343
Endpoint string `ini:"MINIO_ENDPOINT" json:",omitempty"`
4444
AccessKeyID string `ini:"MINIO_ACCESS_KEY_ID" json:",omitempty"`
4545
SecretAccessKey string `ini:"MINIO_SECRET_ACCESS_KEY" json:",omitempty"`
46+
IamEndpoint string `ini:"MINIO_IAM_ENDPOINT" json:",omitempty"`
4647
Bucket string `ini:"MINIO_BUCKET" json:",omitempty"`
4748
Location string `ini:"MINIO_LOCATION" json:",omitempty"`
4849
BasePath string `ini:"MINIO_BASE_PATH" json:",omitempty"`

modules/setting/storage_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,19 @@ MINIO_BASE_PATH = /prefix
470470
cfg, err = NewConfigProviderFromData(`
471471
[storage]
472472
STORAGE_TYPE = minio
473+
MINIO_IAM_ENDPOINT = 127.0.0.1
474+
MINIO_USE_SSL = true
475+
MINIO_BASE_PATH = /prefix
476+
`)
477+
assert.NoError(t, err)
478+
assert.NoError(t, loadRepoArchiveFrom(cfg))
479+
assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint)
480+
assert.EqualValues(t, true, RepoArchive.Storage.MinioConfig.UseSSL)
481+
assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath)
482+
483+
cfg, err = NewConfigProviderFromData(`
484+
[storage]
485+
STORAGE_TYPE = minio
473486
MINIO_ACCESS_KEY_ID = my_access_key
474487
MINIO_SECRET_ACCESS_KEY = my_secret_key
475488
MINIO_USE_SSL = true

modules/storage/minio.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
9797
}
9898

9999
minioClient, err := minio.New(config.Endpoint, &minio.Options{
100-
Creds: buildMinioCredentials(config, credentials.DefaultIAMRoleEndpoint),
100+
Creds: buildMinioCredentials(config),
101101
Secure: config.UseSSL,
102102
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}},
103103
Region: config.Location,
@@ -164,7 +164,7 @@ func (m *MinioStorage) buildMinioDirPrefix(p string) string {
164164
return p
165165
}
166166

167-
func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string) *credentials.Credentials {
167+
func buildMinioCredentials(config setting.MinioStorageConfig) *credentials.Credentials {
168168
// If static credentials are provided, use those
169169
if config.AccessKeyID != "" {
170170
return credentials.NewStaticV4(config.AccessKeyID, config.SecretAccessKey, "")
@@ -184,7 +184,9 @@ func buildMinioCredentials(config setting.MinioStorageConfig, iamEndpoint string
184184
&credentials.FileAWSCredentials{},
185185
// read IAM role from EC2 metadata endpoint if available
186186
&credentials.IAM{
187-
Endpoint: iamEndpoint,
187+
// passing in an empty Endpoint lets the IAM Provider
188+
// decide which endpoint to resolve internally
189+
Endpoint: config.IamEndpoint,
188190
Client: &http.Client{
189191
Transport: http.DefaultTransport,
190192
},

modules/storage/minio_test.go

+13-8
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,9 @@ func TestMinioCredentials(t *testing.T) {
107107
cfg := setting.MinioStorageConfig{
108108
AccessKeyID: ExpectedAccessKey,
109109
SecretAccessKey: ExpectedSecretAccessKey,
110+
IamEndpoint: FakeEndpoint,
110111
}
111-
creds := buildMinioCredentials(cfg, FakeEndpoint)
112+
creds := buildMinioCredentials(cfg)
112113
v, err := creds.Get()
113114

114115
assert.NoError(t, err)
@@ -117,13 +118,15 @@ func TestMinioCredentials(t *testing.T) {
117118
})
118119

119120
t.Run("Chain", func(t *testing.T) {
120-
cfg := setting.MinioStorageConfig{}
121+
cfg := setting.MinioStorageConfig{
122+
IamEndpoint: FakeEndpoint,
123+
}
121124

122125
t.Run("EnvMinio", func(t *testing.T) {
123126
t.Setenv("MINIO_ACCESS_KEY", ExpectedAccessKey+"Minio")
124127
t.Setenv("MINIO_SECRET_KEY", ExpectedSecretAccessKey+"Minio")
125128

126-
creds := buildMinioCredentials(cfg, FakeEndpoint)
129+
creds := buildMinioCredentials(cfg)
127130
v, err := creds.Get()
128131

129132
assert.NoError(t, err)
@@ -135,7 +138,7 @@ func TestMinioCredentials(t *testing.T) {
135138
t.Setenv("AWS_ACCESS_KEY", ExpectedAccessKey+"AWS")
136139
t.Setenv("AWS_SECRET_KEY", ExpectedSecretAccessKey+"AWS")
137140

138-
creds := buildMinioCredentials(cfg, FakeEndpoint)
141+
creds := buildMinioCredentials(cfg)
139142
v, err := creds.Get()
140143

141144
assert.NoError(t, err)
@@ -144,11 +147,11 @@ func TestMinioCredentials(t *testing.T) {
144147
})
145148

146149
t.Run("FileMinio", func(t *testing.T) {
147-
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
148150
// prevent loading any actual credentials files from the user
151+
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/minio.json")
149152
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/fake")
150153

151-
creds := buildMinioCredentials(cfg, FakeEndpoint)
154+
creds := buildMinioCredentials(cfg)
152155
v, err := creds.Get()
153156

154157
assert.NoError(t, err)
@@ -161,7 +164,7 @@ func TestMinioCredentials(t *testing.T) {
161164
t.Setenv("MINIO_SHARED_CREDENTIALS_FILE", "testdata/fake.json")
162165
t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/aws_credentials")
163166

164-
creds := buildMinioCredentials(cfg, FakeEndpoint)
167+
creds := buildMinioCredentials(cfg)
165168
v, err := creds.Get()
166169

167170
assert.NoError(t, err)
@@ -187,7 +190,9 @@ func TestMinioCredentials(t *testing.T) {
187190
defer server.Close()
188191

189192
// Use the provided EC2 Instance Metadata server
190-
creds := buildMinioCredentials(cfg, server.URL)
193+
creds := buildMinioCredentials(setting.MinioStorageConfig{
194+
IamEndpoint: server.URL,
195+
})
191196
v, err := creds.Get()
192197

193198
assert.NoError(t, err)

routers/api/v1/repo/collaborators.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"code.gitea.io/gitea/routers/api/v1/utils"
1818
"code.gitea.io/gitea/services/context"
1919
"code.gitea.io/gitea/services/convert"
20+
issue_service "code.gitea.io/gitea/services/issue"
21+
pull_service "code.gitea.io/gitea/services/pull"
2022
repo_service "code.gitea.io/gitea/services/repository"
2123
)
2224

@@ -320,7 +322,13 @@ func GetReviewers(ctx *context.APIContext) {
320322
// "404":
321323
// "$ref": "#/responses/notFound"
322324

323-
reviewers, err := repo_model.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0)
325+
canChooseReviewer := issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, ctx.Repo.Repository, 0)
326+
if !canChooseReviewer {
327+
ctx.Error(http.StatusForbidden, "GetReviewers", errors.New("doer has no permission to get reviewers"))
328+
return
329+
}
330+
331+
reviewers, err := pull_service.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0)
324332
if err != nil {
325333
ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
326334
return

routers/web/repo/issue_page_meta.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
shared_user "code.gitea.io/gitea/routers/web/shared/user"
2020
"code.gitea.io/gitea/services/context"
2121
issue_service "code.gitea.io/gitea/services/issue"
22-
repo_service "code.gitea.io/gitea/services/repository"
22+
pull_service "code.gitea.io/gitea/services/pull"
2323
)
2424

2525
type issueSidebarMilestoneData struct {
@@ -186,7 +186,7 @@ func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) {
186186
if d.Issue == nil {
187187
data.CanChooseReviewer = true
188188
} else {
189-
data.CanChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, d.Issue)
189+
data.CanChooseReviewer = issue_service.CanDoerChangeReviewRequests(ctx, ctx.Doer, repo, d.Issue.PosterID)
190190
}
191191
}
192192

@@ -231,13 +231,13 @@ func (d *IssuePageMetaData) retrieveReviewersData(ctx *context.Context) {
231231

232232
if data.CanChooseReviewer {
233233
var err error
234-
reviewers, err = repo_model.GetReviewers(ctx, repo, ctx.Doer.ID, posterID)
234+
reviewers, err = pull_service.GetReviewers(ctx, repo, ctx.Doer.ID, posterID)
235235
if err != nil {
236236
ctx.ServerError("GetReviewers", err)
237237
return
238238
}
239239

240-
teamReviewers, err = repo_service.GetReviewerTeams(ctx, repo)
240+
teamReviewers, err = pull_service.GetReviewerTeams(ctx, repo)
241241
if err != nil {
242242
ctx.ServerError("GetReviewerTeams", err)
243243
return

routers/web/repo/repo.go

+3
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ func Action(ctx *context.Context) {
352352
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
353353
}
354354

355+
// see the `hx-trigger="refreshUserCards ..."` comments in tmpl
356+
ctx.RespHeader().Add("hx-trigger", "refreshUserCards")
357+
355358
switch ctx.PathParam(":action") {
356359
case "watch", "unwatch", "star", "unstar":
357360
// we have to reload the repository because NumStars or NumWatching (used in the templates) has just changed

0 commit comments

Comments
 (0)