Skip to content

Commit 0387195

Browse files
changchaishiBen Changwxiaoguang
authored
[Feature] Private README.md for organization (#32872)
Implemented #29503 --------- Co-authored-by: Ben Chang <ben_chang@htc.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent c09656e commit 0387195

File tree

27 files changed

+484
-149
lines changed

27 files changed

+484
-149
lines changed

modules/gitrepo/gitrepo.go

+9-8
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,20 @@ type contextKey struct {
4343
}
4444

4545
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
46+
// The caller must call "defer gitRepo.Close()"
4647
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
47-
ds := reqctx.GetRequestDataStore(ctx)
48-
if ds != nil {
49-
gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo)
48+
reqCtx := reqctx.FromContext(ctx)
49+
if reqCtx != nil {
50+
gitRepo, err := RepositoryFromRequestContextOrOpen(reqCtx, repo)
5051
return gitRepo, util.NopCloser{}, err
5152
}
5253
gitRepo, err := OpenRepository(ctx, repo)
5354
return gitRepo, gitRepo, err
5455
}
5556

56-
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context
57-
// The repo will be automatically closed when the request context is done
58-
func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) {
57+
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
58+
// Caller shouldn't close the git repo manually, the git repo will be automatically closed when the request context is done.
59+
func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
5960
ck := contextKey{repoPath: repoPath(repo)}
6061
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
6162
return gitRepo, nil
@@ -64,7 +65,7 @@ func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDa
6465
if err != nil {
6566
return nil, err
6667
}
67-
ds.AddCloser(gitRepo)
68-
ds.SetContextValue(ck, gitRepo)
68+
ctx.AddCloser(gitRepo)
69+
ctx.SetContextValue(ck, gitRepo)
6970
return gitRepo, nil
7071
}

modules/reqctx/datastore.go

+21-5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ func (r *requestDataStore) cleanUp() {
8888
}
8989
}
9090

91+
type RequestContext interface {
92+
context.Context
93+
RequestDataStore
94+
}
95+
96+
func FromContext(ctx context.Context) RequestContext {
97+
// here we must use the current ctx and the underlying store
98+
// the current ctx guarantees that the ctx deadline/cancellation/values are respected
99+
// the underlying store guarantees that the request-specific data is available
100+
if store := GetRequestDataStore(ctx); store != nil {
101+
return &requestContext{Context: ctx, RequestDataStore: store}
102+
}
103+
return nil
104+
}
105+
91106
func GetRequestDataStore(ctx context.Context) RequestDataStore {
92107
if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok {
93108
return req
@@ -97,27 +112,28 @@ func GetRequestDataStore(ctx context.Context) RequestDataStore {
97112

98113
type requestContext struct {
99114
context.Context
100-
dataStore *requestDataStore
115+
RequestDataStore
101116
}
102117

103118
func (c *requestContext) Value(key any) any {
104-
if v := c.dataStore.GetContextValue(key); v != nil {
119+
if v := c.GetContextValue(key); v != nil {
105120
return v
106121
}
107122
return c.Context.Value(key)
108123
}
109124

110125
func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) {
111126
ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true)
112-
reqCtx := &requestContext{Context: ctx, dataStore: &requestDataStore{values: make(map[any]any)}}
127+
store := &requestDataStore{values: make(map[any]any)}
128+
reqCtx := &requestContext{Context: ctx, RequestDataStore: store}
113129
return reqCtx, func() {
114-
reqCtx.dataStore.cleanUp()
130+
store.cleanUp()
115131
processFinished()
116132
}
117133
}
118134

119135
// NewRequestContextForTest creates a new RequestContext for testing purposes
120136
// It doesn't add the context to the process manager, nor do cleanup
121137
func NewRequestContextForTest(parentCtx context.Context) context.Context {
122-
return &requestContext{Context: parentCtx, dataStore: &requestDataStore{values: make(map[any]any)}}
138+
return &requestContext{Context: parentCtx, RequestDataStore: &requestDataStore{values: make(map[any]any)}}
123139
}

modules/templates/helper.go

+46-16
Original file line numberDiff line numberDiff line change
@@ -264,22 +264,42 @@ func userThemeName(user *user_model.User) string {
264264
return setting.UI.DefaultTheme
265265
}
266266

267+
func isQueryParamEmpty(v any) bool {
268+
return v == nil || v == false || v == 0 || v == int64(0) || v == ""
269+
}
270+
267271
// QueryBuild builds a query string from a list of key-value pairs.
268-
// It omits the nil and empty strings, but it doesn't omit other zero values,
269-
// because the zero value of number types may have a meaning.
272+
// It omits the nil, false, zero int/int64 and empty string values,
273+
// because they are default empty values for "ctx.FormXxx" calls.
274+
// If 0 or false need to be included, use string values: "0" and "false".
275+
// Build rules:
276+
// * Even parameters: always build as query string: a=b&c=d
277+
// * Odd parameters:
278+
// * * {"/anything", param-pairs...} => "/?param-paris"
279+
// * * {"anything?old-params", new-param-pairs...} => "anything?old-params&new-param-paris"
280+
// * * Otherwise: {"old&params", new-param-pairs...} => "old&params&new-param-paris"
281+
// * * Other behaviors are undefined yet.
270282
func QueryBuild(a ...any) template.URL {
271-
var s string
283+
var reqPath, s string
284+
hasTrailingSep := false
272285
if len(a)%2 == 1 {
273286
if v, ok := a[0].(string); ok {
274-
if v == "" || (v[0] != '?' && v[0] != '&') {
275-
panic("QueryBuild: invalid argument")
276-
}
277287
s = v
278288
} else if v, ok := a[0].(template.URL); ok {
279289
s = string(v)
280290
} else {
281291
panic("QueryBuild: invalid argument")
282292
}
293+
hasTrailingSep = s != "&" && strings.HasSuffix(s, "&")
294+
if strings.HasPrefix(s, "/") || strings.Contains(s, "?") {
295+
if s1, s2, ok := strings.Cut(s, "?"); ok {
296+
reqPath = s1 + "?"
297+
s = s2
298+
} else {
299+
reqPath += s + "?"
300+
s = ""
301+
}
302+
}
283303
}
284304
for i := len(a) % 2; i < len(a); i += 2 {
285305
k, ok := a[i].(string)
@@ -290,19 +310,16 @@ func QueryBuild(a ...any) template.URL {
290310
if va, ok := a[i+1].(string); ok {
291311
v = va
292312
} else if a[i+1] != nil {
293-
v = fmt.Sprint(a[i+1])
313+
if !isQueryParamEmpty(a[i+1]) {
314+
v = fmt.Sprint(a[i+1])
315+
}
294316
}
295317
// pos1 to pos2 is the "k=v&" part, "&" is optional
296318
pos1 := strings.Index(s, "&"+k+"=")
297319
if pos1 != -1 {
298320
pos1++
299-
} else {
300-
pos1 = strings.Index(s, "?"+k+"=")
301-
if pos1 != -1 {
302-
pos1++
303-
} else if strings.HasPrefix(s, k+"=") {
304-
pos1 = 0
305-
}
321+
} else if strings.HasPrefix(s, k+"=") {
322+
pos1 = 0
306323
}
307324
pos2 := len(s)
308325
if pos1 == -1 {
@@ -315,7 +332,7 @@ func QueryBuild(a ...any) template.URL {
315332
}
316333
if v != "" {
317334
sep := ""
318-
hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && (s[pos1-1] == '?' || s[pos1-1] == '&'))
335+
hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && s[pos1-1] == '&')
319336
if !hasPrefixSep {
320337
sep = "&"
321338
}
@@ -324,9 +341,22 @@ func QueryBuild(a ...any) template.URL {
324341
s = s[:pos1] + s[pos2:]
325342
}
326343
}
327-
if s != "" && s != "&" && s[len(s)-1] == '&' {
344+
if s != "" && s[len(s)-1] == '&' && !hasTrailingSep {
328345
s = s[:len(s)-1]
329346
}
347+
if reqPath != "" {
348+
if s == "" {
349+
s = reqPath
350+
if s != "?" {
351+
s = s[:len(s)-1]
352+
}
353+
} else {
354+
if s[0] == '&' {
355+
s = s[1:]
356+
}
357+
s = reqPath + s
358+
}
359+
}
330360
return template.URL(s)
331361
}
332362

modules/templates/helper_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,58 @@ func TestTemplateEscape(t *testing.T) {
118118
assert.Equal(t, `<a k="&#34;">&lt;&gt;</a>`, actual)
119119
})
120120
}
121+
122+
func TestQueryBuild(t *testing.T) {
123+
t.Run("construct", func(t *testing.T) {
124+
assert.Equal(t, "", string(QueryBuild()))
125+
assert.Equal(t, "", string(QueryBuild("a", nil, "b", false, "c", 0, "d", "")))
126+
assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true")))
127+
128+
// path with query parameters
129+
assert.Equal(t, "/?k=1", string(QueryBuild("/", "k", 1)))
130+
assert.Equal(t, "/", string(QueryBuild("/?k=a", "k", 0)))
131+
132+
// no path but question mark with query parameters
133+
assert.Equal(t, "?k=1", string(QueryBuild("?", "k", 1)))
134+
assert.Equal(t, "?", string(QueryBuild("?", "k", 0)))
135+
assert.Equal(t, "path?k=1", string(QueryBuild("path?", "k", 1)))
136+
assert.Equal(t, "path", string(QueryBuild("path?", "k", 0)))
137+
138+
// only query parameters
139+
assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1)))
140+
assert.Equal(t, "", string(QueryBuild("&", "k", 0)))
141+
assert.Equal(t, "", string(QueryBuild("&k=a", "k", 0)))
142+
assert.Equal(t, "", string(QueryBuild("k=a&", "k", 0)))
143+
assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2)))
144+
assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2)))
145+
assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2)))
146+
})
147+
148+
t.Run("replace", func(t *testing.T) {
149+
assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1)))
150+
assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1)))
151+
assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1)))
152+
assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1)))
153+
})
154+
155+
t.Run("replace-&", func(t *testing.T) {
156+
assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1)))
157+
assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1)))
158+
assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1)))
159+
assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1)))
160+
})
161+
162+
t.Run("delete", func(t *testing.T) {
163+
assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", "")))
164+
assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", "")))
165+
assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", "")))
166+
assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", "")))
167+
})
168+
169+
t.Run("delete-&", func(t *testing.T) {
170+
assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", "")))
171+
assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", "")))
172+
assert.Equal(t, "&a=b&c=d", string(QueryBuild("&a=b&c=d&e=f", "e", "")))
173+
assert.Equal(t, "&a=b&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "k", "")))
174+
})
175+
}

modules/util/path.go

+1
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ func statDir(dirPath, recPath string, includeDir, isDirOnly, followSymlinks bool
203203
//
204204
// Slice does not include given path itself.
205205
// If subdirectories is enabled, they will have suffix '/'.
206+
// FIXME: it doesn't like dot-files, for example: "owner/.profile.git"
206207
func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
207208
if isDir, err := IsDir(rootPath); err != nil {
208209
return nil, err

options/locale/locale_en-US.ini

+7-1
Original file line numberDiff line numberDiff line change
@@ -1015,7 +1015,9 @@ new_repo_helper = A repository contains all project files, including revision hi
10151015
owner = Owner
10161016
owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit.
10171017
repo_name = Repository Name
1018-
repo_name_helper = Good repository names use short, memorable and unique keywords.
1018+
repo_name_profile_public_hint= .profile is a special repository that you can use to add README.md to your public organization profile, visible to anyone. Make sure it’s public and initialize it with a README in the profile directory to get started.
1019+
repo_name_profile_private_hint = .profile-private is a special repository that you can use to add a README.md to your organization member profile, visible only to organization members. Make sure it’s private and initialize it with a README in the profile directory to get started.
1020+
repo_name_helper = Good repository names use short, memorable and unique keywords. A repository named '.profile' or '.profile-private' could be used to add a README.md for the user/organization profile.
10191021
repo_size = Repository Size
10201022
template = Template
10211023
template_select = Select a template.
@@ -2862,6 +2864,10 @@ teams.invite.title = You have been invited to join team <strong>%s</strong> in o
28622864
teams.invite.by = Invited by %s
28632865
teams.invite.description = Please click the button below to join the team.
28642866
2867+
view_as_role = View as: %s
2868+
view_as_public_hint = You are viewing the README a public user.
2869+
view_as_member_hint = You are viewing the README a member of this organization.
2870+
28652871
[admin]
28662872
maintenance = Maintenance
28672873
dashboard = Dashboard

routers/api/v1/repo/branch.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
729729
} else {
730730
if !isPlainRule {
731731
if ctx.Repo.GitRepo == nil {
732-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
732+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
733733
if err != nil {
734734
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
735735
return
@@ -1057,7 +1057,7 @@ func EditBranchProtection(ctx *context.APIContext) {
10571057
} else {
10581058
if !isPlainRule {
10591059
if ctx.Repo.GitRepo == nil {
1060-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
1060+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
10611061
if err != nil {
10621062
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
10631063
return

routers/api/v1/repo/compare.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func CompareDiff(ctx *context.APIContext) {
4545

4646
if ctx.Repo.GitRepo == nil {
4747
var err error
48-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
48+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
4949
if err != nil {
5050
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
5151
return

routers/api/v1/repo/download.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func DownloadArchive(ctx *context.APIContext) {
2929

3030
if ctx.Repo.GitRepo == nil {
3131
var err error
32-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
32+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
3333
if err != nil {
3434
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
3535
return

routers/api/v1/repo/file.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ func GetArchive(ctx *context.APIContext) {
282282

283283
if ctx.Repo.GitRepo == nil {
284284
var err error
285-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
285+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx.Repo.Repository)
286286
if err != nil {
287287
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
288288
return

routers/api/v1/repo/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
726726

727727
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
728728
var err error
729-
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
729+
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
730730
if err != nil {
731731
ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
732732
return err

routers/private/internal_repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func RepoAssignment(ctx *gitea_context.PrivateContext) {
2727
return
2828
}
2929

30-
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
30+
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo)
3131
if err != nil {
3232
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
3333
ctx.JSON(http.StatusInternalServerError, private.Response{

0 commit comments

Comments
 (0)