mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-17 00:01:00 -04:00
Compare commits
10 Commits
7e3b7c2346
...
8cadd51bf2
Author | SHA1 | Date | |
---|---|---|---|
|
8cadd51bf2 | ||
|
52e24167e5 | ||
|
c5573dbc0f | ||
|
af0468ed8d | ||
|
1960ad5c90 | ||
|
d949d8e074 | ||
|
15a1c2d7ef | ||
|
b116418f05 | ||
|
090e753923 | ||
|
a12f575737 |
96
CODE_OF_CONDUCT.md
Normal file
96
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Gitea Community Code of Conduct
|
||||
|
||||
## About
|
||||
|
||||
Online communities include people from many different backgrounds. The Gitea contributors are committed to providing a friendly, safe and welcoming environment for all, regardless of gender identity and expression, sexual orientation, disabilities, neurodiversity, physical appearance, body size, ethnicity, nationality, race, age, religion, or similar personal characteristics.
|
||||
|
||||
The first goal of the Code of Conduct is to specify a baseline standard of behavior so that people with different social values and communication styles can talk about Gitea effectively, productively, and respectfully.
|
||||
|
||||
The second goal is to provide a mechanism for resolving conflicts in the community when they arise.
|
||||
|
||||
The third goal of the Code of Conduct is to make our community welcoming to people from different backgrounds. Diversity is critical to the project; for Gitea to be successful, it needs contributors and users from all backgrounds.
|
||||
|
||||
We believe that healthy debate and disagreement are essential to a healthy project and community. However, it is never ok to be disrespectful. We value diverse opinions, but we value respectful behavior more.
|
||||
|
||||
## Community values
|
||||
|
||||
These are the values to which people in the Gitea community should aspire.
|
||||
|
||||
- **Be friendly and welcoming.**
|
||||
- **Be patient.**
|
||||
- Remember that people have varying communication styles and that not everyone is using their native language. (Meaning and tone can be lost in translation.)
|
||||
- **Be thoughtful.**
|
||||
- Productive communication requires effort. Think about how your words will be interpreted.
|
||||
- Remember that sometimes it is best to refrain entirely from commenting.
|
||||
- **Be respectful.**
|
||||
- In particular, respect differences of opinion.
|
||||
- **Be charitable.**
|
||||
- Interpret the arguments of others in good faith, do not seek to disagree.
|
||||
- When we do disagree, try to understand why.
|
||||
- **Be constructive.**
|
||||
- Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation.
|
||||
- Avoid unconstructive criticism: don't merely decry the current state of affairs; offer—or at least solicit—suggestions as to how things may be improved.
|
||||
- Avoid snarking (pithy, unproductive, sniping comments)
|
||||
- Avoid discussing potentially offensive or sensitive issues; this all too often leads to unnecessary conflict.
|
||||
- Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults to a person or group).
|
||||
- **Be responsible.**
|
||||
- What you say and do matters. Take responsibility for your words and actions, including their consequences, whether intended or otherwise.
|
||||
|
||||
People are complicated. You should expect to be misunderstood and to misunderstand others; when this inevitably occurs, resist the urge to be defensive or assign blame. Try not to take offense where no offense was intended. Give people the benefit of the doubt. Even if the intent was to provoke, do not rise to it. It is the responsibility of all parties to de-escalate conflict when it arises.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others’ private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject: comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, as well as to ban (temporarily or permanently) any contributor for behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when the Project Stewards have a reasonable belief that an individual’s behavior may have a negative impact on the project or its community.
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the project’s code of conduct.
|
||||
|
||||
If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe.
|
||||
|
||||
Reports should be directed to the Gitea Project Stewards at conduct@gitea.com. It is the Project Stewards’ duty to receive and address reported violations of the code of conduct. They will then work with a committee consisting of representatives from the technical-oversight-committee.
|
||||
|
||||
We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. Under normal circumstances, we will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. If there is a consensus between maintainers that such an endeavor would be useless (i.e. in case of an obvious spammer), we reserve the right to take action without notifying the accused first. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone’s safety, we may take action without notice.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
## Summary
|
||||
|
||||
- Treat everyone with respect and kindness.
|
||||
- Be thoughtful in how you communicate.
|
||||
- Don’t be destructive or inflammatory.
|
||||
- If you encounter an issue, please mail conduct@gitea.com.
|
@ -25,7 +25,7 @@ func TestIterate(t *testing.T) {
|
||||
return nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 83, repoCnt)
|
||||
assert.EqualValues(t, 84, repoCnt)
|
||||
|
||||
err = db.Iterate(db.DefaultContext, nil, func(ctx context.Context, repoUnit *repo_model.RepoUnit) error {
|
||||
reopUnit2 := repo_model.RepoUnit{ID: repoUnit.ID}
|
||||
|
@ -35,11 +35,11 @@ func TestFind(t *testing.T) {
|
||||
var repoUnits []repo_model.RepoUnit
|
||||
err := db.Find(db.DefaultContext, &opts, &repoUnits)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 83, len(repoUnits))
|
||||
assert.EqualValues(t, 84, len(repoUnits))
|
||||
|
||||
cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 83, cnt)
|
||||
assert.EqualValues(t, 84, cnt)
|
||||
|
||||
repoUnits = make([]repo_model.RepoUnit, 0, 10)
|
||||
newCnt, err := db.FindAndCount(db.DefaultContext, &opts, &repoUnits)
|
||||
|
@ -569,3 +569,9 @@
|
||||
type: 3
|
||||
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
|
||||
created_unix: 946684810
|
||||
|
||||
-
|
||||
id: 84
|
||||
repo_id: 56
|
||||
type: 1
|
||||
created_unix: 946684810
|
||||
|
@ -1634,3 +1634,16 @@
|
||||
is_private: true
|
||||
num_issues: 1
|
||||
status: 0
|
||||
|
||||
-
|
||||
id: 56
|
||||
owner_id: 2
|
||||
owner_name: user2
|
||||
lower_name: readme-test
|
||||
name: readme-test
|
||||
default_branch: master
|
||||
is_empty: false
|
||||
is_archived: false
|
||||
is_private: true
|
||||
status: 0
|
||||
num_issues: 0
|
||||
|
@ -66,7 +66,7 @@
|
||||
num_followers: 2
|
||||
num_following: 1
|
||||
num_stars: 2
|
||||
num_repos: 11
|
||||
num_repos: 12
|
||||
num_teams: 0
|
||||
num_members: 0
|
||||
visibility: 0
|
||||
|
@ -6,7 +6,6 @@ package git
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -17,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// LFSLock represents a git lfs lock of repository.
|
||||
@ -34,11 +34,7 @@ func init() {
|
||||
|
||||
// BeforeInsert is invoked from XORM before inserting an object of this type.
|
||||
func (l *LFSLock) BeforeInsert() {
|
||||
l.Path = cleanPath(l.Path)
|
||||
}
|
||||
|
||||
func cleanPath(p string) string {
|
||||
return path.Clean("/" + p)[1:]
|
||||
l.Path = util.CleanPath(l.Path)
|
||||
}
|
||||
|
||||
// CreateLFSLock creates a new lock.
|
||||
@ -53,7 +49,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lock.Path = cleanPath(lock.Path)
|
||||
lock.Path = util.CleanPath(lock.Path)
|
||||
lock.RepoID = repo.ID
|
||||
|
||||
l, err := GetLFSLock(dbCtx, repo, lock.Path)
|
||||
@ -73,7 +69,7 @@ func CreateLFSLock(ctx context.Context, repo *repo_model.Repository, lock *LFSLo
|
||||
|
||||
// GetLFSLock returns release by given path.
|
||||
func GetLFSLock(ctx context.Context, repo *repo_model.Repository, path string) (*LFSLock, error) {
|
||||
path = cleanPath(path)
|
||||
path = util.CleanPath(path)
|
||||
rel := &LFSLock{RepoID: repo.ID}
|
||||
has, err := db.GetEngine(ctx).Where("lower(path) = ?", strings.ToLower(path)).Get(rel)
|
||||
if err != nil {
|
||||
|
119
modules/cache/context.go
vendored
119
modules/cache/context.go
vendored
@ -6,6 +6,7 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
)
|
||||
@ -14,65 +15,151 @@ import (
|
||||
// This is useful for caching data that is expensive to calculate and is likely to be
|
||||
// used multiple times in a request.
|
||||
type cacheContext struct {
|
||||
ctx context.Context
|
||||
data map[any]map[any]any
|
||||
lock sync.RWMutex
|
||||
data map[any]map[any]any
|
||||
lock sync.RWMutex
|
||||
created time.Time
|
||||
discard bool
|
||||
}
|
||||
|
||||
func (cc *cacheContext) Get(tp, key any) any {
|
||||
cc.lock.RLock()
|
||||
defer cc.lock.RUnlock()
|
||||
if cc.data[tp] == nil {
|
||||
return nil
|
||||
}
|
||||
return cc.data[tp][key]
|
||||
}
|
||||
|
||||
func (cc *cacheContext) Put(tp, key, value any) {
|
||||
cc.lock.Lock()
|
||||
defer cc.lock.Unlock()
|
||||
if cc.data[tp] == nil {
|
||||
cc.data[tp] = make(map[any]any)
|
||||
|
||||
if cc.discard {
|
||||
return
|
||||
}
|
||||
cc.data[tp][key] = value
|
||||
|
||||
d := cc.data[tp]
|
||||
if d == nil {
|
||||
d = make(map[any]any)
|
||||
cc.data[tp] = d
|
||||
}
|
||||
d[key] = value
|
||||
}
|
||||
|
||||
func (cc *cacheContext) Delete(tp, key any) {
|
||||
cc.lock.Lock()
|
||||
defer cc.lock.Unlock()
|
||||
if cc.data[tp] == nil {
|
||||
return
|
||||
}
|
||||
delete(cc.data[tp], key)
|
||||
}
|
||||
|
||||
func (cc *cacheContext) Discard() {
|
||||
cc.lock.Lock()
|
||||
defer cc.lock.Unlock()
|
||||
cc.data = nil
|
||||
cc.discard = true
|
||||
}
|
||||
|
||||
func (cc *cacheContext) isDiscard() bool {
|
||||
cc.lock.RLock()
|
||||
defer cc.lock.RUnlock()
|
||||
return cc.discard
|
||||
}
|
||||
|
||||
// cacheContextLifetime is the max lifetime of cacheContext.
|
||||
// Since cacheContext is used to cache data in a request level context, 10s is enough.
|
||||
// If a cacheContext is used more than 10s, it's probably misuse.
|
||||
const cacheContextLifetime = 10 * time.Second
|
||||
|
||||
var timeNow = time.Now
|
||||
|
||||
func (cc *cacheContext) Expired() bool {
|
||||
return timeNow().Sub(cc.created) > cacheContextLifetime
|
||||
}
|
||||
|
||||
var cacheContextKey = struct{}{}
|
||||
|
||||
/*
|
||||
Since there are both WithCacheContext and WithNoCacheContext,
|
||||
it may be confusing when there is nesting.
|
||||
|
||||
Some cases to explain the design:
|
||||
|
||||
When:
|
||||
- A, B or C means a cache context.
|
||||
- A', B' or C' means a discard cache context.
|
||||
- ctx means context.Backgrand().
|
||||
- A(ctx) means a cache context with ctx as the parent context.
|
||||
- B(A(ctx)) means a cache context with A(ctx) as the parent context.
|
||||
- With is alias of WithCacheContext.
|
||||
- WithNo is alias of WithNoCacheContext.
|
||||
|
||||
So:
|
||||
- With(ctx) -> A(ctx)
|
||||
- With(With(ctx)) -> A(ctx), not B(A(ctx)), always reuse parent cache context if possible.
|
||||
- With(With(With(ctx))) -> A(ctx), not C(B(A(ctx))), ditto.
|
||||
- WithNo(ctx) -> ctx, not A'(ctx), don't create new cache context if we don't have to.
|
||||
- WithNo(With(ctx)) -> A'(ctx)
|
||||
- WithNo(WithNo(With(ctx))) -> A'(ctx), not B'(A'(ctx)), don't create new cache context if we don't have to.
|
||||
- With(WithNo(With(ctx))) -> B(A'(ctx)), not A(ctx), never reuse a discard cache context.
|
||||
- WithNo(With(WithNo(With(ctx)))) -> B'(A'(ctx))
|
||||
- With(WithNo(With(WithNo(With(ctx))))) -> C(B'(A'(ctx))), so there's always only one not-discard cache context.
|
||||
*/
|
||||
|
||||
func WithCacheContext(ctx context.Context) context.Context {
|
||||
if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
|
||||
if !c.isDiscard() {
|
||||
// reuse parent context
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
return context.WithValue(ctx, cacheContextKey, &cacheContext{
|
||||
ctx: ctx,
|
||||
data: make(map[any]map[any]any),
|
||||
data: make(map[any]map[any]any),
|
||||
created: timeNow(),
|
||||
})
|
||||
}
|
||||
|
||||
func WithNoCacheContext(ctx context.Context) context.Context {
|
||||
if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
|
||||
// The caller want to run long-life tasks, but the parent context is a cache context.
|
||||
// So we should disable and clean the cache data, or it will be kept in memory for a long time.
|
||||
c.Discard()
|
||||
return ctx
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func GetContextData(ctx context.Context, tp, key any) any {
|
||||
if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
return nil
|
||||
}
|
||||
return c.Get(tp, key)
|
||||
}
|
||||
log.Warn("cannot get cache context when getting data: %v", ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetContextData(ctx context.Context, tp, key, value any) {
|
||||
if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
return
|
||||
}
|
||||
c.Put(tp, key, value)
|
||||
return
|
||||
}
|
||||
log.Warn("cannot get cache context when setting data: %v", ctx)
|
||||
}
|
||||
|
||||
func RemoveContextData(ctx context.Context, tp, key any) {
|
||||
if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok {
|
||||
if c.Expired() {
|
||||
// The warning means that the cache context is misused for long-life task,
|
||||
// it can be resolved with WithNoCacheContext(ctx).
|
||||
log.Warn("cache context is expired, may be misused for long-life tasks: %v", c)
|
||||
return
|
||||
}
|
||||
c.Delete(tp, key)
|
||||
}
|
||||
}
|
||||
|
39
modules/cache/context_test.go
vendored
39
modules/cache/context_test.go
vendored
@ -6,6 +6,7 @@ package cache
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -25,7 +26,7 @@ func TestWithCacheContext(t *testing.T) {
|
||||
assert.EqualValues(t, 1, v.(int))
|
||||
|
||||
RemoveContextData(ctx, field, "my_config1")
|
||||
RemoveContextData(ctx, field, "my_config2") // remove an non-exist key
|
||||
RemoveContextData(ctx, field, "my_config2") // remove a non-exist key
|
||||
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
@ -38,4 +39,40 @@ func TestWithCacheContext(t *testing.T) {
|
||||
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.EqualValues(t, 1, v)
|
||||
|
||||
now := timeNow
|
||||
defer func() {
|
||||
timeNow = now
|
||||
}()
|
||||
timeNow = func() time.Time {
|
||||
return now().Add(10 * time.Second)
|
||||
}
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
}
|
||||
|
||||
func TestWithNoCacheContext(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
const field = "system_setting"
|
||||
|
||||
v := GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
SetContextData(ctx, field, "my_config1", 1)
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v) // still no cache
|
||||
|
||||
ctx = WithCacheContext(ctx)
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
SetContextData(ctx, field, "my_config1", 1)
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.NotNil(t, v)
|
||||
|
||||
ctx = WithNoCacheContext(ctx)
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v)
|
||||
SetContextData(ctx, field, "my_config1", 1)
|
||||
v = GetContextData(ctx, field, "my_config1")
|
||||
assert.Nil(t, v) // still no cache
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["Context"] = &ctx
|
||||
|
@ -388,7 +388,7 @@ func (ctx *Context) SetServeHeaders(opts *ServeHeaderOptions) {
|
||||
if duration == 0 {
|
||||
duration = 5 * time.Minute
|
||||
}
|
||||
httpcache.AddCacheControlToHeader(header, duration)
|
||||
httpcache.SetCacheControlInHeader(header, duration)
|
||||
|
||||
if !opts.LastModified.IsZero() {
|
||||
header.Set("Last-Modified", opts.LastModified.UTC().Format(http.TimeFormat))
|
||||
@ -753,7 +753,7 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform")
|
||||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["CsrfToken"] = ctx.csrf.GetToken()
|
||||
|
@ -15,8 +15,8 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// AddCacheControlToHeader adds suitable cache-control headers to response
|
||||
func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
||||
// SetCacheControlInHeader sets suitable cache-control headers in the response
|
||||
func SetCacheControlInHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
|
||||
directives := make([]string, 0, 2+len(additionalDirectives))
|
||||
|
||||
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
|
||||
@ -31,7 +31,7 @@ func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDire
|
||||
directives = append(directives, "max-age=0", "private", "must-revalidate")
|
||||
|
||||
// to remind users they are using non-prod setting.
|
||||
h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
|
||||
h.Set("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)
|
||||
}
|
||||
|
||||
h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", "))
|
||||
@ -50,7 +50,7 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (
|
||||
|
||||
// HandleGenericTimeCache handles time-based caching for a HTTP request
|
||||
func HandleGenericTimeCache(req *http.Request, w http.ResponseWriter, lastModified time.Time) (handled bool) {
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
|
||||
ifModifiedSince := req.Header.Get("If-Modified-Since")
|
||||
if ifModifiedSince != "" {
|
||||
@ -81,7 +81,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
|
||||
return true
|
||||
}
|
||||
}
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
return false
|
||||
}
|
||||
|
||||
@ -125,6 +125,6 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
|
||||
}
|
||||
}
|
||||
}
|
||||
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
|
||||
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
|
||||
return false
|
||||
}
|
||||
|
@ -36,17 +36,17 @@ func (err ErrTemplateLoad) Error() string {
|
||||
// GetTemplateFile loads the label template file by given name,
|
||||
// then parses and returns a list of name-color pairs and optionally description.
|
||||
func GetTemplateFile(name string) ([]*Label, error) {
|
||||
data, err := options.GetRepoInitFile("label", name+".yaml")
|
||||
data, err := options.Labels(name + ".yaml")
|
||||
if err == nil && len(data) > 0 {
|
||||
return parseYamlFormat(name+".yaml", data)
|
||||
}
|
||||
|
||||
data, err = options.GetRepoInitFile("label", name+".yml")
|
||||
data, err = options.Labels(name + ".yml")
|
||||
if err == nil && len(data) > 0 {
|
||||
return parseYamlFormat(name+".yml", data)
|
||||
}
|
||||
|
||||
data, err = options.GetRepoInitFile("label", name)
|
||||
data, err = options.Labels(name)
|
||||
if err != nil {
|
||||
return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)}
|
||||
}
|
||||
|
@ -7,11 +7,52 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// Locale reads the content of a specific locale from static/bindata or custom path.
|
||||
func Locale(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("locale", util.CleanPath(name)))
|
||||
}
|
||||
|
||||
// Readme reads the content of a specific readme from static/bindata or custom path.
|
||||
func Readme(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("readme", util.CleanPath(name)))
|
||||
}
|
||||
|
||||
// Gitignore reads the content of a gitignore locale from static/bindata or custom path.
|
||||
func Gitignore(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("gitignore", util.CleanPath(name)))
|
||||
}
|
||||
|
||||
// License reads the content of a specific license from static/bindata or custom path.
|
||||
func License(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("license", util.CleanPath(name)))
|
||||
}
|
||||
|
||||
// Labels reads the content of a specific labels from static/bindata or custom path.
|
||||
func Labels(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("label", util.CleanPath(name)))
|
||||
}
|
||||
|
||||
// WalkLocales reads the content of a specific locale
|
||||
func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error {
|
||||
if IsDynamic() {
|
||||
if err := walkAssetDir(filepath.Join(setting.StaticRootPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to walk locales. Error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to walk locales. Error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func walkAssetDir(root string, callback func(path, name string, d fs.DirEntry, err error) error) error {
|
||||
if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
// name is the path relative to the root
|
||||
@ -37,3 +78,18 @@ func walkAssetDir(root string, callback func(path, name string, d fs.DirEntry, e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func statDirIfExist(dir string) ([]string, error) {
|
||||
isDir, err := util.IsDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to check if static directory %s is a directory. %w", dir, err)
|
||||
}
|
||||
if !isDir {
|
||||
return nil, nil
|
||||
}
|
||||
files, err := util.StatDir(dir, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read directory %q. %w", dir, err)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
@ -7,10 +7,8 @@ package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -27,76 +25,20 @@ func Dir(name string) ([]string, error) {
|
||||
|
||||
var result []string
|
||||
|
||||
customDir := path.Join(setting.CustomPath, "options", name)
|
||||
|
||||
isDir, err := util.IsDir(customDir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Unabe to check if custom directory %s is a directory. %w", customDir, err)
|
||||
}
|
||||
if isDir {
|
||||
files, err := util.StatDir(customDir, true)
|
||||
for _, dir := range []string{
|
||||
path.Join(setting.CustomPath, "options", name), // custom dir
|
||||
path.Join(setting.StaticRootPath, "options", name), // static dir
|
||||
} {
|
||||
files, err := statDirIfExist(dir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Failed to read custom directory. %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, files...)
|
||||
}
|
||||
|
||||
staticDir := path.Join(setting.StaticRootPath, "options", name)
|
||||
|
||||
isDir, err = util.IsDir(staticDir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("unable to check if static directory %s is a directory. %w", staticDir, err)
|
||||
}
|
||||
if isDir {
|
||||
files, err := util.StatDir(staticDir, true)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Failed to read static directory. %w", err)
|
||||
}
|
||||
|
||||
result = append(result, files...)
|
||||
}
|
||||
|
||||
return directories.AddAndGet(name, result), nil
|
||||
}
|
||||
|
||||
// Locale reads the content of a specific locale from static or custom path.
|
||||
func Locale(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("locale", name))
|
||||
}
|
||||
|
||||
// WalkLocales reads the content of a specific locale from static or custom path.
|
||||
func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error {
|
||||
if err := walkAssetDir(filepath.Join(setting.StaticRootPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to walk locales. Error: %w", err)
|
||||
}
|
||||
|
||||
if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to walk locales. Error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readme reads the content of a specific readme from static or custom path.
|
||||
func Readme(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("readme", name))
|
||||
}
|
||||
|
||||
// Gitignore reads the content of a specific gitignore from static or custom path.
|
||||
func Gitignore(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("gitignore", name))
|
||||
}
|
||||
|
||||
// License reads the content of a specific license from static or custom path.
|
||||
func License(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("license", name))
|
||||
}
|
||||
|
||||
// Labels reads the content of a specific labels from static or custom path.
|
||||
func Labels(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("label", name))
|
||||
}
|
||||
|
||||
// fileFromDir is a helper to read files from static or custom path.
|
||||
func fileFromDir(name string) ([]byte, error) {
|
||||
customPath := path.Join(setting.CustomPath, "options", name)
|
||||
|
@ -1,44 +0,0 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// GetRepoInitFile returns repository init files
|
||||
func GetRepoInitFile(tp, name string) ([]byte, error) {
|
||||
cleanedName := strings.TrimLeft(path.Clean("/"+name), "/")
|
||||
relPath := path.Join("options", tp, cleanedName)
|
||||
|
||||
// Use custom file when available.
|
||||
customPath := path.Join(setting.CustomPath, relPath)
|
||||
isFile, err := util.IsFile(customPath)
|
||||
if err != nil {
|
||||
log.Error("Unable to check if %s is a file. Error: %v", customPath, err)
|
||||
}
|
||||
if isFile {
|
||||
return os.ReadFile(customPath)
|
||||
}
|
||||
|
||||
switch tp {
|
||||
case "readme":
|
||||
return Readme(cleanedName)
|
||||
case "gitignore":
|
||||
return Gitignore(cleanedName)
|
||||
case "license":
|
||||
return License(cleanedName)
|
||||
case "label":
|
||||
return Labels(cleanedName)
|
||||
default:
|
||||
return []byte{}, fmt.Errorf("Invalid init file type")
|
||||
}
|
||||
}
|
@ -8,10 +8,8 @@ package options
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -28,17 +26,14 @@ func Dir(name string) ([]string, error) {
|
||||
|
||||
var result []string
|
||||
|
||||
customDir := path.Join(setting.CustomPath, "options", name)
|
||||
isDir, err := util.IsDir(customDir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("unable to check if custom directory %q is a directory. %w", customDir, err)
|
||||
}
|
||||
if isDir {
|
||||
files, err := util.StatDir(customDir, true)
|
||||
for _, dir := range []string{
|
||||
path.Join(setting.CustomPath, "options", name), // custom dir
|
||||
// no static dir
|
||||
} {
|
||||
files, err := statDirIfExist(dir)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("unable to read custom directory %q. %w", customDir, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, files...)
|
||||
}
|
||||
|
||||
@ -69,39 +64,6 @@ func AssetDir(dirName string) ([]string, error) {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Locale reads the content of a specific locale from bindata or custom path.
|
||||
func Locale(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("locale", name))
|
||||
}
|
||||
|
||||
// WalkLocales reads the content of a specific locale from static or custom path.
|
||||
func WalkLocales(callback func(path, name string, d fs.DirEntry, err error) error) error {
|
||||
if err := walkAssetDir(filepath.Join(setting.CustomPath, "options", "locale"), callback); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to walk locales. Error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readme reads the content of a specific readme from bindata or custom path.
|
||||
func Readme(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("readme", name))
|
||||
}
|
||||
|
||||
// Gitignore reads the content of a gitignore locale from bindata or custom path.
|
||||
func Gitignore(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("gitignore", name))
|
||||
}
|
||||
|
||||
// License reads the content of a specific license from bindata or custom path.
|
||||
func License(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("license", name))
|
||||
}
|
||||
|
||||
// Labels reads the content of a specific labels from static or custom path.
|
||||
func Labels(name string) ([]byte, error) {
|
||||
return fileFromDir(path.Join("label", name))
|
||||
}
|
||||
|
||||
// fileFromDir is a helper to read files from bindata or custom path.
|
||||
func fileFromDir(name string) ([]byte, error) {
|
||||
customPath := path.Join(setting.CustomPath, "options", name)
|
||||
|
@ -6,7 +6,6 @@ package public
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -14,6 +13,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// Options represents the available options to configure the handler.
|
||||
@ -103,7 +103,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) {
|
||||
|
||||
func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
|
||||
// use clean to keep the file is a valid path with no . or ..
|
||||
f, err := fs.Open(path.Clean(file))
|
||||
f, err := fs.Open(util.CleanPath(file))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
|
@ -136,7 +136,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
|
||||
}
|
||||
|
||||
// README
|
||||
data, err := options.GetRepoInitFile("readme", opts.Readme)
|
||||
data, err := options.Readme(opts.Readme)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err)
|
||||
}
|
||||
@ -164,7 +164,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
|
||||
var buf bytes.Buffer
|
||||
names := strings.Split(opts.Gitignores, ",")
|
||||
for _, name := range names {
|
||||
data, err = options.GetRepoInitFile("gitignore", name)
|
||||
data, err = options.Gitignore(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err)
|
||||
}
|
||||
@ -182,7 +182,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir,
|
||||
|
||||
// LICENSE
|
||||
if len(opts.License) > 0 {
|
||||
data, err = options.GetRepoInitFile("license", opts.License)
|
||||
data, err = options.License(opts.License)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err)
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@ -59,7 +58,7 @@ func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
|
||||
}
|
||||
|
||||
func (l *LocalStorage) buildLocalPath(p string) string {
|
||||
return filepath.Join(l.dir, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:])
|
||||
return filepath.Join(l.dir, util.CleanPath(strings.ReplaceAll(p, "\\", "/")))
|
||||
}
|
||||
|
||||
// Open a file
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
@ -120,7 +121,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
|
||||
}
|
||||
|
||||
func (m *MinioStorage) buildMinioPath(p string) string {
|
||||
return strings.TrimPrefix(path.Join(m.basePath, path.Clean("/" + strings.ReplaceAll(p, "\\", "/"))[1:]), "/")
|
||||
return strings.TrimPrefix(path.Join(m.basePath, util.CleanPath(strings.ReplaceAll(p, "\\", "/"))), "/")
|
||||
}
|
||||
|
||||
// Open open a file
|
||||
|
@ -14,6 +14,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CleanPath ensure to clean the path
|
||||
func CleanPath(p string) string {
|
||||
if strings.HasPrefix(p, "/") {
|
||||
return path.Clean(p)
|
||||
}
|
||||
return path.Clean("/" + p)[1:]
|
||||
}
|
||||
|
||||
// EnsureAbsolutePath ensure that a path is absolute, making it
|
||||
// relative to absoluteBase if necessary
|
||||
func EnsureAbsolutePath(path, absoluteBase string) string {
|
||||
|
@ -136,3 +136,15 @@ func TestMisc_IsReadmeFileName(t *testing.T) {
|
||||
assert.Equal(t, testCase.idx, idx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPath(t *testing.T) {
|
||||
cases := map[string]string{
|
||||
"../../test": "test",
|
||||
"/test": "/test",
|
||||
"/../test": "/test",
|
||||
}
|
||||
|
||||
for k, v := range cases {
|
||||
assert.Equal(t, v, CleanPath(k))
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ new_mirror=新鏡像
|
||||
new_fork=新增儲存庫 fork
|
||||
new_org=新增組織
|
||||
new_project=新增專案
|
||||
new_project_column=新增欄位
|
||||
manage_org=管理組織
|
||||
admin_panel=網站管理
|
||||
account_settings=帳戶設定
|
||||
@ -90,9 +91,11 @@ disabled=已停用
|
||||
|
||||
copy=複製
|
||||
copy_url=複製 URL
|
||||
copy_content=複製內容
|
||||
copy_branch=複製分支名稱
|
||||
copy_success=複製成功!
|
||||
copy_error=複製失敗
|
||||
copy_type_unsupported=無法複製此類型的檔案
|
||||
|
||||
write=撰寫
|
||||
preview=預覽
|
||||
@ -109,8 +112,14 @@ never=從來沒有
|
||||
rss_feed=RSS 摘要
|
||||
|
||||
[aria]
|
||||
navbar=導航列
|
||||
footer=頁尾
|
||||
footer.software=關於軟體
|
||||
footer.links=連結
|
||||
|
||||
[filter]
|
||||
string.asc=A - Z
|
||||
string.desc=Z - A
|
||||
|
||||
[error]
|
||||
occurred=發生錯誤
|
||||
@ -238,7 +247,10 @@ default_enable_timetracking_popup=預設情況下啟用新存儲庫的時間跟
|
||||
no_reply_address=隱藏電子信箱域名
|
||||
no_reply_address_helper=作為隱藏電子信箱使用者的域名。例如,如果隱藏的電子信箱域名設定為「noreply.example.org」,帳號「joe」將以「joe@noreply.example.org」的身分登錄到 Git 中。
|
||||
password_algorithm=密碼雜湊演算法
|
||||
invalid_password_algorithm=無效的密碼雜湊演算法
|
||||
password_algorithm_helper=設定密碼雜湊演算法。演算法有不同的需求和強度。「argon2」雖然有優秀的特性但會占用大量記憶體,所以可能不適用於小型系統。
|
||||
enable_update_checker=啟用更新檢查器
|
||||
enable_update_checker_helper=定期連線到 gitea.io 檢查更新。
|
||||
|
||||
[home]
|
||||
uname_holder=帳號或電子信箱
|
||||
@ -285,6 +297,8 @@ org_no_results=沒有找到符合的組織。
|
||||
code_no_results=找不到符合您關鍵字的原始碼。
|
||||
code_search_results=「%s」的搜尋結果
|
||||
code_last_indexed_at=最後索引 %s
|
||||
relevant_repositories_tooltip=已隱藏缺少主題、圖示、說明、Fork 的儲存庫。
|
||||
relevant_repositories=只顯示相關的儲存庫,<a href="%s">顯示未篩選的結果</a>。
|
||||
|
||||
|
||||
[auth]
|
||||
@ -357,6 +371,7 @@ password_pwned_err=無法完成對 HaveIBeenPwned 的請求。
|
||||
|
||||
[mail]
|
||||
view_it_on=在 %s 上查看
|
||||
reply=或是直接回覆此電子郵件
|
||||
link_not_working_do_paste=無法開啟?請複製超連結到瀏覽器貼上。
|
||||
hi_user_x=<b>%s</b> 您好,
|
||||
|
||||
@ -491,6 +506,7 @@ user_not_exist=該用戶名不存在
|
||||
team_not_exist=團隊不存在
|
||||
last_org_owner=你不能從「Owners」團隊中刪除最後一個使用者。每個組織中至少要有一個擁有者。
|
||||
cannot_add_org_to_team=組織不能被新增為團隊成員。
|
||||
organization_leave_success=您已成功離開組織 %s。
|
||||
|
||||
invalid_ssh_key=無法驗證您的 SSH 密鑰:%s
|
||||
invalid_gpg_key=無法驗證您的 GPG 密鑰:%s
|
||||
@ -1002,10 +1018,12 @@ unstar=移除星號
|
||||
star=加上星號
|
||||
fork=Fork
|
||||
download_archive=下載此儲存庫
|
||||
more_operations=更多操作
|
||||
|
||||
no_desc=暫無描述
|
||||
quick_guide=快速幫助
|
||||
clone_this_repo=Clone 此儲存庫
|
||||
cite_this_repo=引用此儲存庫
|
||||
create_new_repo_command=從命令列建立新儲存庫。
|
||||
push_exist_repo=從命令列推送已存在的儲存庫
|
||||
empty_message=此儲存庫未包含任何內容。
|
||||
@ -1155,6 +1173,7 @@ commits.signed_by_untrusted_user_unmatched=由不受信任且與提交者不相
|
||||
commits.gpg_key_id=GPG 金鑰 ID
|
||||
commits.ssh_key_fingerprint=SSH 金鑰指紋
|
||||
|
||||
commit.operations=操作
|
||||
commit.revert=還原
|
||||
commit.revert-header=還原: %s
|
||||
commit.revert-content=選擇還原的目標分支:
|
||||
@ -1187,11 +1206,22 @@ projects.type.bug_triage=Bug 檢傷分類
|
||||
projects.template.desc=專案範本
|
||||
projects.template.desc_helper=選擇專案範本以開始
|
||||
projects.type.uncategorized=未分類
|
||||
projects.column.edit=編輯欄位
|
||||
projects.column.edit_title=組織名稱
|
||||
projects.column.new_title=組織名稱
|
||||
projects.column.new_submit=建立欄位
|
||||
projects.column.new=新增欄位
|
||||
projects.column.set_default=設為預設
|
||||
projects.column.set_default_desc=將此欄位設定為未分類問題及合併請求的預設預設值
|
||||
projects.column.delete=刪除欄位
|
||||
projects.column.deletion_desc=刪除專案欄位會將所有相關的問題移動到「未分類」,是否繼續?
|
||||
projects.column.color=彩色
|
||||
projects.open=開啟
|
||||
projects.close=關閉
|
||||
projects.column.assigned_to=已指派給
|
||||
projects.card_type.desc=卡片預覽
|
||||
projects.card_type.images_and_text=圖片和文字
|
||||
projects.card_type.text_only=純文字
|
||||
|
||||
issues.desc=管理錯誤報告、任務和里程碑。
|
||||
issues.filter_assignees=篩選負責人
|
||||
@ -1268,6 +1298,7 @@ issues.filter_label_no_select=所有標籤
|
||||
issues.filter_milestone=里程碑
|
||||
issues.filter_milestone_no_select=所有里程碑
|
||||
issues.filter_project=專案
|
||||
issues.filter_project_all=所有專案
|
||||
issues.filter_project_none=未選擇專案
|
||||
issues.filter_assignee=負責人
|
||||
issues.filter_assginee_no_select=所有負責人
|
||||
@ -1300,6 +1331,8 @@ issues.action_milestone=里程碑
|
||||
issues.action_milestone_no_select=無里程碑
|
||||
issues.action_assignee=負責人
|
||||
issues.action_assignee_no_select=沒有負責人
|
||||
issues.action_check=選取/取消選取
|
||||
issues.action_check_all=全選/取消全選
|
||||
issues.opened_by=建立於 %[1]s 由 <a href="%[2]s">%[3]s</a>
|
||||
pulls.merged_by=由 <a href="%[2]s">%[3]s</a> 建立,合併於 %[1]s
|
||||
pulls.merged_by_fake=由 %[2]s 建立,合併於 %[1]s
|
||||
@ -1798,6 +1831,7 @@ settings.mirror_sync_in_progress=鏡像同步正在進行中。 請稍後再回
|
||||
settings.site=網站
|
||||
settings.update_settings=更新設定
|
||||
settings.branches.update_default_branch=更新預設分支
|
||||
settings.branches.add_new_rule=加入新規則
|
||||
settings.advanced_settings=進階設定
|
||||
settings.wiki_desc=啟用儲存庫 Wiki
|
||||
settings.use_internal_wiki=使用內建 Wiki
|
||||
@ -1827,8 +1861,11 @@ settings.pulls.ignore_whitespace=衝突時忽略空白
|
||||
settings.pulls.enable_autodetect_manual_merge=啟用自動偵測手動合併 (注意: 在某些特殊情況下可能發生誤判)
|
||||
settings.pulls.allow_rebase_update=啟用透過 Rebase 更新合併請求分支
|
||||
settings.pulls.default_delete_branch_after_merge=預設在合併後刪除合併請求分支
|
||||
settings.pulls.default_allow_edits_from_maintainers=預設允許維護者進行編輯
|
||||
settings.releases_desc=啟用儲存庫版本發佈
|
||||
settings.packages_desc=啟用儲存庫套件註冊中心
|
||||
settings.projects_desc=啟用儲存庫專案
|
||||
settings.actions_desc=啟用儲存庫 Actions
|
||||
settings.admin_settings=管理員設定
|
||||
settings.admin_enable_health_check=啟用儲存庫的健康檢查 (git fsck)
|
||||
settings.admin_code_indexer=程式碼索引器
|
||||
@ -2038,6 +2075,8 @@ settings.deploy_key_deletion_desc=移除部署金鑰將拒絕它存取此儲存
|
||||
settings.deploy_key_deletion_success=部署金鑰已移除。
|
||||
settings.branches=分支
|
||||
settings.protected_branch=分支保護
|
||||
settings.protected_branch.save_rule=儲存規則
|
||||
settings.protected_branch.delete_rule=刪除規則
|
||||
settings.protected_branch_can_push=允許推送?
|
||||
settings.protected_branch_can_push_yes=你可以推送
|
||||
settings.protected_branch_can_push_no=你不能推送
|
||||
@ -2072,6 +2111,7 @@ settings.dismiss_stale_approvals=捨棄過時的核可
|
||||
settings.dismiss_stale_approvals_desc=當新的提交有修改到合併請求的內容,並被推送到此分支時,將捨棄舊的核可。
|
||||
settings.require_signed_commits=僅接受經簽署的提交
|
||||
settings.require_signed_commits_desc=拒絕未經簽署或未經驗證的提交推送到此分支。
|
||||
settings.protect_branch_name_pattern=受保護的分支名稱模式
|
||||
settings.protect_protected_file_patterns=受保護的檔案模式 (以分號區隔「\;」):
|
||||
settings.protect_protected_file_patterns_desc=即便使用者有權限新增、修改、刪除此分支的檔案,仍不允許直接修改受保護的檔案。可以用半形分號「\;」分隔多個模式。請於<a href="https://pkg.go.dev/github.com/gobwas/glob#Compile">github.com/gobwas/glob</a> 文件查看模式格式。範例:<code>.drone.yml</code>, <code>/docs/**/*.txt</code>。
|
||||
settings.protect_unprotected_file_patterns=未受保護的檔案模式 (以分號區隔「\;」):
|
||||
@ -2080,6 +2120,7 @@ settings.add_protected_branch=啟用保護
|
||||
settings.delete_protected_branch=停用保護
|
||||
settings.update_protect_branch_success=已更新「%s」的分支保護。
|
||||
settings.remove_protected_branch_success=已停用「%s」的分支保護。
|
||||
settings.remove_protected_branch_failed=刪除分支保護規則「%s」失敗。
|
||||
settings.protected_branch_deletion=停用分支保護
|
||||
settings.protected_branch_deletion_desc=停用分支保護將允許有寫入權限的使用者推送至該分支,是否繼續?
|
||||
settings.block_rejected_reviews=有退回的審核時阻擋合併
|
||||
@ -2089,9 +2130,13 @@ settings.block_on_official_review_requests_desc=如果有官方的審核請求
|
||||
settings.block_outdated_branch=如果合併請求已經過時則阻擋合併
|
||||
settings.block_outdated_branch_desc=當 head 分支落後於基礎分支時不得合併。
|
||||
settings.default_branch_desc=請選擇用來提交程式碼和合併請求的預設分支。
|
||||
settings.merge_style_desc=合併方式
|
||||
settings.default_merge_style_desc=預設合併方式
|
||||
settings.choose_branch=選擇一個分支...
|
||||
settings.no_protected_branch=沒有受保護的分支。
|
||||
settings.edit_protected_branch=編輯
|
||||
settings.protected_branch_required_rule_name=必須填寫規則名稱
|
||||
settings.protected_branch_duplicate_rule_name=規則名稱已存在
|
||||
settings.protected_branch_required_approvals_min=需要的核可數量不能為負數。
|
||||
settings.tags=標籤
|
||||
settings.tags.protection=標籤保護
|
||||
@ -2247,6 +2292,8 @@ release.downloads=下載附件
|
||||
release.download_count=下載次數:%s
|
||||
release.add_tag_msg=使用此版本的標題和內容作為標籤訊息。
|
||||
release.add_tag=只建立標籤
|
||||
release.releases_for=%s 的版本發佈
|
||||
release.tags_for=%s 的標籤
|
||||
|
||||
branch.name=分支名稱
|
||||
branch.search=搜尋分支
|
||||
@ -2305,7 +2352,7 @@ org_full_name_holder=組織全名
|
||||
org_name_helper=組織名稱應該要簡短且方便記憶
|
||||
create_org=建立組織
|
||||
repo_updated=更新於
|
||||
members=成員數
|
||||
members=成員
|
||||
teams=團隊
|
||||
code=程式碼
|
||||
lower_members=名成員
|
||||
@ -2957,6 +3004,7 @@ monitor.queue.pool.cancel_desc=讓佇列沒有任何工作者群組可能造成
|
||||
|
||||
notices.system_notice_list=系統提示
|
||||
notices.view_detail_header=查看提示細節
|
||||
notices.operations=操作
|
||||
notices.select_all=選取全部
|
||||
notices.deselect_all=取消所有選取
|
||||
notices.inverse_selection=反向選取
|
||||
@ -3081,6 +3129,8 @@ keywords=關鍵字
|
||||
details=詳情
|
||||
details.author=作者
|
||||
details.project_site=專案網站
|
||||
details.repository_site=儲存庫網站
|
||||
details.documentation_site=文件網站
|
||||
details.license=授權條款
|
||||
assets=檔案
|
||||
versions=版本
|
||||
@ -3088,7 +3138,14 @@ versions.on=於
|
||||
versions.view_all=檢視全部
|
||||
dependency.id=ID
|
||||
dependency.version=版本
|
||||
cargo.registry=在 Cargo 組態檔設定此註冊中心 (例如: <code>~/.cargo/config.toml</code>):
|
||||
cargo.install=執行下列命令以使用 Cargo 安裝此套件:
|
||||
cargo.documentation=關於 Cargo registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/cargo/">說明文件</a>。
|
||||
cargo.details.repository_site=儲存庫網站
|
||||
cargo.details.documentation_site=文件網站
|
||||
chef.registry=在您的 <code>~/.chef/config.rb</code> 檔設定此註冊中心:
|
||||
chef.install=執行下列命令安裝此套件:
|
||||
chef.documentation=關於 Chef registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/chef/">說明文件</a>。
|
||||
composer.registry=在您的 <code>~/.composer/config.json</code> 檔設定此註冊中心:
|
||||
composer.install=執行下列命令以使用 Composer 安裝此套件:
|
||||
composer.documentation=關於 Composer registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/composer/">說明文件</a>。
|
||||
@ -3098,6 +3155,11 @@ conan.details.repository=儲存庫
|
||||
conan.registry=透過下列命令設定此註冊中心:
|
||||
conan.install=執行下列命令以使用 Conan 安裝此套件:
|
||||
conan.documentation=關於 Conan registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">說明文件</a>。
|
||||
conda.registry=在您的 <code>.condarc</code> 檔設定此註冊中心為 Conda 存儲庫:
|
||||
conda.install=執行下列命令以使用 Conda 安裝此套件:
|
||||
conda.documentation=關於 Conda registry 的詳情請參閱<a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conda/">說明文件</a>。
|
||||
conda.details.repository_site=儲存庫網站
|
||||
conda.details.documentation_site=文件網站
|
||||
container.details.type=映像檔類型
|
||||
container.details.platform=平台
|
||||
container.pull=透過下列命令拉取映像檔:
|
||||
@ -3156,7 +3218,27 @@ settings.delete.description=刪除套件是永久且不可還原的。
|
||||
settings.delete.notice=您正要刪除 %s (%s),此動作是無法還原的,您確定嗎?
|
||||
settings.delete.success=已刪除該套件。
|
||||
settings.delete.error=刪除套件失敗。
|
||||
owner.settings.cargo.initialize=初始化索引
|
||||
owner.settings.cargo.rebuild=重建索引
|
||||
owner.settings.cleanuprules.title=管理清理規則
|
||||
owner.settings.cleanuprules.add=加入清理規則
|
||||
owner.settings.cleanuprules.edit=編輯清理規則
|
||||
owner.settings.cleanuprules.none=沒有可用的清理規則。閱讀文件以了解更多。
|
||||
owner.settings.cleanuprules.preview=清理規則預覽
|
||||
owner.settings.cleanuprules.preview.none=清理規則不符合任何套件。
|
||||
owner.settings.cleanuprules.enabled=已啟用
|
||||
owner.settings.cleanuprules.pattern_full_match=將比對規則套用到完整的套件名稱
|
||||
owner.settings.cleanuprules.keep.title=符合這些規則的版本即使符合下面的刪除規則也會被保留。
|
||||
owner.settings.cleanuprules.keep.count=保留最新的
|
||||
owner.settings.cleanuprules.keep.count.1=每個套件 1 個版本
|
||||
owner.settings.cleanuprules.keep.count.n=每個套件 %d 個版本
|
||||
owner.settings.cleanuprules.keep.pattern=保留版本的比對規則
|
||||
owner.settings.cleanuprules.keep.pattern.container=Container 套件的<code>最新</code>版本總是會保留。
|
||||
owner.settings.cleanuprules.remove.title=符合這些規則的版本將被移除,除非前述的規則要求保留它們。
|
||||
owner.settings.cleanuprules.remove.days=移除早於天數的版本
|
||||
owner.settings.cleanuprules.remove.pattern=移除版本的比對規則
|
||||
owner.settings.cleanuprules.success.update=已更新清理規則。
|
||||
owner.settings.cleanuprules.success.delete=已刪除清理規則。
|
||||
|
||||
[secrets]
|
||||
value=值
|
||||
@ -3166,16 +3248,23 @@ name=組織名稱
|
||||
|
||||
|
||||
|
||||
runners.status=狀態
|
||||
runners.id=ID
|
||||
runners.name=組織名稱
|
||||
runners.owner_type=認證類型
|
||||
runners.description=組織描述
|
||||
runners.labels=標籤
|
||||
runners.agent_labels=代理程式標籤
|
||||
runners.custom_labels=自訂標籤
|
||||
runners.task_list.run=執行
|
||||
runners.task_list.status=狀態
|
||||
runners.task_list.repository=儲存庫
|
||||
runners.task_list.commit=提交
|
||||
runners.task_list.done_at=完成於
|
||||
runners.status.active=啟用
|
||||
|
||||
runs.open_tab=%d 開放中
|
||||
runs.closed_tab=%d 已關閉
|
||||
runs.commit=提交
|
||||
|
||||
|
||||
|
@ -58,8 +58,18 @@ func getNote(ctx *context.APIContext, identifier string) {
|
||||
return
|
||||
}
|
||||
|
||||
commitSHA, err := ctx.Repo.GitRepo.ConvertToSHA1(identifier)
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(err)
|
||||
} else {
|
||||
ctx.Error(http.StatusInternalServerError, "ConvertToSHA1", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var note git.Note
|
||||
if err := git.GetNote(ctx, ctx.Repo.GitRepo, identifier, ¬e); err != nil {
|
||||
if err := git.GetNote(ctx, ctx.Repo.GitRepo, commitSHA.String(), ¬e); err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound(identifier)
|
||||
return
|
||||
|
@ -64,7 +64,7 @@ func installRecovery(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
"SignedUserName": "",
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
|
||||
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
if !setting.IsProd {
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
"code.gitea.io/gitea/services/auth"
|
||||
@ -44,7 +45,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:]
|
||||
rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/"))
|
||||
|
||||
u, err := objStore.URL(rPath, path.Base(rPath))
|
||||
if err != nil {
|
||||
@ -80,7 +81,7 @@ func storageHandler(storageSetting setting.Storage, prefix string, objStore stor
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
|
||||
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
|
||||
rPath = path.Clean("/" + strings.ReplaceAll(rPath, "\\", "/"))[1:]
|
||||
rPath = util.CleanPath(strings.ReplaceAll(rPath, "\\", "/"))
|
||||
if rPath == "" {
|
||||
http.Error(w, "file not found", http.StatusNotFound)
|
||||
return
|
||||
@ -158,7 +159,7 @@ func Recovery(ctx goctx.Context) func(next http.Handler) http.Handler {
|
||||
store["SignedUserName"] = ""
|
||||
}
|
||||
|
||||
httpcache.AddCacheControlToHeader(w.Header(), 0, "no-transform")
|
||||
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform")
|
||||
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
if !setting.IsProd {
|
||||
|
@ -726,7 +726,7 @@ func UploadFilePost(ctx *context.Context) {
|
||||
|
||||
func cleanUploadFileName(name string) string {
|
||||
// Rebase the filename
|
||||
name = strings.Trim(path.Clean("/"+name), "/")
|
||||
name = strings.Trim(util.CleanPath(name), "/")
|
||||
// Git disallows any filenames to have a .git directory in them.
|
||||
for _, part := range strings.Split(name, "/") {
|
||||
if strings.ToLower(part) == ".git" {
|
||||
|
@ -207,7 +207,7 @@ func LFSLockFile(ctx *context.Context) {
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
|
||||
return
|
||||
}
|
||||
lockPath = path.Clean("/" + lockPath)[1:]
|
||||
lockPath = util.CleanPath(lockPath)
|
||||
if len(lockPath) == 0 {
|
||||
ctx.Flash.Error(ctx.Tr("repo.settings.lfs_invalid_locking_path", originalPath))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/lfs/locks")
|
||||
|
@ -17,7 +17,7 @@ func cacheableRedirect(ctx *context.Context, location string) {
|
||||
// here we should not use `setting.StaticCacheTime`, it is pretty long (default: 6 hours)
|
||||
// we must make sure the redirection cache time is short enough, otherwise a user won't see the updated avatar in 6 hours
|
||||
// it's OK to make the cache time short, it is only a redirection, and doesn't cost much to make a new request
|
||||
httpcache.AddCacheControlToHeader(ctx.Resp.Header(), 5*time.Minute)
|
||||
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 5*time.Minute)
|
||||
ctx.Redirect(location)
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -30,6 +29,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/uri"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/pull"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@ -866,7 +866,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
|
||||
}
|
||||
|
||||
// SECURITY: The TreePath must be cleaned!
|
||||
comment.TreePath = path.Clean("/" + comment.TreePath)[1:]
|
||||
comment.TreePath = util.CleanPath(comment.TreePath)
|
||||
|
||||
var patch string
|
||||
reader, writer := io.Pipe()
|
||||
|
@ -8,13 +8,13 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
packages_model "code.gitea.io/gitea/models/packages"
|
||||
packages_module "code.gitea.io/gitea/modules/packages"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -33,7 +33,7 @@ type BlobUploader struct {
|
||||
}
|
||||
|
||||
func buildFilePath(id string) string {
|
||||
return filepath.Join(setting.Packages.ChunkedUploadPath, path.Clean("/" + strings.ReplaceAll(id, "\\", "/"))[1:])
|
||||
return filepath.Join(setting.Packages.ChunkedUploadPath, util.CleanPath(strings.ReplaceAll(id, "\\", "/")))
|
||||
}
|
||||
|
||||
// NewBlobUploader creates a new blob uploader for the given id
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -15,6 +14,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// GetFileResponseFromCommit Constructs a FileResponse from a Commit object
|
||||
@ -129,7 +129,7 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m
|
||||
// CleanUploadFileName Trims a filename and returns empty string if it is a .git directory
|
||||
func CleanUploadFileName(name string) string {
|
||||
// Rebase the filename
|
||||
name = strings.Trim(path.Clean("/"+name), "/")
|
||||
name = strings.Trim(util.CleanPath(name), "/")
|
||||
// Git disallows any filenames to have a .git directory in them.
|
||||
for _, part := range strings.Split(name, "/") {
|
||||
if strings.ToLower(part) == ".git" {
|
||||
|
@ -80,7 +80,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
|
||||
|
||||
ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PushUpdates: %s/%s", optsList[0].RepoUserName, optsList[0].RepoName))
|
||||
defer finished()
|
||||
ctx = cache.WithCacheContext(ctx)
|
||||
|
||||
repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, optsList[0].RepoUserName, optsList[0].RepoName)
|
||||
if err != nil {
|
||||
|
@ -5,12 +5,10 @@
|
||||
<span class="text truncated-item-container">
|
||||
{{avatar $.Context .ContextUser}}
|
||||
<span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span>
|
||||
{{if .ContextUser.IsOrganization}}
|
||||
<span class="org-visibility">
|
||||
{{if .ContextUser.Visibility.IsLimited}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
|
||||
{{if .ContextUser.Visibility.IsPrivate}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
|
||||
</span>
|
||||
{{end}}
|
||||
<span class="org-visibility">
|
||||
{{if .ContextUser.Visibility.IsLimited}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
|
||||
{{if .ContextUser.Visibility.IsPrivate}}<div class="ui basic tiny horizontal label">{{.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
|
||||
</span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
</span>
|
||||
<div class="context user overflow menu" tabindex="-1">
|
||||
@ -21,6 +19,10 @@
|
||||
<a class="{{if eq .ContextUser.ID .SignedUser.ID}}active selected{{end}} item truncated-item-container" href="{{AppSubUrl}}/{{if .PageIsIssues}}issues{{else if .PageIsPulls}}pulls{{else if .PageIsMilestonesDashboard}}milestones{{end}}">
|
||||
{{avatar $.Context .SignedUser}}
|
||||
<span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
|
||||
<span class="org-visibility">
|
||||
{{if .ContextUser.Visibility.IsLimited}}<div class="ui basic tiny horizontal label">{{$.locale.Tr "org.settings.visibility.limited_shortname"}}</div>{{end}}
|
||||
{{if .ContextUser.Visibility.IsPrivate}}<div class="ui basic tiny horizontal label">{{$.locale.Tr "org.settings.visibility.private_shortname"}}</div>{{end}}
|
||||
</span>
|
||||
</a>
|
||||
{{range .Orgs}}
|
||||
<a class="{{if eq $.ContextUser.ID .ID}}active selected{{end}} item truncated-item-container" title="{{.Name}}" href="{{.OrganisationLink}}/{{if $.PageIsIssues}}issues{{else if $.PageIsPulls}}pulls{{else if $.PageIsMilestonesDashboard}}milestones{{else}}dashboard{{end}}">
|
||||
|
1
tests/gitea-repositories-meta/user2/readme-test.git/HEAD
Normal file
1
tests/gitea-repositories-meta/user2/readme-test.git/HEAD
Normal file
@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
@ -0,0 +1,4 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
@ -0,0 +1,21 @@
|
||||
ea9ef877d1d88af76682d8798418081264f10cfc refs/heads/fallbacks
|
||||
0d4c14db927c9ffba01fa7e126cc748b5c02c01e refs/heads/fallbacks2
|
||||
c66d5b07c2063d3268707f22226c708b589574ef refs/heads/fallbacks3
|
||||
89f8426e9eb5eff35c09b3565836c8f8e15d0ce9 refs/heads/fallbacks4
|
||||
b0e902496eae435ad03c92a5d479f916ef2d4893 refs/heads/fallbacks5
|
||||
84a5500b5cc040b11daf53fc42c542a99589dc76 refs/heads/fallbacks6
|
||||
cf406a96e416d7de5c4c1bbfffdd672300c822bf refs/heads/fallbacks7
|
||||
0d6ac644b969e9199915a492da9dba08c179fd23 refs/heads/fallbacks8
|
||||
5038febc0c57215beb3748d7ae4091a25a4acc93 refs/heads/fallbacks9
|
||||
9134e1f178ca4cccf1a197142646f2d7627e8cd5 refs/heads/i18n
|
||||
744d2441e55bc0010d6b340d303f0106a627ad29 refs/heads/master
|
||||
3c492566170b057e962c025515ab38bbd7444077 refs/heads/plain
|
||||
3882d6373a0882a6739b3cd9b24d21c630621234 refs/heads/sp-ace
|
||||
bf5ed898252eaa50dcc01108ed4417c3ea98a294 refs/heads/special-subdir-.gitea
|
||||
c03543573ab088ce1cf7090a387d2be621426234 refs/heads/special-subdir-.github
|
||||
e75957ad9b7e6ed16dda183529ec283db0bbc5fe refs/heads/special-subdir-docs
|
||||
46f5d5ab33d701642e08c713fab42af89fdd4fea refs/heads/special-subdir-nested
|
||||
9c0f872256b839c2b97ec22fd348d87b14045513 refs/heads/subdir
|
||||
d7a854fff61e45b98234d7aa79ecbcb1619cd3dd refs/heads/symlink
|
||||
30b9c0ed4b1039dbd99f3fb537b84ca507e0549d refs/heads/symlink-loop
|
||||
41489b7be5c2244d2b7b524dcb31caf3bd1f9ccc refs/heads/txt
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
x<01>Ž;Â0@™s
|
||||
_€*NÒÄH±±1q×u(¢?•tàöTˆ0¾á==™†áQÀ·+‹*4¤Ìd¹¥h“SÌη.z¢à°¢Í™Z3ó¢ctˆ<74>0'<27>As“5hÉzL¶=D¡ÌB˜\cx-Ý´Àõ!O¸¬›ÚéÇqÃêþÃó<õï¡ô•ð 0¦T×装½5ò=-›õÃÜôU sß7,Oó#ÆMÜ
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
||||
x<01>ÎM †aל‚Ø0&ƸsçÊØ¦ô'•.¼½Õx—ïâùòñ4]•õ®.)ÉDÖñQÅ|@b6Xbdƒì}Ö2+b¦%<25>Tè ƒI>g<> 2<>7QÇÀˆ. (c µ¶Ó"o÷òºn´M‹<<3C>[6<>_^橼†Z¦³Tç¬Uƹ øû´nêÿ
qOÏ*3•ˆ{ñ™²N\
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
x<01>Ž;Â0@™s
|
||||
_€*N뤕bccâŽëЊþTÒ<54>ÛS!NÀø†÷ôdÇ>ƒ«Ü!¯ªÖ„LuUlÙ#qô¡´”líQÅ,¼ê”¡Œ®lCBn$6¶’XùDɹàbbÒ–R0ÅÆð–»y…[/O¸n»Úé
|
||||
§iÇâñÃË2ï1…ðЇ@„e<E2809E>p´d‘ïiÞÿ殯‰‡!²<Í™ÜN—
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
x<01>ÎM
|
||||
1†a×=E. ¤mšŽ âÎ<C3A2>wh›¨ƒó#µ"ÞÞA<<3C>Ëgñ~|eǾ<C387>#»jU:×ÙØ–$%ž9o‰Ø{µ9F ™Õ£QdsOU§‘H‘ÕrA´(œ=¡xôçEœØÅ$nkÒ³]ç
|
||||
§¾Üàø\Ò«VØM7—÷yx<79>mØ”´Ë1ê-¬1 šò}Ú–êÿ
3Ì/%=úf>&L¨
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
P pack-8933bd634b76f8154310cccb52537a0195e43166.pack
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,22 @@
|
||||
# pack-refs with: peeled fully-peeled sorted
|
||||
ea9ef877d1d88af76682d8798418081264f10cfc refs/heads/fallbacks
|
||||
0d4c14db927c9ffba01fa7e126cc748b5c02c01e refs/heads/fallbacks2
|
||||
c66d5b07c2063d3268707f22226c708b589574ef refs/heads/fallbacks3
|
||||
89f8426e9eb5eff35c09b3565836c8f8e15d0ce9 refs/heads/fallbacks4
|
||||
b0e902496eae435ad03c92a5d479f916ef2d4893 refs/heads/fallbacks5
|
||||
84a5500b5cc040b11daf53fc42c542a99589dc76 refs/heads/fallbacks6
|
||||
cf406a96e416d7de5c4c1bbfffdd672300c822bf refs/heads/fallbacks7
|
||||
0d6ac644b969e9199915a492da9dba08c179fd23 refs/heads/fallbacks8
|
||||
5038febc0c57215beb3748d7ae4091a25a4acc93 refs/heads/fallbacks9
|
||||
9134e1f178ca4cccf1a197142646f2d7627e8cd5 refs/heads/i18n
|
||||
744d2441e55bc0010d6b340d303f0106a627ad29 refs/heads/master
|
||||
3c492566170b057e962c025515ab38bbd7444077 refs/heads/plain
|
||||
3882d6373a0882a6739b3cd9b24d21c630621234 refs/heads/sp-ace
|
||||
bf5ed898252eaa50dcc01108ed4417c3ea98a294 refs/heads/special-subdir-.gitea
|
||||
c03543573ab088ce1cf7090a387d2be621426234 refs/heads/special-subdir-.github
|
||||
e75957ad9b7e6ed16dda183529ec283db0bbc5fe refs/heads/special-subdir-docs
|
||||
46f5d5ab33d701642e08c713fab42af89fdd4fea refs/heads/special-subdir-nested
|
||||
9c0f872256b839c2b97ec22fd348d87b14045513 refs/heads/subdir
|
||||
d7a854fff61e45b98234d7aa79ecbcb1619cd3dd refs/heads/symlink
|
||||
30b9c0ed4b1039dbd99f3fb537b84ca507e0549d refs/heads/symlink-loop
|
||||
41489b7be5c2244d2b7b524dcb31caf3bd1f9ccc refs/heads/txt
|
@ -0,0 +1 @@
|
||||
fe495ea336f079ef2bed68648d0ba9a37cdbd4aa
|
@ -257,6 +257,111 @@ func TestViewRepoDirectory(t *testing.T) {
|
||||
assert.Zero(t, repoSummary.Length())
|
||||
}
|
||||
|
||||
// ensure that the all the different ways to find and render a README work
|
||||
func TestViewRepoDirectoryReadme(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// there are many combinations:
|
||||
// - READMEs can be .md, .txt, or have no extension
|
||||
// - READMEs can be tagged with a language and even a country code
|
||||
// - READMEs can be stored in docs/, .gitea/, or .github/
|
||||
// - READMEs can be symlinks to other files
|
||||
// - READMEs can be broken symlinks which should not render
|
||||
//
|
||||
// this doesn't cover all possible cases, just the major branches of the code
|
||||
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
check := func(name, url, expectedFilename, expectedReadmeType, expectedContent string) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", url)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
readmeName := htmlDoc.doc.Find("h4.file-header")
|
||||
readmeContent := htmlDoc.doc.Find(".file-view") // TODO: add a id="readme" to the output to make this test more precise
|
||||
readmeType, _ := readmeContent.Attr("class")
|
||||
|
||||
assert.Equal(t, expectedFilename, strings.TrimSpace(readmeName.Text()))
|
||||
assert.Contains(t, readmeType, expectedReadmeType)
|
||||
assert.Contains(t, readmeContent.Text(), expectedContent)
|
||||
})
|
||||
}
|
||||
|
||||
// viewing the top level
|
||||
check("Home", "/user2/readme-test/", "README.md", "markdown", "The cake is a lie.")
|
||||
|
||||
// viewing different file extensions
|
||||
check("md", "/user2/readme-test/src/branch/master/", "README.md", "markdown", "The cake is a lie.")
|
||||
check("txt", "/user2/readme-test/src/branch/txt/", "README.txt", "plain-text", "My spoon is too big.")
|
||||
check("plain", "/user2/readme-test/src/branch/plain/", "README", "plain-text", "Birken my stocks gee howdy")
|
||||
check("i18n", "/user2/readme-test/src/branch/i18n/", "README.zh.md", "markdown", "蛋糕是一个谎言")
|
||||
|
||||
// viewing different subdirectories
|
||||
check("subdir", "/user2/readme-test/src/branch/subdir/libcake", "README.md", "markdown", "Four pints of sugar.")
|
||||
check("docs-direct", "/user2/readme-test/src/branch/special-subdir-docs/docs/", "README.md", "markdown", "This is in docs/")
|
||||
check("docs", "/user2/readme-test/src/branch/special-subdir-docs/", "docs/README.md", "markdown", "This is in docs/")
|
||||
check(".gitea", "/user2/readme-test/src/branch/special-subdir-.gitea/", ".gitea/README.md", "markdown", "This is in .gitea/")
|
||||
check(".github", "/user2/readme-test/src/branch/special-subdir-.github/", ".github/README.md", "markdown", "This is in .github/")
|
||||
|
||||
// symlinks
|
||||
// symlinks are subtle:
|
||||
// - they should be able to handle going a reasonable number of times up and down in the tree
|
||||
// - they shouldn't get stuck on link cycles
|
||||
// - they should determine the filetype based on the name of the link, not the target
|
||||
check("symlink", "/user2/readme-test/src/branch/symlink/", "README.md", "markdown", "This is in some/other/path")
|
||||
check("symlink-multiple", "/user2/readme-test/src/branch/symlink/some/", "README.txt", "plain-text", "This is in some/other/path")
|
||||
check("symlink-up-and-down", "/user2/readme-test/src/branch/symlink/up/back/down/down", "README.md", "markdown", "It's a me, mario")
|
||||
|
||||
// testing fallback rules
|
||||
// READMEs are searched in this order:
|
||||
// - [README.zh-cn.md, README.zh_cn.md, README.zh.md, README_zh.md, README.md, README.txt, README,
|
||||
// docs/README.zh-cn.md, docs/README.zh_cn.md, docs/README.zh.md, docs/README_zh.md, docs/README.md, docs/README.txt, docs/README,
|
||||
// .gitea/README.zh-cn.md, .gitea/README.zh_cn.md, .gitea/README.zh.md, .gitea/README_zh.md, .gitea/README.md, .gitea/README.txt, .gitea/README,
|
||||
|
||||
// .github/README.zh-cn.md, .github/README.zh_cn.md, .github/README.zh.md, .github/README_zh.md, .github/README.md, .github/README.txt, .github/README]
|
||||
// and a broken/looped symlink counts as not existing at all and should be skipped.
|
||||
// again, this doesn't cover all cases, but it covers a few
|
||||
check("fallback/top", "/user2/readme-test/src/branch/fallbacks/", "README.en.md", "markdown", "This is README.en.md")
|
||||
check("fallback/2", "/user2/readme-test/src/branch/fallbacks2/", "README.md", "markdown", "This is README.md")
|
||||
check("fallback/3", "/user2/readme-test/src/branch/fallbacks3/", "README", "plain-text", "This is README")
|
||||
check("fallback/4", "/user2/readme-test/src/branch/fallbacks4/", "docs/README.en.md", "markdown", "This is docs/README.en.md")
|
||||
check("fallback/5", "/user2/readme-test/src/branch/fallbacks5/", "docs/README.md", "markdown", "This is docs/README.md")
|
||||
check("fallback/6", "/user2/readme-test/src/branch/fallbacks6/", "docs/README", "plain-text", "This is docs/README")
|
||||
check("fallback/7", "/user2/readme-test/src/branch/fallbacks7/", ".gitea/README.en.md", "markdown", "This is .gitea/README.en.md")
|
||||
check("fallback/8", "/user2/readme-test/src/branch/fallbacks8/", ".gitea/README.md", "markdown", "This is .gitea/README.md")
|
||||
check("fallback/9", "/user2/readme-test/src/branch/fallbacks9/", ".gitea/README", "plain-text", "This is .gitea/README")
|
||||
|
||||
// this case tests that broken symlinks count as missing files, instead of rendering their contents
|
||||
check("fallbacks-broken-symlinks", "/user2/readme-test/src/branch/fallbacks-broken-symlinks/", "docs/README", "plain-text", "This is docs/README")
|
||||
|
||||
// some cases that should NOT render a README
|
||||
// - /readme
|
||||
// - /.github/docs/README.md
|
||||
// - a symlink loop
|
||||
|
||||
missing := func(name, url string) {
|
||||
t.Run("missing/"+name, func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
req := NewRequest(t, "GET", url)
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
_, exists := htmlDoc.doc.Find(".file-view").Attr("class")
|
||||
fmt.Printf("%s", resp.Body)
|
||||
|
||||
assert.False(t, exists, "README should not have rendered")
|
||||
})
|
||||
}
|
||||
missing("sp-ace", "/user2/readme-test/src/branch/sp-ace/")
|
||||
missing("nested-special", "/user2/readme-test/src/branch/special-subdir-nested/subproject") // the special subdirs should only trigger on the repo root
|
||||
// missing("special-subdir-nested", "/user2/readme-test/src/branch/special-subdir-nested/") // This is currently FAILING, due to a bug introduced in https://github.com/go-gitea/gitea/pull/22177
|
||||
missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/")
|
||||
}
|
||||
|
||||
func TestMarkDownImage(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user