mirror of
https://gitcode.com/gitea/gitea.git
synced 2025-06-03 18:57:37 +08:00
[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>
This commit is contained in:
@ -43,19 +43,20 @@ type contextKey struct {
|
||||
}
|
||||
|
||||
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
||||
// The caller must call "defer gitRepo.Close()"
|
||||
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
|
||||
ds := reqctx.GetRequestDataStore(ctx)
|
||||
if ds != nil {
|
||||
gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo)
|
||||
reqCtx := reqctx.FromContext(ctx)
|
||||
if reqCtx != nil {
|
||||
gitRepo, err := RepositoryFromRequestContextOrOpen(reqCtx, repo)
|
||||
return gitRepo, util.NopCloser{}, err
|
||||
}
|
||||
gitRepo, err := OpenRepository(ctx, repo)
|
||||
return gitRepo, gitRepo, err
|
||||
}
|
||||
|
||||
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context
|
||||
// The repo will be automatically closed when the request context is done
|
||||
func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) {
|
||||
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context.
|
||||
// Caller shouldn't close the git repo manually, the git repo will be automatically closed when the request context is done.
|
||||
func RepositoryFromRequestContextOrOpen(ctx reqctx.RequestContext, repo Repository) (*git.Repository, error) {
|
||||
ck := contextKey{repoPath: repoPath(repo)}
|
||||
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
|
||||
return gitRepo, nil
|
||||
@ -64,7 +65,7 @@ func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDa
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ds.AddCloser(gitRepo)
|
||||
ds.SetContextValue(ck, gitRepo)
|
||||
ctx.AddCloser(gitRepo)
|
||||
ctx.SetContextValue(ck, gitRepo)
|
||||
return gitRepo, nil
|
||||
}
|
||||
|
@ -88,6 +88,21 @@ func (r *requestDataStore) cleanUp() {
|
||||
}
|
||||
}
|
||||
|
||||
type RequestContext interface {
|
||||
context.Context
|
||||
RequestDataStore
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) RequestContext {
|
||||
// here we must use the current ctx and the underlying store
|
||||
// the current ctx guarantees that the ctx deadline/cancellation/values are respected
|
||||
// the underlying store guarantees that the request-specific data is available
|
||||
if store := GetRequestDataStore(ctx); store != nil {
|
||||
return &requestContext{Context: ctx, RequestDataStore: store}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetRequestDataStore(ctx context.Context) RequestDataStore {
|
||||
if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok {
|
||||
return req
|
||||
@ -97,11 +112,11 @@ func GetRequestDataStore(ctx context.Context) RequestDataStore {
|
||||
|
||||
type requestContext struct {
|
||||
context.Context
|
||||
dataStore *requestDataStore
|
||||
RequestDataStore
|
||||
}
|
||||
|
||||
func (c *requestContext) Value(key any) any {
|
||||
if v := c.dataStore.GetContextValue(key); v != nil {
|
||||
if v := c.GetContextValue(key); v != nil {
|
||||
return v
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
@ -109,9 +124,10 @@ func (c *requestContext) Value(key any) any {
|
||||
|
||||
func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) {
|
||||
ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true)
|
||||
reqCtx := &requestContext{Context: ctx, dataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
store := &requestDataStore{values: make(map[any]any)}
|
||||
reqCtx := &requestContext{Context: ctx, RequestDataStore: store}
|
||||
return reqCtx, func() {
|
||||
reqCtx.dataStore.cleanUp()
|
||||
store.cleanUp()
|
||||
processFinished()
|
||||
}
|
||||
}
|
||||
@ -119,5 +135,5 @@ func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Co
|
||||
// NewRequestContextForTest creates a new RequestContext for testing purposes
|
||||
// It doesn't add the context to the process manager, nor do cleanup
|
||||
func NewRequestContextForTest(parentCtx context.Context) context.Context {
|
||||
return &requestContext{Context: parentCtx, dataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
return &requestContext{Context: parentCtx, RequestDataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
}
|
||||
|
@ -264,22 +264,42 @@ func userThemeName(user *user_model.User) string {
|
||||
return setting.UI.DefaultTheme
|
||||
}
|
||||
|
||||
func isQueryParamEmpty(v any) bool {
|
||||
return v == nil || v == false || v == 0 || v == int64(0) || v == ""
|
||||
}
|
||||
|
||||
// QueryBuild builds a query string from a list of key-value pairs.
|
||||
// It omits the nil and empty strings, but it doesn't omit other zero values,
|
||||
// because the zero value of number types may have a meaning.
|
||||
// It omits the nil, false, zero int/int64 and empty string values,
|
||||
// because they are default empty values for "ctx.FormXxx" calls.
|
||||
// If 0 or false need to be included, use string values: "0" and "false".
|
||||
// Build rules:
|
||||
// * Even parameters: always build as query string: a=b&c=d
|
||||
// * Odd parameters:
|
||||
// * * {"/anything", param-pairs...} => "/?param-paris"
|
||||
// * * {"anything?old-params", new-param-pairs...} => "anything?old-params&new-param-paris"
|
||||
// * * Otherwise: {"old¶ms", new-param-pairs...} => "old¶ms&new-param-paris"
|
||||
// * * Other behaviors are undefined yet.
|
||||
func QueryBuild(a ...any) template.URL {
|
||||
var s string
|
||||
var reqPath, s string
|
||||
hasTrailingSep := false
|
||||
if len(a)%2 == 1 {
|
||||
if v, ok := a[0].(string); ok {
|
||||
if v == "" || (v[0] != '?' && v[0] != '&') {
|
||||
panic("QueryBuild: invalid argument")
|
||||
}
|
||||
s = v
|
||||
} else if v, ok := a[0].(template.URL); ok {
|
||||
s = string(v)
|
||||
} else {
|
||||
panic("QueryBuild: invalid argument")
|
||||
}
|
||||
hasTrailingSep = s != "&" && strings.HasSuffix(s, "&")
|
||||
if strings.HasPrefix(s, "/") || strings.Contains(s, "?") {
|
||||
if s1, s2, ok := strings.Cut(s, "?"); ok {
|
||||
reqPath = s1 + "?"
|
||||
s = s2
|
||||
} else {
|
||||
reqPath += s + "?"
|
||||
s = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := len(a) % 2; i < len(a); i += 2 {
|
||||
k, ok := a[i].(string)
|
||||
@ -290,19 +310,16 @@ func QueryBuild(a ...any) template.URL {
|
||||
if va, ok := a[i+1].(string); ok {
|
||||
v = va
|
||||
} else if a[i+1] != nil {
|
||||
v = fmt.Sprint(a[i+1])
|
||||
if !isQueryParamEmpty(a[i+1]) {
|
||||
v = fmt.Sprint(a[i+1])
|
||||
}
|
||||
}
|
||||
// pos1 to pos2 is the "k=v&" part, "&" is optional
|
||||
pos1 := strings.Index(s, "&"+k+"=")
|
||||
if pos1 != -1 {
|
||||
pos1++
|
||||
} else {
|
||||
pos1 = strings.Index(s, "?"+k+"=")
|
||||
if pos1 != -1 {
|
||||
pos1++
|
||||
} else if strings.HasPrefix(s, k+"=") {
|
||||
pos1 = 0
|
||||
}
|
||||
} else if strings.HasPrefix(s, k+"=") {
|
||||
pos1 = 0
|
||||
}
|
||||
pos2 := len(s)
|
||||
if pos1 == -1 {
|
||||
@ -315,7 +332,7 @@ func QueryBuild(a ...any) template.URL {
|
||||
}
|
||||
if v != "" {
|
||||
sep := ""
|
||||
hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && (s[pos1-1] == '?' || s[pos1-1] == '&'))
|
||||
hasPrefixSep := pos1 == 0 || (pos1 <= len(s) && s[pos1-1] == '&')
|
||||
if !hasPrefixSep {
|
||||
sep = "&"
|
||||
}
|
||||
@ -324,9 +341,22 @@ func QueryBuild(a ...any) template.URL {
|
||||
s = s[:pos1] + s[pos2:]
|
||||
}
|
||||
}
|
||||
if s != "" && s != "&" && s[len(s)-1] == '&' {
|
||||
if s != "" && s[len(s)-1] == '&' && !hasTrailingSep {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
if reqPath != "" {
|
||||
if s == "" {
|
||||
s = reqPath
|
||||
if s != "?" {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
} else {
|
||||
if s[0] == '&' {
|
||||
s = s[1:]
|
||||
}
|
||||
s = reqPath + s
|
||||
}
|
||||
}
|
||||
return template.URL(s)
|
||||
}
|
||||
|
||||
|
@ -118,3 +118,58 @@ func TestTemplateEscape(t *testing.T) {
|
||||
assert.Equal(t, `<a k="""><></a>`, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryBuild(t *testing.T) {
|
||||
t.Run("construct", func(t *testing.T) {
|
||||
assert.Equal(t, "", string(QueryBuild()))
|
||||
assert.Equal(t, "", string(QueryBuild("a", nil, "b", false, "c", 0, "d", "")))
|
||||
assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true")))
|
||||
|
||||
// path with query parameters
|
||||
assert.Equal(t, "/?k=1", string(QueryBuild("/", "k", 1)))
|
||||
assert.Equal(t, "/", string(QueryBuild("/?k=a", "k", 0)))
|
||||
|
||||
// no path but question mark with query parameters
|
||||
assert.Equal(t, "?k=1", string(QueryBuild("?", "k", 1)))
|
||||
assert.Equal(t, "?", string(QueryBuild("?", "k", 0)))
|
||||
assert.Equal(t, "path?k=1", string(QueryBuild("path?", "k", 1)))
|
||||
assert.Equal(t, "path", string(QueryBuild("path?", "k", 0)))
|
||||
|
||||
// only query parameters
|
||||
assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1)))
|
||||
assert.Equal(t, "", string(QueryBuild("&", "k", 0)))
|
||||
assert.Equal(t, "", string(QueryBuild("&k=a", "k", 0)))
|
||||
assert.Equal(t, "", string(QueryBuild("k=a&", "k", 0)))
|
||||
assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2)))
|
||||
assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2)))
|
||||
assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2)))
|
||||
})
|
||||
|
||||
t.Run("replace", func(t *testing.T) {
|
||||
assert.Equal(t, "a=1&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", 1)))
|
||||
assert.Equal(t, "a=b&c=1&e=f", string(QueryBuild("a=b&c=d&e=f", "c", 1)))
|
||||
assert.Equal(t, "a=b&c=d&e=1", string(QueryBuild("a=b&c=d&e=f", "e", 1)))
|
||||
assert.Equal(t, "a=b&c=d&e=f&k=1", string(QueryBuild("a=b&c=d&e=f", "k", 1)))
|
||||
})
|
||||
|
||||
t.Run("replace-&", func(t *testing.T) {
|
||||
assert.Equal(t, "&a=1&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", 1)))
|
||||
assert.Equal(t, "&a=b&c=1&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", 1)))
|
||||
assert.Equal(t, "&a=b&c=d&e=1", string(QueryBuild("&a=b&c=d&e=f", "e", 1)))
|
||||
assert.Equal(t, "&a=b&c=d&e=f&k=1", string(QueryBuild("&a=b&c=d&e=f", "k", 1)))
|
||||
})
|
||||
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
assert.Equal(t, "c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "a", "")))
|
||||
assert.Equal(t, "a=b&e=f", string(QueryBuild("a=b&c=d&e=f", "c", "")))
|
||||
assert.Equal(t, "a=b&c=d", string(QueryBuild("a=b&c=d&e=f", "e", "")))
|
||||
assert.Equal(t, "a=b&c=d&e=f", string(QueryBuild("a=b&c=d&e=f", "k", "")))
|
||||
})
|
||||
|
||||
t.Run("delete-&", func(t *testing.T) {
|
||||
assert.Equal(t, "&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "a", "")))
|
||||
assert.Equal(t, "&a=b&e=f", string(QueryBuild("&a=b&c=d&e=f", "c", "")))
|
||||
assert.Equal(t, "&a=b&c=d", string(QueryBuild("&a=b&c=d&e=f", "e", "")))
|
||||
assert.Equal(t, "&a=b&c=d&e=f", string(QueryBuild("&a=b&c=d&e=f", "k", "")))
|
||||
})
|
||||
}
|
||||
|
@ -203,6 +203,7 @@ func statDir(dirPath, recPath string, includeDir, isDirOnly, followSymlinks bool
|
||||
//
|
||||
// Slice does not include given path itself.
|
||||
// If subdirectories is enabled, they will have suffix '/'.
|
||||
// FIXME: it doesn't like dot-files, for example: "owner/.profile.git"
|
||||
func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
|
||||
if isDir, err := IsDir(rootPath); err != nil {
|
||||
return nil, err
|
||||
|
Reference in New Issue
Block a user