Skip to content

Commit ccd3577

Browse files
Morlinestlunny
authored andcommitted
Fix repository search function (#2689)
* Fix and remove FIXME * Respect membership visibility * Fix/rewrite searchRepositoryByName function * Add unit tests * Add integration tests * Remove Searcher completely * Remove trailing space
1 parent af4a094 commit ccd3577

File tree

10 files changed

+140
-88
lines changed

10 files changed

+140
-88
lines changed

integrations/api_repo_test.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func TestAPISearchRepo(t *testing.T) {
5050

5151
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 15}).(*models.User)
5252
user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 16}).(*models.User)
53+
user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 18}).(*models.User)
5354
orgUser := models.AssertExistsAndLoadBean(t, &models.User{ID: 17}).(*models.User)
5455

5556
// Map of expected results, where key is user for login
@@ -85,17 +86,21 @@ func TestAPISearchRepo(t *testing.T) {
8586
user2: {count: 4, repoName: "big_test_"}},
8687
},
8788
{name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
88-
// FIXME: Should return 4 (all public repositories related to "another" user = owned + collaborative), now returns only public repositories directly owned by user
89-
nil: {count: 2},
90-
user: {count: 8, includesPrivate: true},
91-
// FIXME: Should return 4 (all public repositories related to "another" user = owned + collaborative), now returns only public repositories directly owned by user
92-
user2: {count: 2}},
89+
nil: {count: 4},
90+
user: {count: 8, includesPrivate: true},
91+
user2: {count: 4}},
9392
},
9493
{name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{
9594
nil: {count: 1},
9695
user: {count: 1},
9796
user2: {count: 2, includesPrivate: true}},
9897
},
98+
{name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user3.ID), expectedResults: expectedResults{
99+
nil: {count: 1},
100+
user: {count: 1},
101+
user2: {count: 1},
102+
user3: {count: 4, includesPrivate: true}},
103+
},
99104
{name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{
100105
nil: {count: 1, repoOwnerID: orgUser.ID},
101106
user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true},

models/fixtures/access.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,28 @@
3838
id: 7
3939
user_id: 15
4040
repo_id: 24
41-
mode: 4 # owner
41+
mode: 4 # owner
42+
43+
-
44+
id: 8
45+
user_id: 18
46+
repo_id: 23
47+
mode: 4 # owner
48+
49+
-
50+
id: 9
51+
user_id: 18
52+
repo_id: 24
53+
mode: 4 # owner
54+
55+
-
56+
id: 10
57+
user_id: 18
58+
repo_id: 22
59+
mode: 2 # write
60+
61+
-
62+
id: 11
63+
user_id: 18
64+
repo_id: 21
65+
mode: 2 # write

models/fixtures/org_user.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,11 @@
3737
is_public: true
3838
is_owner: true
3939
num_teams: 1
40+
41+
-
42+
id: 6
43+
uid: 18
44+
org_id: 17
45+
is_public: false
46+
is_owner: true
47+
num_teams: 1

models/fixtures/team.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,5 @@
4444
name: Owners
4545
authorize: 4 # owner
4646
num_repos: 2
47-
num_members: 1
48-
unit_types: '[1,2,3,4,5,6,7]'
47+
num_members: 2
48+
unit_types: '[1,2,3,4,5,6,7]'

models/fixtures/team_user.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@
3232
id: 6
3333
org_id: 17
3434
team_id: 5
35-
uid: 15
35+
uid: 15
36+
37+
-
38+
id: 7
39+
org_id: 17
40+
team_id: 5
41+
uid: 18

models/fixtures/user.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,20 @@
263263
avatar_email: [email protected]
264264
num_repos: 2
265265
is_active: true
266-
num_members: 1
267-
num_teams: 1
266+
num_members: 2
267+
num_teams: 1
268+
269+
-
270+
id: 18
271+
lower_name: user18
272+
name: user18
273+
full_name: User 18
274+
275+
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
276+
type: 0 # individual
277+
salt: ZogKvWdyEx
278+
is_admin: false
279+
avatar: avatar18
280+
avatar_email: [email protected]
281+
num_repos: 0
282+
is_active: true

models/repo_list.go

Lines changed: 38 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ type SearchRepoOptions struct {
9898
//
9999
// in: query
100100
OwnerID int64 `json:"uid"`
101-
Searcher *User `json:"-"` //ID of the person who's seeking
102101
OrderBy SearchOrderBy `json:"-"`
103102
Private bool `json:"-"` // Include private repositories in results
104103
Collaborate bool `json:"-"` // Include collaborative repositories
@@ -136,57 +135,48 @@ const (
136135

137136
// SearchRepositoryByName takes keyword and part of repository name to search,
138137
// it returns results in given range and number of total results.
139-
func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, count int64, err error) {
140-
var cond = builder.NewCond()
138+
func SearchRepositoryByName(opts *SearchRepoOptions) (RepositoryList, int64, error) {
141139
if opts.Page <= 0 {
142140
opts.Page = 1
143141
}
144142

145-
var starJoin bool
146-
if opts.Starred && opts.OwnerID > 0 {
147-
cond = builder.Eq{
148-
"star.uid": opts.OwnerID,
149-
}
150-
starJoin = true
151-
}
152-
153-
// Append conditions
154-
if !opts.Starred && opts.OwnerID > 0 {
155-
var searcherReposCond builder.Cond = builder.Eq{"owner_id": opts.OwnerID}
156-
if opts.Searcher != nil {
157-
var ownerIds []int64
158-
159-
ownerIds = append(ownerIds, opts.Searcher.ID)
160-
err = opts.Searcher.GetOrganizations(true)
143+
var cond = builder.NewCond()
161144

162-
if err != nil {
163-
return nil, 0, fmt.Errorf("Organization: %v", err)
164-
}
145+
if !opts.Private {
146+
cond = cond.And(builder.Eq{"is_private": false})
147+
}
165148

166-
for _, org := range opts.Searcher.Orgs {
167-
ownerIds = append(ownerIds, org.ID)
149+
starred := false
150+
if opts.OwnerID > 0 {
151+
if opts.Starred {
152+
starred = true
153+
cond = builder.Eq{
154+
"star.uid": opts.OwnerID,
168155
}
156+
} else {
157+
var accessCond builder.Cond = builder.Eq{"owner_id": opts.OwnerID}
169158

170-
searcherReposCond = searcherReposCond.Or(builder.In("owner_id", ownerIds))
171159
if opts.Collaborate {
172-
searcherReposCond = searcherReposCond.Or(builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ? AND owner_id != ?)",
173-
opts.Searcher.ID, opts.Searcher.ID))
160+
collaborateCond := builder.And(
161+
builder.Expr("id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
162+
builder.Neq{"owner_id": opts.OwnerID})
163+
if !opts.Private {
164+
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
165+
}
166+
167+
accessCond = accessCond.Or(collaborateCond)
174168
}
175-
}
176-
cond = cond.And(searcherReposCond)
177-
}
178169

179-
if !opts.Private {
180-
cond = cond.And(builder.Eq{"is_private": false})
170+
cond = cond.And(accessCond)
171+
}
181172
}
182173

183174
if opts.OwnerID > 0 && opts.AllPublic {
184175
cond = cond.Or(builder.Eq{"is_private": false})
185176
}
186177

187-
opts.Keyword = strings.ToLower(opts.Keyword)
188178
if opts.Keyword != "" {
189-
cond = cond.And(builder.Like{"lower_name", opts.Keyword})
179+
cond = cond.And(builder.Like{"lower_name", strings.ToLower(opts.Keyword)})
190180
}
191181

192182
if len(opts.OrderBy) == 0 {
@@ -196,26 +186,23 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, coun
196186
sess := x.NewSession()
197187
defer sess.Close()
198188

199-
if starJoin {
200-
count, err = sess.
201-
Join("INNER", "star", "star.repo_id = repository.id").
202-
Where(cond).
203-
Count(new(Repository))
204-
if err != nil {
205-
return nil, 0, fmt.Errorf("Count: %v", err)
206-
}
189+
if starred {
190+
sess.Join("INNER", "star", "star.repo_id = repository.id")
191+
}
207192

193+
count, err := sess.
194+
Where(cond).
195+
Count(new(Repository))
196+
if err != nil {
197+
return nil, 0, fmt.Errorf("Count: %v", err)
198+
}
199+
200+
// Set again after reset by Count()
201+
if starred {
208202
sess.Join("INNER", "star", "star.repo_id = repository.id")
209-
} else {
210-
count, err = sess.
211-
Where(cond).
212-
Count(new(Repository))
213-
if err != nil {
214-
return nil, 0, fmt.Errorf("Count: %v", err)
215-
}
216203
}
217204

218-
repos = make([]*Repository, 0, opts.PageSize)
205+
repos := make(RepositoryList, 0, opts.PageSize)
219206
if err = sess.
220207
Where(cond).
221208
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
@@ -230,5 +217,5 @@ func SearchRepositoryByName(opts *SearchRepoOptions) (repos RepositoryList, coun
230217
}
231218
}
232219

233-
return
220+
return repos, count, nil
234221
}

models/repo_list_test.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ func TestSearchRepositoryByName(t *testing.T) {
4242
Page: 1,
4343
PageSize: 10,
4444
Private: true,
45-
Searcher: &User{ID: 14},
4645
})
4746

4847
assert.NoError(t, err)
@@ -56,7 +55,6 @@ func TestSearchRepositoryByName(t *testing.T) {
5655
Page: 1,
5756
PageSize: 10,
5857
Private: true,
59-
Searcher: &User{ID: 14},
6058
})
6159

6260
assert.NoError(t, err)
@@ -82,16 +80,28 @@ func TestSearchRepositoryByName(t *testing.T) {
8280
count: 8},
8381
{name: "PublicRepositoriesOfUser",
8482
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15},
85-
count: 3}, // FIXME: Should return 2 (only directly owned repositories), now includes 1 public repository from owned organization
83+
count: 2},
84+
{name: "PublicRepositoriesOfUser2",
85+
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18},
86+
count: 0},
8687
{name: "PublicAndPrivateRepositoriesOfUser",
8788
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true},
88-
count: 6}, // FIXME: Should return 4 (only directly owned repositories), now includes 2 repositories from owned organization
89+
count: 4},
90+
{name: "PublicAndPrivateRepositoriesOfUser2",
91+
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true},
92+
count: 0},
8993
{name: "PublicRepositoriesOfUserIncludingCollaborative",
9094
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Collaborate: true},
9195
count: 4},
96+
{name: "PublicRepositoriesOfUser2IncludingCollaborative",
97+
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Collaborate: true},
98+
count: 1},
9299
{name: "PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
93100
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: true},
94101
count: 8},
102+
{name: "PublicAndPrivateRepositoriesOfUser2IncludingCollaborative",
103+
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 18, Private: true, Collaborate: true},
104+
count: 4},
95105
{name: "PublicRepositoriesOfOrganization",
96106
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17},
97107
count: 1},
@@ -113,16 +123,16 @@ func TestSearchRepositoryByName(t *testing.T) {
113123
{name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborativeByName",
114124
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 15, Private: true, Collaborate: true, AllPublic: true},
115125
count: 10},
126+
{name: "AllPublic/PublicAndPrivateRepositoriesOfUser2IncludingCollaborativeByName",
127+
opts: &SearchRepoOptions{Keyword: "test", Page: 1, PageSize: 10, OwnerID: 18, Private: true, Collaborate: true, AllPublic: true},
128+
count: 8},
116129
{name: "AllPublic/PublicRepositoriesOfOrganization",
117130
opts: &SearchRepoOptions{Page: 1, PageSize: 10, OwnerID: 17, AllPublic: true},
118131
count: 12},
119132
}
120133

121134
for _, testCase := range testCases {
122135
t.Run(testCase.name, func(t *testing.T) {
123-
if testCase.opts.OwnerID > 0 {
124-
testCase.opts.Searcher = &User{ID: testCase.opts.OwnerID}
125-
}
126136
repos, count, err := SearchRepositoryByName(testCase.opts)
127137

128138
assert.NoError(t, err)
@@ -143,10 +153,9 @@ func TestSearchRepositoryByName(t *testing.T) {
143153
assert.Contains(t, repo.Name, testCase.opts.Keyword)
144154
}
145155

146-
// FIXME: Can't check, need to fix current behaviour (see previous FIXME comments in test cases)
147-
/*if testCase.opts.OwnerID > 0 && !testCase.opts.Collaborate && !AllPublic {
156+
if testCase.opts.OwnerID > 0 && !testCase.opts.Collaborate && !testCase.opts.AllPublic {
148157
assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
149-
}*/
158+
}
150159

151160
if !testCase.opts.Private {
152161
assert.False(t, repo.IsPrivate)

routers/api/v1/repo/repo.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,30 @@ func Search(ctx *context.APIContext) {
3434
OwnerID: ctx.QueryInt64("uid"),
3535
PageSize: convert.ToCorrectPageSize(ctx.QueryInt("limit")),
3636
}
37-
if ctx.User != nil && ctx.User.ID == opts.OwnerID {
38-
opts.Searcher = ctx.User
39-
}
4037

41-
// Check visibility.
42-
if ctx.IsSigned && opts.OwnerID > 0 {
43-
if ctx.User.ID == opts.OwnerID {
44-
opts.Private = true
45-
opts.Collaborate = true
38+
if opts.OwnerID > 0 {
39+
var repoOwner *models.User
40+
if ctx.User != nil && ctx.User.ID == opts.OwnerID {
41+
repoOwner = ctx.User
4642
} else {
47-
u, err := models.GetUserByID(opts.OwnerID)
43+
var err error
44+
repoOwner, err = models.GetUserByID(opts.OwnerID)
4845
if err != nil {
4946
ctx.JSON(500, api.SearchError{
5047
OK: false,
5148
Error: err.Error(),
5249
})
5350
return
5451
}
55-
if u.IsOrganization() && u.IsOwnedBy(ctx.User.ID) {
56-
opts.Private = true
57-
}
52+
}
5853

59-
if !u.IsOrganization() {
60-
opts.Collaborate = true
61-
}
54+
if !repoOwner.IsOrganization() {
55+
opts.Collaborate = true
56+
}
57+
58+
// Check visibility.
59+
if ctx.IsSigned && (ctx.User.ID == repoOwner.ID || (repoOwner.IsOrganization() && repoOwner.IsOwnedBy(ctx.User.ID))) {
60+
opts.Private = true
6261
}
6362
}
6463

routers/home.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
120120
Private: opts.Private,
121121
Keyword: keyword,
122122
OwnerID: opts.OwnerID,
123-
Searcher: ctx.User,
124123
Collaborate: true,
125124
AllPublic: true,
126125
})

0 commit comments

Comments
 (0)