mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
Merge branch 'main' into zzc/dev/agit_2
This commit is contained in:
commit
1685a35363
@ -324,7 +324,7 @@ rules:
|
|||||||
jquery/no-sizzle: [2]
|
jquery/no-sizzle: [2]
|
||||||
jquery/no-slide: [2]
|
jquery/no-slide: [2]
|
||||||
jquery/no-submit: [2]
|
jquery/no-submit: [2]
|
||||||
jquery/no-text: [0]
|
jquery/no-text: [2]
|
||||||
jquery/no-toggle: [2]
|
jquery/no-toggle: [2]
|
||||||
jquery/no-trigger: [0]
|
jquery/no-trigger: [0]
|
||||||
jquery/no-trim: [2]
|
jquery/no-trim: [2]
|
||||||
@ -477,7 +477,7 @@ rules:
|
|||||||
no-jquery/no-slide: [2]
|
no-jquery/no-slide: [2]
|
||||||
no-jquery/no-sub: [2]
|
no-jquery/no-sub: [2]
|
||||||
no-jquery/no-support: [2]
|
no-jquery/no-support: [2]
|
||||||
no-jquery/no-text: [0]
|
no-jquery/no-text: [2]
|
||||||
no-jquery/no-trigger: [0]
|
no-jquery/no-trigger: [0]
|
||||||
no-jquery/no-trim: [2]
|
no-jquery/no-trim: [2]
|
||||||
no-jquery/no-type: [2]
|
no-jquery/no-type: [2]
|
||||||
@ -798,7 +798,7 @@ rules:
|
|||||||
unicorn/prefer-object-has-own: [0]
|
unicorn/prefer-object-has-own: [0]
|
||||||
unicorn/prefer-optional-catch-binding: [2]
|
unicorn/prefer-optional-catch-binding: [2]
|
||||||
unicorn/prefer-prototype-methods: [0]
|
unicorn/prefer-prototype-methods: [0]
|
||||||
unicorn/prefer-query-selector: [0]
|
unicorn/prefer-query-selector: [2]
|
||||||
unicorn/prefer-reflect-apply: [0]
|
unicorn/prefer-reflect-apply: [0]
|
||||||
unicorn/prefer-regexp-test: [2]
|
unicorn/prefer-regexp-test: [2]
|
||||||
unicorn/prefer-set-has: [0]
|
unicorn/prefer-set-has: [0]
|
||||||
|
11
.github/pull_request_template.md
vendored
11
.github/pull_request_template.md
vendored
@ -1,9 +1,10 @@
|
|||||||
<!-- start tips -->
|
<!-- start tips -->
|
||||||
Please check the following:
|
Please check the following:
|
||||||
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
1. Make sure you are targeting the `main` branch, pull requests on release branches are only allowed for backports.
|
||||||
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
2. Make sure you have read contributing guidelines: https://github.com/go-gitea/gitea/blob/main/CONTRIBUTING.md .
|
||||||
3. Describe what your pull request does and which issue you're targeting (if any).
|
3. For documentations contribution, please go to https://gitea.com/gitea/docs
|
||||||
4. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
4. Describe what your pull request does and which issue you're targeting (if any).
|
||||||
5. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
5. It is recommended to enable "Allow edits by maintainers", so maintainers can help more easily.
|
||||||
6. Delete all these tips before posting.
|
6. Your input here will be included in the commit message when this PR has been merged. If you don't want some content to be included, please separate them with a line like `---`.
|
||||||
|
7. Delete all these tips before posting.
|
||||||
<!-- end tips -->
|
<!-- end tips -->
|
||||||
|
@ -22,6 +22,7 @@ linters:
|
|||||||
- typecheck
|
- typecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unused
|
- unused
|
||||||
|
- unparam
|
||||||
- wastedassign
|
- wastedassign
|
||||||
|
|
||||||
run:
|
run:
|
||||||
|
@ -358,7 +358,8 @@ $REWRITTEN_PR_SUMMARY
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in the same PR.
|
If you add a new feature or change an existing aspect of Gitea, the documentation for that feature must be created or updated in another PR at [https://gitea.com/gitea/docs](https://gitea.com/gitea/docs).
|
||||||
|
**The docs directory on main repository will be removed at some time. We will have a yaml file to store configuration file's meta data. After that completed, configuration documentation should be in the main repository.**
|
||||||
|
|
||||||
## API v1
|
## API v1
|
||||||
|
|
||||||
|
4
Makefile
4
Makefile
@ -878,7 +878,7 @@ node_modules: package-lock.json
|
|||||||
@touch node_modules
|
@touch node_modules
|
||||||
|
|
||||||
.venv: poetry.lock
|
.venv: poetry.lock
|
||||||
poetry install --no-root
|
poetry install
|
||||||
@touch .venv
|
@touch .venv
|
||||||
|
|
||||||
.PHONY: update
|
.PHONY: update
|
||||||
@ -895,7 +895,7 @@ update-js: node-check | node_modules
|
|||||||
update-py: node-check | node_modules
|
update-py: node-check | node_modules
|
||||||
npx updates -u -f pyproject.toml
|
npx updates -u -f pyproject.toml
|
||||||
rm -rf .venv poetry.lock
|
rm -rf .venv poetry.lock
|
||||||
poetry install --no-root
|
poetry install
|
||||||
@touch .venv
|
@touch .venv
|
||||||
|
|
||||||
.PHONY: fomantic
|
.PHONY: fomantic
|
||||||
|
@ -81,6 +81,10 @@ RUN_USER = ; git
|
|||||||
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
||||||
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
;;
|
;;
|
||||||
|
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
||||||
|
;; DO NOT USE IT IN PRODUCTION!!!
|
||||||
|
;USE_SUB_URL_PATH = false
|
||||||
|
;;
|
||||||
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
||||||
;STATIC_URL_PREFIX =
|
;STATIC_URL_PREFIX =
|
||||||
;;
|
;;
|
||||||
|
6
flake.lock
generated
6
flake.lock
generated
@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1715534503,
|
"lastModified": 1717974879,
|
||||||
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
|
"narHash": "sha256-GTO3C88+5DX171F/gVS3Qga/hOs/eRMxPFpiHq2t+D8=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
|
"rev": "c7b821ba2e1e635ba5a76d299af62821cbcb09f3",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -18,12 +18,6 @@ const (
|
|||||||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
||||||
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
||||||
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
||||||
SearchOrderBySize SearchOrderBy = "size ASC"
|
|
||||||
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
|
|
||||||
SearchOrderByGitSize SearchOrderBy = "git_size ASC"
|
|
||||||
SearchOrderByGitSizeReverse SearchOrderBy = "git_size DESC"
|
|
||||||
SearchOrderByLFSSize SearchOrderBy = "lfs_size ASC"
|
|
||||||
SearchOrderByLFSSizeReverse SearchOrderBy = "lfs_size DESC"
|
|
||||||
SearchOrderByID SearchOrderBy = "id ASC"
|
SearchOrderByID SearchOrderBy = "id ASC"
|
||||||
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
||||||
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
||||||
|
@ -215,16 +215,15 @@ func fileTimestampToTime(timestamp int64) time.Time {
|
|||||||
return time.UnixMicro(timestamp)
|
return time.UnixMicro(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) loadMetaByPath() (*dbfsMeta, error) {
|
func (f *file) loadMetaByPath() error {
|
||||||
var fileMeta dbfsMeta
|
var fileMeta dbfsMeta
|
||||||
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {
|
if ok, err := db.GetEngine(f.ctx).Where("full_path = ?", f.fullPath).Get(&fileMeta); err != nil {
|
||||||
return nil, err
|
return err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
f.metaID = fileMeta.ID
|
f.metaID = fileMeta.ID
|
||||||
f.blockSize = fileMeta.BlockSize
|
f.blockSize = fileMeta.BlockSize
|
||||||
return &fileMeta, nil
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) open(flag int) (err error) {
|
func (f *file) open(flag int) (err error) {
|
||||||
@ -288,10 +287,7 @@ func (f *file) createEmpty() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = f.loadMetaByPath(); err != nil {
|
return f.loadMetaByPath()
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) truncate() error {
|
func (f *file) truncate() error {
|
||||||
@ -368,8 +364,5 @@ func buildPath(path string) string {
|
|||||||
func newDbFile(ctx context.Context, path string) (*file, error) {
|
func newDbFile(ctx context.Context, path string) (*file, error) {
|
||||||
path = buildPath(path)
|
path = buildPath(path)
|
||||||
f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize}
|
f := &file{ctx: ctx, fullPath: path, blockSize: defaultFileBlockSize}
|
||||||
if _, err := f.loadMetaByPath(); err != nil {
|
return f, f.loadMetaByPath()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
}
|
||||||
|
@ -110,6 +110,19 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
|
|||||||
return tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProtectedTagByNamePattern gets protected tag by name_pattern
|
||||||
|
func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern string) (*ProtectedTag, error) {
|
||||||
|
tag := &ProtectedTag{NamePattern: pattern, RepoID: repoID}
|
||||||
|
has, err := db.GetEngine(ctx).Get(tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
||||||
// It returns true if the tag name is not protected or the user is allowed to control it.
|
// It returns true if the tag name is not protected or the user is allowed to control it.
|
||||||
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
||||||
|
@ -99,9 +99,9 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyLimit(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if opts.Paginator == nil || opts.Paginator.IsListAll() {
|
if opts.Paginator == nil || opts.Paginator.IsListAll() {
|
||||||
return sess
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
start := 0
|
start := 0
|
||||||
@ -109,11 +109,9 @@ func applyLimit(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
|||||||
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
|
start = (opts.Paginator.Page - 1) * opts.Paginator.PageSize
|
||||||
}
|
}
|
||||||
sess.Limit(opts.Paginator.PageSize, start)
|
sess.Limit(opts.Paginator.PageSize, start)
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.LabelIDs) > 0 {
|
if len(opts.LabelIDs) > 0 {
|
||||||
if opts.LabelIDs[0] == 0 {
|
if opts.LabelIDs[0] == 0 {
|
||||||
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
sess.Where("issue.id NOT IN (SELECT issue_id FROM issue_label)")
|
||||||
@ -136,11 +134,9 @@ func applyLabelsCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session
|
|||||||
if len(opts.ExcludedLabelNames) > 0 {
|
if len(opts.ExcludedLabelNames) > 0 {
|
||||||
sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames)))
|
sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID {
|
if len(opts.MilestoneIDs) == 1 && opts.MilestoneIDs[0] == db.NoConditionID {
|
||||||
sess.And("issue.milestone_id = 0")
|
sess.And("issue.milestone_id = 0")
|
||||||
} else if len(opts.MilestoneIDs) > 0 {
|
} else if len(opts.MilestoneIDs) > 0 {
|
||||||
@ -153,11 +149,9 @@ func applyMilestoneCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sess
|
|||||||
From("milestone").
|
From("milestone").
|
||||||
Where(builder.In("name", opts.IncludeMilestones)))
|
Where(builder.In("name", opts.IncludeMilestones)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if opts.ProjectID > 0 { // specific project
|
if opts.ProjectID > 0 { // specific project
|
||||||
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id").
|
||||||
And("project_issue.project_id=?", opts.ProjectID)
|
And("project_issue.project_id=?", opts.ProjectID)
|
||||||
@ -166,10 +160,9 @@ func applyProjectCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Sessio
|
|||||||
}
|
}
|
||||||
// opts.ProjectID == 0 means all projects,
|
// opts.ProjectID == 0 means all projects,
|
||||||
// do not need to apply any condition
|
// do not need to apply any condition
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
// opts.ProjectColumnID == 0 means all project columns,
|
// opts.ProjectColumnID == 0 means all project columns,
|
||||||
// do not need to apply any condition
|
// do not need to apply any condition
|
||||||
if opts.ProjectColumnID > 0 {
|
if opts.ProjectColumnID > 0 {
|
||||||
@ -177,10 +170,9 @@ func applyProjectColumnCondition(sess *xorm.Session, opts *IssuesOptions) *xorm.
|
|||||||
} else if opts.ProjectColumnID == db.NoConditionID {
|
} else if opts.ProjectColumnID == db.NoConditionID {
|
||||||
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
|
sess.In("issue.id", builder.Select("issue_id").From("project_issue").Where(builder.Eq{"project_board_id": 0}))
|
||||||
}
|
}
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.RepoIDs) == 1 {
|
if len(opts.RepoIDs) == 1 {
|
||||||
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
|
opts.RepoCond = builder.Eq{"issue.repo_id": opts.RepoIDs[0]}
|
||||||
} else if len(opts.RepoIDs) > 1 {
|
} else if len(opts.RepoIDs) > 1 {
|
||||||
@ -195,10 +187,9 @@ func applyRepoConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session
|
|||||||
if opts.RepoCond != nil {
|
if opts.RepoCond != nil {
|
||||||
sess.And(opts.RepoCond)
|
sess.And(opts.RepoCond)
|
||||||
}
|
}
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
func applyConditions(sess *xorm.Session, opts *IssuesOptions) {
|
||||||
if len(opts.IssueIDs) > 0 {
|
if len(opts.IssueIDs) > 0 {
|
||||||
sess.In("issue.id", opts.IssueIDs)
|
sess.In("issue.id", opts.IssueIDs)
|
||||||
}
|
}
|
||||||
@ -261,8 +252,6 @@ func applyConditions(sess *xorm.Session, opts *IssuesOptions) *xorm.Session {
|
|||||||
if opts.User != nil {
|
if opts.User != nil {
|
||||||
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
sess.And(issuePullAccessibleRepoCond("issue.repo_id", opts.User.ID, opts.Org, opts.Team, opts.IsPull.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access
|
// teamUnitsRepoCond returns query condition for those repo id in the special org team with special units access
|
||||||
@ -339,22 +328,22 @@ func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *organizati
|
|||||||
return cond
|
return cond
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) *xorm.Session {
|
func applyAssigneeCondition(sess *xorm.Session, assigneeID int64) {
|
||||||
return sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
sess.Join("INNER", "issue_assignees", "issue.id = issue_assignees.issue_id").
|
||||||
And("issue_assignees.assignee_id = ?", assigneeID)
|
And("issue_assignees.assignee_id = ?", assigneeID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyPosterCondition(sess *xorm.Session, posterID int64) *xorm.Session {
|
func applyPosterCondition(sess *xorm.Session, posterID int64) {
|
||||||
return sess.And("issue.poster_id=?", posterID)
|
sess.And("issue.poster_id=?", posterID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) *xorm.Session {
|
func applyMentionedCondition(sess *xorm.Session, mentionedID int64) {
|
||||||
return sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
|
sess.Join("INNER", "issue_user", "issue.id = issue_user.issue_id").
|
||||||
And("issue_user.is_mentioned = ?", true).
|
And("issue_user.is_mentioned = ?", true).
|
||||||
And("issue_user.uid = ?", mentionedID)
|
And("issue_user.uid = ?", mentionedID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) *xorm.Session {
|
func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64) {
|
||||||
existInTeamQuery := builder.Select("team_user.team_id").
|
existInTeamQuery := builder.Select("team_user.team_id").
|
||||||
From("team_user").
|
From("team_user").
|
||||||
Where(builder.Eq{"team_user.uid": reviewRequestedID})
|
Where(builder.Eq{"team_user.uid": reviewRequestedID})
|
||||||
@ -375,11 +364,11 @@ func applyReviewRequestedCondition(sess *xorm.Session, reviewRequestedID int64)
|
|||||||
),
|
),
|
||||||
builder.In("review.id", maxReview),
|
builder.In("review.id", maxReview),
|
||||||
))
|
))
|
||||||
return sess.Where("issue.poster_id <> ?", reviewRequestedID).
|
sess.Where("issue.poster_id <> ?", reviewRequestedID).
|
||||||
And(builder.In("issue.id", subQuery))
|
And(builder.In("issue.id", subQuery))
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session {
|
func applyReviewedCondition(sess *xorm.Session, reviewedID int64) {
|
||||||
// Query for pull requests where you are a reviewer or commenter, excluding
|
// Query for pull requests where you are a reviewer or commenter, excluding
|
||||||
// any pull requests already returned by the review requested filter.
|
// any pull requests already returned by the review requested filter.
|
||||||
notPoster := builder.Neq{"issue.poster_id": reviewedID}
|
notPoster := builder.Neq{"issue.poster_id": reviewedID}
|
||||||
@ -406,11 +395,11 @@ func applyReviewedCondition(sess *xorm.Session, reviewedID int64) *xorm.Session
|
|||||||
builder.In("type", CommentTypeComment, CommentTypeCode, CommentTypeReview),
|
builder.In("type", CommentTypeComment, CommentTypeCode, CommentTypeReview),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
return sess.And(notPoster, builder.Or(reviewed, commented))
|
sess.And(notPoster, builder.Or(reviewed, commented))
|
||||||
}
|
}
|
||||||
|
|
||||||
func applySubscribedCondition(sess *xorm.Session, subscriberID int64) *xorm.Session {
|
func applySubscribedCondition(sess *xorm.Session, subscriberID int64) {
|
||||||
return sess.And(
|
sess.And(
|
||||||
builder.
|
builder.
|
||||||
NotIn("issue.id",
|
NotIn("issue.id",
|
||||||
builder.Select("issue_id").
|
builder.Select("issue_id").
|
||||||
|
@ -28,7 +28,7 @@ type PullRequestsOptions struct {
|
|||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) (*xorm.Session, error) {
|
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
||||||
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
|
sess := db.GetEngine(ctx).Where("pull_request.base_repo_id=?", baseRepoID)
|
||||||
|
|
||||||
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
|
sess.Join("INNER", "issue", "pull_request.issue_id = issue.id")
|
||||||
@ -46,7 +46,7 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
|||||||
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sess, nil
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
// GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged
|
||||||
@ -130,23 +130,15 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio
|
|||||||
opts.Page = 1
|
opts.Page = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
countSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
|
countSession := listPullRequestStatement(ctx, baseRepoID, opts)
|
||||||
if err != nil {
|
|
||||||
log.Error("listPullRequestStatement: %v", err)
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
maxResults, err := countSession.Count(new(PullRequest))
|
maxResults, err := countSession.Count(new(PullRequest))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Count PRs: %v", err)
|
log.Error("Count PRs: %v", err)
|
||||||
return nil, maxResults, err
|
return nil, maxResults, err
|
||||||
}
|
}
|
||||||
|
|
||||||
findSession, err := listPullRequestStatement(ctx, baseRepoID, opts)
|
findSession := listPullRequestStatement(ctx, baseRepoID, opts)
|
||||||
applySorts(findSession, opts.SortType, 0)
|
applySorts(findSession, opts.SortType, 0)
|
||||||
if err != nil {
|
|
||||||
log.Error("listPullRequestStatement: %v", err)
|
|
||||||
return nil, maxResults, err
|
|
||||||
}
|
|
||||||
findSession = db.SetSessionPagination(findSession, opts)
|
findSession = db.SetSessionPagination(findSession, opts)
|
||||||
prs := make([]*PullRequest, 0, opts.PageSize)
|
prs := make([]*PullRequest, 0, opts.PageSize)
|
||||||
return prs, maxResults, findSession.Find(&prs)
|
return prs, maxResults, findSession.Find(&prs)
|
||||||
@ -200,8 +192,10 @@ func (prs PullRequestList) LoadIssues(ctx context.Context) (IssueList, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load issues.
|
// Load issues which are not loaded
|
||||||
issueIDs := prs.GetIssueIDs()
|
issueIDs := container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
||||||
|
return pr.IssueID, pr.Issue == nil && pr.IssueID > 0
|
||||||
|
})
|
||||||
issues := make(map[int64]*Issue, len(issueIDs))
|
issues := make(map[int64]*Issue, len(issueIDs))
|
||||||
if err := db.GetEngine(ctx).
|
if err := db.GetEngine(ctx).
|
||||||
In("id", issueIDs).
|
In("id", issueIDs).
|
||||||
@ -237,10 +231,7 @@ func (prs PullRequestList) LoadIssues(ctx context.Context) (IssueList, error) {
|
|||||||
// GetIssueIDs returns all issue ids
|
// GetIssueIDs returns all issue ids
|
||||||
func (prs PullRequestList) GetIssueIDs() []int64 {
|
func (prs PullRequestList) GetIssueIDs() []int64 {
|
||||||
return container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
return container.FilterSlice(prs, func(pr *PullRequest) (int64, bool) {
|
||||||
if pr.Issue == nil {
|
return pr.IssueID, pr.IssueID > 0
|
||||||
return pr.IssueID, pr.IssueID > 0
|
|
||||||
}
|
|
||||||
return 0, false
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
28
models/repo/avatar_test.go
Normal file
28
models/repo/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
repo := &Repository{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/repo-avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
|
||||||
|
}
|
@ -207,31 +207,6 @@ type SearchRepoOptions struct {
|
|||||||
OnlyShowRelevant bool
|
OnlyShowRelevant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrderBy is used to sort the result
|
|
||||||
type SearchOrderBy string
|
|
||||||
|
|
||||||
func (s SearchOrderBy) String() string {
|
|
||||||
return string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strings for sorting result
|
|
||||||
const (
|
|
||||||
SearchOrderByAlphabetically SearchOrderBy = "name ASC"
|
|
||||||
SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC"
|
|
||||||
SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC"
|
|
||||||
SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC"
|
|
||||||
SearchOrderByOldest SearchOrderBy = "created_unix ASC"
|
|
||||||
SearchOrderByNewest SearchOrderBy = "created_unix DESC"
|
|
||||||
SearchOrderBySize SearchOrderBy = "size ASC"
|
|
||||||
SearchOrderBySizeReverse SearchOrderBy = "size DESC"
|
|
||||||
SearchOrderByID SearchOrderBy = "id ASC"
|
|
||||||
SearchOrderByIDReverse SearchOrderBy = "id DESC"
|
|
||||||
SearchOrderByStars SearchOrderBy = "num_stars ASC"
|
|
||||||
SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC"
|
|
||||||
SearchOrderByForks SearchOrderBy = "num_forks ASC"
|
|
||||||
SearchOrderByForksReverse SearchOrderBy = "num_forks DESC"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserOwnedRepoCond returns user ownered repositories
|
// UserOwnedRepoCond returns user ownered repositories
|
||||||
func UserOwnedRepoCond(userID int64) builder.Cond {
|
func UserOwnedRepoCond(userID int64) builder.Cond {
|
||||||
return builder.Eq{
|
return builder.Eq{
|
||||||
|
@ -5,20 +5,48 @@ package repo
|
|||||||
|
|
||||||
import "code.gitea.io/gitea/models/db"
|
import "code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
// SearchOrderByMap represents all possible search order
|
// OrderByMap represents all possible search order
|
||||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
"asc": {
|
"asc": {
|
||||||
"alpha": "owner_name ASC, name ASC",
|
"alpha": "owner_name ASC, name ASC",
|
||||||
"created": db.SearchOrderByOldest,
|
"created": db.SearchOrderByOldest,
|
||||||
"updated": db.SearchOrderByLeastUpdated,
|
"updated": db.SearchOrderByLeastUpdated,
|
||||||
"size": db.SearchOrderBySize,
|
"size": "size ASC",
|
||||||
"id": db.SearchOrderByID,
|
"git_size": "git_size ASC",
|
||||||
|
"lfs_size": "lfs_size ASC",
|
||||||
|
"id": db.SearchOrderByID,
|
||||||
|
"stars": db.SearchOrderByStars,
|
||||||
|
"forks": db.SearchOrderByForks,
|
||||||
},
|
},
|
||||||
"desc": {
|
"desc": {
|
||||||
"alpha": "owner_name DESC, name DESC",
|
"alpha": "owner_name DESC, name DESC",
|
||||||
"created": db.SearchOrderByNewest,
|
"created": db.SearchOrderByNewest,
|
||||||
"updated": db.SearchOrderByRecentUpdated,
|
"updated": db.SearchOrderByRecentUpdated,
|
||||||
"size": db.SearchOrderBySizeReverse,
|
"size": "size DESC",
|
||||||
"id": db.SearchOrderByIDReverse,
|
"git_size": "git_size DESC",
|
||||||
|
"lfs_size": "lfs_size DESC",
|
||||||
|
"id": db.SearchOrderByIDReverse,
|
||||||
|
"stars": db.SearchOrderByStarsReverse,
|
||||||
|
"forks": db.SearchOrderByForksReverse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrderByFlatMap is similar to OrderByMap but use human language keywords
|
||||||
|
// to decide between asc and desc
|
||||||
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
|
"newest": OrderByMap["desc"]["created"],
|
||||||
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
|
"reversesize": OrderByMap["desc"]["size"],
|
||||||
|
"size": OrderByMap["asc"]["size"],
|
||||||
|
"reversegitsize": OrderByMap["desc"]["git_size"],
|
||||||
|
"gitsize": OrderByMap["asc"]["git_size"],
|
||||||
|
"reverselfssize": OrderByMap["desc"]["lfs_size"],
|
||||||
|
"lfssize": OrderByMap["asc"]["lfs_size"],
|
||||||
|
"moststars": OrderByMap["desc"]["stars"],
|
||||||
|
"feweststars": OrderByMap["asc"]["stars"],
|
||||||
|
"mostforks": OrderByMap["desc"]["forks"],
|
||||||
|
"fewestforks": OrderByMap["asc"]["forks"],
|
||||||
|
}
|
||||||
|
@ -89,9 +89,11 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||||||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvatarLink returns the full avatar url with http host. TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
// AvatarLink returns the full avatar url with http host.
|
||||||
|
// TODO: refactor it to a relative URL, but it is still used in API response at the moment
|
||||||
func (u *User) AvatarLink(ctx context.Context) string {
|
func (u *User) AvatarLink(ctx context.Context) string {
|
||||||
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty
|
||||||
|
return httplib.MakeAbsoluteURL(ctx, relLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||||
|
28
models/user/avatar_test.go
Normal file
28
models/user/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
u := &User{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
|
}
|
@ -18,7 +18,7 @@ func parseIntParam(value, param, algorithmName, config string, previousErr error
|
|||||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) {
|
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) { //nolint:unparam
|
||||||
parsed, err := strconv.ParseUint(value, 10, 64)
|
parsed, err := strconv.ParseUint(value, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
||||||
|
@ -4,12 +4,67 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func naturalSortGetRune(str string, pos int) (r rune, size int, has bool) {
|
||||||
|
if pos >= len(str) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(str[pos:])
|
||||||
|
if r == utf8.RuneError {
|
||||||
|
r, size = rune(str[pos]), 1 // if invalid input, treat it as a single byte ascii
|
||||||
|
}
|
||||||
|
return r, size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||||
|
end = pos
|
||||||
|
for {
|
||||||
|
r, size, has := naturalSortGetRune(str, end)
|
||||||
|
if !has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
isCurRuneNum := '0' <= r && r <= '9'
|
||||||
|
if end == pos {
|
||||||
|
isNumber = isCurRuneNum
|
||||||
|
end += size
|
||||||
|
} else if isCurRuneNum == isNumber {
|
||||||
|
end += size
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end, isNumber
|
||||||
|
}
|
||||||
|
|
||||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||||
func NaturalSortLess(s1, s2 string) bool {
|
func NaturalSortLess(s1, s2 string) bool {
|
||||||
|
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||||
|
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||||
|
// So we need to handle the number parts by ourselves
|
||||||
c := collate.New(language.English, collate.Numeric)
|
c := collate.New(language.English, collate.Numeric)
|
||||||
return c.CompareString(s1, s2) < 0
|
pos1, pos2 := 0, 0
|
||||||
|
for pos1 < len(s1) && pos2 < len(s2) {
|
||||||
|
end1, isNum1 := naturalSortAdvance(s1, pos1)
|
||||||
|
end2, isNum2 := naturalSortAdvance(s2, pos2)
|
||||||
|
part1, part2 := s1[pos1:end1], s2[pos2:end2]
|
||||||
|
if isNum1 && isNum2 {
|
||||||
|
if part1 != part2 {
|
||||||
|
if len(part1) != len(part2) {
|
||||||
|
return len(part1) < len(part2)
|
||||||
|
}
|
||||||
|
return part1 < part2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||||
|
return cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos1, pos2 = end1, end2
|
||||||
|
}
|
||||||
|
return len(s1) < len(s2)
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNaturalSortLess(t *testing.T) {
|
func TestNaturalSortLess(t *testing.T) {
|
||||||
test := func(s1, s2 string, less bool) {
|
testLess := func(s1, s2 string) {
|
||||||
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
|
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
}
|
||||||
|
testEqual := func(s1, s2 string) {
|
||||||
|
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
}
|
}
|
||||||
test("v1.20.0", "v1.2.0", false)
|
|
||||||
test("v1.20.0", "v1.29.0", true)
|
|
||||||
test("v1.20.0", "v1.20.0", false)
|
|
||||||
test("abc", "bcd", true)
|
|
||||||
test("a-1-a", "a-1-b", true)
|
|
||||||
test("2", "12", true)
|
|
||||||
test("a", "ab", true)
|
|
||||||
|
|
||||||
test("A", "b", true)
|
testEqual("", "")
|
||||||
test("a", "B", true)
|
testLess("", "a")
|
||||||
|
testLess("", "1")
|
||||||
|
|
||||||
test("cafe", "café", true)
|
testLess("v1.2", "v1.2.0")
|
||||||
test("café", "cafe", false)
|
testLess("v1.2.0", "v1.10.0")
|
||||||
test("caff", "café", false)
|
testLess("v1.20.0", "v1.29.0")
|
||||||
|
testEqual("v1.20.0", "v1.20.0")
|
||||||
|
|
||||||
|
testLess("a", "A")
|
||||||
|
testLess("a", "B")
|
||||||
|
testLess("A", "b")
|
||||||
|
testLess("A", "ab")
|
||||||
|
|
||||||
|
testLess("abc", "bcd")
|
||||||
|
testLess("a-1-a", "a-1-b")
|
||||||
|
testLess("2", "12")
|
||||||
|
|
||||||
|
testLess("cafe", "café")
|
||||||
|
testLess("café", "caff")
|
||||||
|
|
||||||
|
testLess("A-2", "A-11")
|
||||||
|
testLess("0.txt", "1.txt")
|
||||||
}
|
}
|
||||||
|
32
modules/cache/cache.go
vendored
32
modules/cache/cache.go
vendored
@ -4,6 +4,7 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -35,6 +36,37 @@ func Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testCacheKey = "DefaultCache.TestKey"
|
||||||
|
SlowCacheThreshold = 100 * time.Microsecond
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test() (time.Duration, error) {
|
||||||
|
if defaultCache == nil {
|
||||||
|
return 0, fmt.Errorf("default cache not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
testData := fmt.Sprintf("%x", make([]byte, 500))
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
if err := defaultCache.Delete(testCacheKey); err != nil {
|
||||||
|
return 0, fmt.Errorf("expect cache to delete data based on key if exist but got: %w", err)
|
||||||
|
}
|
||||||
|
if err := defaultCache.Put(testCacheKey, testData, 10); err != nil {
|
||||||
|
return 0, fmt.Errorf("expect cache to store data but got: %w", err)
|
||||||
|
}
|
||||||
|
testVal, hit := defaultCache.Get(testCacheKey)
|
||||||
|
if !hit {
|
||||||
|
return 0, fmt.Errorf("expect cache hit but got none")
|
||||||
|
}
|
||||||
|
if testVal != testData {
|
||||||
|
return 0, fmt.Errorf("expect cache to return same value as stored but got other")
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Since(start), nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetCache returns the currently configured cache
|
// GetCache returns the currently configured cache
|
||||||
func GetCache() StringCache {
|
func GetCache() StringCache {
|
||||||
return defaultCache
|
return defaultCache
|
||||||
|
12
modules/cache/cache_test.go
vendored
12
modules/cache/cache_test.go
vendored
@ -34,6 +34,18 @@ func TestNewContext(t *testing.T) {
|
|||||||
assert.Nil(t, con)
|
assert.Nil(t, con)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTest(t *testing.T) {
|
||||||
|
defaultCache = nil
|
||||||
|
_, err := Test()
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
createTestCache()
|
||||||
|
elapsed, err := Test()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
||||||
|
assert.Less(t, elapsed, SlowCacheThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetCache(t *testing.T) {
|
func TestGetCache(t *testing.T) {
|
||||||
createTestCache()
|
createTestCache()
|
||||||
|
|
||||||
|
@ -57,11 +57,16 @@ func getForwardedHost(req *http.Request) string {
|
|||||||
return req.Header.Get("X-Forwarded-Host")
|
return req.Header.Get("X-Forwarded-Host")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
// GuessCurrentAppURL tries to guess the current full app URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
||||||
func GuessCurrentAppURL(ctx context.Context) string {
|
func GuessCurrentAppURL(ctx context.Context) string {
|
||||||
|
return GuessCurrentHostURL(ctx) + setting.AppSubURL + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash.
|
||||||
|
func GuessCurrentHostURL(ctx context.Context) string {
|
||||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
||||||
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
||||||
@ -74,20 +79,27 @@ func GuessCurrentAppURL(ctx context.Context) string {
|
|||||||
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
||||||
reqScheme := getRequestScheme(req)
|
reqScheme := getRequestScheme(req)
|
||||||
if reqScheme == "" {
|
if reqScheme == "" {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
reqHost := getForwardedHost(req)
|
reqHost := getForwardedHost(req)
|
||||||
if reqHost == "" {
|
if reqHost == "" {
|
||||||
reqHost = req.Host
|
reqHost = req.Host
|
||||||
}
|
}
|
||||||
return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
|
return reqScheme + "://" + reqHost
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeAbsoluteURL(ctx context.Context, s string) string {
|
// MakeAbsoluteURL tries to make a link to an absolute URL:
|
||||||
if IsRelativeURL(s) {
|
// * If link is empty, it returns the current app URL.
|
||||||
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
|
// * If link is absolute, it returns the link.
|
||||||
|
// * Otherwise, it returns the current host URL + link, the link itself should have correct sub-path (AppSubURL) if needed.
|
||||||
|
func MakeAbsoluteURL(ctx context.Context, link string) string {
|
||||||
|
if link == "" {
|
||||||
|
return GuessCurrentAppURL(ctx)
|
||||||
}
|
}
|
||||||
return s
|
if !IsRelativeURL(link) {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
return GuessCurrentHostURL(ctx) + "/" + strings.TrimPrefix(link, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
||||||
|
@ -46,14 +46,14 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "foo"))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -61,7 +61,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Host": {"forwarded-host"},
|
"X-Forwarded-Host": {"forwarded-host"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -70,7 +70,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Proto": {"https"},
|
"X-Forwarded-Proto": {"https"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "https://forwarded-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||||
|
@ -38,6 +38,12 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
|||||||
searchOpt.MilestoneIDs = opts.MilestoneIDs
|
searchOpt.MilestoneIDs = opts.MilestoneIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.ProjectID > 0 {
|
||||||
|
searchOpt.ProjectID = optional.Some(opts.ProjectID)
|
||||||
|
} else if opts.ProjectID == -1 { // FIXME: this is inconsistent from other places
|
||||||
|
searchOpt.ProjectID = optional.Some[int64](0) // Those issues with no project(projectid==0)
|
||||||
|
}
|
||||||
|
|
||||||
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
// See the comment of issues_model.SearchOptions for the reason why we need to convert
|
||||||
convertID := func(id int64) optional.Option[int64] {
|
convertID := func(id int64) optional.Option[int64] {
|
||||||
if id > 0 {
|
if id > 0 {
|
||||||
@ -49,7 +55,6 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
searchOpt.ProjectID = convertID(opts.ProjectID)
|
|
||||||
searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
|
searchOpt.ProjectColumnID = convertID(opts.ProjectColumnID)
|
||||||
searchOpt.PosterID = convertID(opts.PosterID)
|
searchOpt.PosterID = convertID(opts.PosterID)
|
||||||
searchOpt.AssigneeID = convertID(opts.AssigneeID)
|
searchOpt.AssigneeID = convertID(opts.AssigneeID)
|
||||||
|
@ -211,7 +211,7 @@ func createRequest(ctx context.Context, method, url string, headers map[string]s
|
|||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
req.Header.Set(key, value)
|
req.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
req.Header.Set("Accept", MediaType)
|
req.Header.Set("Accept", AcceptHeader)
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
@ -251,6 +251,6 @@ func handleErrorResponse(resp *http.Response) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("ErrorResponse: %v", er)
|
log.Trace("ErrorResponse(%v): %v", resp.Status, er)
|
||||||
return errors.New(er.Message)
|
return errors.New(er.Message)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ func TestHTTPClientDownload(t *testing.T) {
|
|||||||
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
|
|
||||||
var batchRequest BatchRequest
|
var batchRequest BatchRequest
|
||||||
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
||||||
@ -263,7 +263,7 @@ func TestHTTPClientUpload(t *testing.T) {
|
|||||||
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
hc := &http.Client{Transport: RoundTripFunc(func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, "POST", req.Method)
|
assert.Equal(t, "POST", req.Method)
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
assert.Equal(t, MediaType, req.Header.Get("Content-type"))
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
|
|
||||||
var batchRequest BatchRequest
|
var batchRequest BatchRequest
|
||||||
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
err := json.NewDecoder(req.Body).Decode(&batchRequest)
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
// MediaType contains the media type for LFS server requests
|
// MediaType contains the media type for LFS server requests
|
||||||
MediaType = "application/vnd.git-lfs+json"
|
MediaType = "application/vnd.git-lfs+json"
|
||||||
|
// Some LFS servers offer content with other types, so fallback to '*/*' if application/vnd.git-lfs+json cannot be served
|
||||||
|
AcceptHeader = "application/vnd.git-lfs+json;q=0.9, */*;q=0.8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BatchRequest contains multiple requests processed in one batch operation.
|
// BatchRequest contains multiple requests processed in one batch operation.
|
||||||
|
@ -37,6 +37,7 @@ func (a *BasicTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debug("Download Request: %+v", req)
|
||||||
resp, err := performRequest(ctx, a.client, req)
|
resp, err := performRequest(ctx, a.client, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -26,7 +26,7 @@ func TestBasicTransferAdapter(t *testing.T) {
|
|||||||
p := Pointer{Oid: "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259", Size: 5}
|
p := Pointer{Oid: "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259", Size: 5}
|
||||||
|
|
||||||
roundTripHandler := func(req *http.Request) *http.Response {
|
roundTripHandler := func(req *http.Request) *http.Response {
|
||||||
assert.Equal(t, MediaType, req.Header.Get("Accept"))
|
assert.Equal(t, AcceptHeader, req.Header.Get("Accept"))
|
||||||
assert.Equal(t, "test-value", req.Header.Get("test-header"))
|
assert.Equal(t, "test-value", req.Header.Get("test-header"))
|
||||||
|
|
||||||
url := req.URL.String()
|
url := req.URL.String()
|
||||||
|
@ -49,7 +49,7 @@ var (
|
|||||||
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
// hashCurrentPattern matches string that represents a commit SHA, e.g. d8a994ef243349f321568f9e36d5c3f444b99cae
|
||||||
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
|
// Although SHA1 hashes are 40 chars long, SHA256 are 64, the regex matches the hash from 7 to 64 chars in length
|
||||||
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
|
// so that abbreviated hash links can be used as well. This matches git and GitHub usability.
|
||||||
hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,](\s|$))`)
|
hashCurrentPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-f]{7,64})(?:\s|$|\)|\]|[.,:](\s|$))`)
|
||||||
|
|
||||||
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
|
// shortLinkPattern matches short but difficult to parse [[name|link|arg=test]] syntax
|
||||||
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
shortLinkPattern = regexp.MustCompile(`\[\[(.*?)\]\](\w*)`)
|
||||||
|
@ -380,6 +380,7 @@ func TestRegExp_sha1CurrentPattern(t *testing.T) {
|
|||||||
"(abcdefabcdefabcdefabcdefabcdefabcdefabcd)",
|
"(abcdefabcdefabcdefabcdefabcdefabcdefabcd)",
|
||||||
"[abcdefabcdefabcdefabcdefabcdefabcdefabcd]",
|
"[abcdefabcdefabcdefabcdefabcdefabcdefabcd]",
|
||||||
"abcdefabcdefabcdefabcdefabcdefabcdefabcd.",
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcd.",
|
||||||
|
"abcdefabcdefabcdefabcdefabcdefabcdefabcd:",
|
||||||
}
|
}
|
||||||
falseTestCases := []string{
|
falseTestCases := []string{
|
||||||
"test",
|
"test",
|
||||||
|
@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type prefixedIDs struct {
|
type prefixedIDs struct {
|
||||||
@ -36,7 +36,7 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
|||||||
if !bytes.HasPrefix(result, []byte("user-content-")) {
|
if !bytes.HasPrefix(result, []byte("user-content-")) {
|
||||||
result = append([]byte("user-content-"), result...)
|
result = append([]byte("user-content-"), result...)
|
||||||
}
|
}
|
||||||
if p.values.Add(util.BytesToReadOnlyString(result)) {
|
if p.values.Add(util.UnsafeBytesToString(result)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
for i := 1; ; i++ {
|
for i := 1; ; i++ {
|
||||||
@ -49,7 +49,7 @@ func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte {
|
|||||||
|
|
||||||
// Put puts a given element id to the used ids table.
|
// Put puts a given element id to the used ids table.
|
||||||
func (p *prefixedIDs) Put(value []byte) {
|
func (p *prefixedIDs) Put(value []byte) {
|
||||||
p.values.Add(util.BytesToReadOnlyString(value))
|
p.values.Add(util.UnsafeBytesToString(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPrefixedIDs() *prefixedIDs {
|
func newPrefixedIDs() *prefixedIDs {
|
||||||
|
@ -7,10 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
|
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
|
||||||
@ -21,11 +21,11 @@ func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Headin
|
|||||||
}
|
}
|
||||||
txt := v.Text(reader.Source())
|
txt := v.Text(reader.Source())
|
||||||
header := markup.Header{
|
header := markup.Header{
|
||||||
Text: util.BytesToReadOnlyString(txt),
|
Text: util.UnsafeBytesToString(txt),
|
||||||
Level: v.Level,
|
Level: v.Level,
|
||||||
}
|
}
|
||||||
if id, found := v.AttributeString("id"); found {
|
if id, found := v.AttributeString("id"); found {
|
||||||
header.ID = util.BytesToReadOnlyString(id.([]byte))
|
header.ID = util.UnsafeBytesToString(id.([]byte))
|
||||||
}
|
}
|
||||||
*tocList = append(*tocList, header)
|
*tocList = append(*tocList, header)
|
||||||
g.applyElementDir(v)
|
g.applyElementDir(v)
|
||||||
|
@ -86,10 +86,10 @@ type RenderContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Links struct {
|
type Links struct {
|
||||||
AbsolutePrefix bool
|
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
|
||||||
Base string
|
Base string // base prefix for pre-provided links and medias (images, videos)
|
||||||
BranchPath string
|
BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
|
||||||
TreePath string
|
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Links) Prefix() string {
|
func (l *Links) Prefix() string {
|
||||||
|
@ -6,6 +6,7 @@ package composer
|
|||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -36,10 +37,14 @@ type Package struct {
|
|||||||
Metadata *Metadata
|
Metadata *Metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://getcomposer.org/doc/04-schema.md
|
||||||
|
|
||||||
// Metadata represents the metadata of a Composer package
|
// Metadata represents the metadata of a Composer package
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
|
Readme string `json:"readme,omitempty"`
|
||||||
Keywords []string `json:"keywords,omitempty"`
|
Keywords []string `json:"keywords,omitempty"`
|
||||||
|
Comments Comments `json:"_comments,omitempty"`
|
||||||
Homepage string `json:"homepage,omitempty"`
|
Homepage string `json:"homepage,omitempty"`
|
||||||
License Licenses `json:"license,omitempty"`
|
License Licenses `json:"license,omitempty"`
|
||||||
Authors []Author `json:"authors,omitempty"`
|
Authors []Author `json:"authors,omitempty"`
|
||||||
@ -74,6 +79,28 @@ func (l *Licenses) UnmarshalJSON(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comments represents the comments of a Composer package
|
||||||
|
type Comments []string
|
||||||
|
|
||||||
|
// UnmarshalJSON reads from a string or array
|
||||||
|
func (c *Comments) UnmarshalJSON(data []byte) error {
|
||||||
|
switch data[0] {
|
||||||
|
case '"':
|
||||||
|
var value string
|
||||||
|
if err := json.Unmarshal(data, &value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = Comments{value}
|
||||||
|
case '[':
|
||||||
|
values := make([]string, 0, 5)
|
||||||
|
if err := json.Unmarshal(data, &values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*c = Comments(values)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Author represents an author
|
// Author represents an author
|
||||||
type Author struct {
|
type Author struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
@ -101,14 +128,14 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return ParseComposerFile(f)
|
return ParseComposerFile(archive, path.Dir(file.Name), f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, ErrMissingComposerFile
|
return nil, ErrMissingComposerFile
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
|
// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
|
||||||
func ParseComposerFile(r io.Reader) (*Package, error) {
|
func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Package, error) {
|
||||||
var cj struct {
|
var cj struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@ -137,6 +164,19 @@ func ParseComposerFile(r io.Reader) (*Package, error) {
|
|||||||
cj.Type = "library"
|
cj.Type = "library"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cj.Readme == "" {
|
||||||
|
cj.Readme = "README.md"
|
||||||
|
}
|
||||||
|
f, err := archive.Open(path.Join(pathPrefix, cj.Readme))
|
||||||
|
if err == nil {
|
||||||
|
// 10kb limit for readme content
|
||||||
|
buf, _ := io.ReadAll(io.LimitReader(f, 10*1024))
|
||||||
|
cj.Readme = string(buf)
|
||||||
|
_ = f.Close()
|
||||||
|
} else {
|
||||||
|
cj.Readme = ""
|
||||||
|
}
|
||||||
|
|
||||||
return &Package{
|
return &Package{
|
||||||
Name: cj.Name,
|
Name: cj.Name,
|
||||||
Version: cj.Version,
|
Version: cj.Version,
|
||||||
|
@ -17,6 +17,8 @@ import (
|
|||||||
const (
|
const (
|
||||||
name = "gitea/composer-package"
|
name = "gitea/composer-package"
|
||||||
description = "Package Description"
|
description = "Package Description"
|
||||||
|
readme = "Package Readme"
|
||||||
|
comments = "Package Comment"
|
||||||
packageType = "composer-plugin"
|
packageType = "composer-plugin"
|
||||||
author = "Gitea Authors"
|
author = "Gitea Authors"
|
||||||
email = "no.reply@gitea.io"
|
email = "no.reply@gitea.io"
|
||||||
@ -41,7 +43,8 @@ const composerContent = `{
|
|||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.2 || ^8.0"
|
"php": ">=7.2 || ^8.0"
|
||||||
}
|
},
|
||||||
|
"_comments": "` + comments + `"
|
||||||
}`
|
}`
|
||||||
|
|
||||||
func TestLicenseUnmarshal(t *testing.T) {
|
func TestLicenseUnmarshal(t *testing.T) {
|
||||||
@ -54,18 +57,30 @@ func TestLicenseUnmarshal(t *testing.T) {
|
|||||||
assert.Equal(t, "MIT", l[0])
|
assert.Equal(t, "MIT", l[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommentsUnmarshal(t *testing.T) {
|
||||||
|
var c Comments
|
||||||
|
assert.NoError(t, json.NewDecoder(strings.NewReader(`["comment"]`)).Decode(&c))
|
||||||
|
assert.Len(t, c, 1)
|
||||||
|
assert.Equal(t, "comment", c[0])
|
||||||
|
assert.NoError(t, json.NewDecoder(strings.NewReader(`"comment"`)).Decode(&c))
|
||||||
|
assert.Len(t, c, 1)
|
||||||
|
assert.Equal(t, "comment", c[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePackage(t *testing.T) {
|
func TestParsePackage(t *testing.T) {
|
||||||
createArchive := func(name, content string) []byte {
|
createArchive := func(files map[string]string) []byte {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
archive := zip.NewWriter(&buf)
|
archive := zip.NewWriter(&buf)
|
||||||
w, _ := archive.Create(name)
|
for name, content := range files {
|
||||||
w.Write([]byte(content))
|
w, _ := archive.Create(name)
|
||||||
|
w.Write([]byte(content))
|
||||||
|
}
|
||||||
archive.Close()
|
archive.Close()
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("MissingComposerFile", func(t *testing.T) {
|
t.Run("MissingComposerFile", func(t *testing.T) {
|
||||||
data := createArchive("dummy.txt", "")
|
data := createArchive(map[string]string{"dummy.txt": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
@ -73,7 +88,7 @@ func TestParsePackage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("MissingComposerFileInRoot", func(t *testing.T) {
|
t.Run("MissingComposerFileInRoot", func(t *testing.T) {
|
||||||
data := createArchive("sub/sub/composer.json", "")
|
data := createArchive(map[string]string{"sub/sub/composer.json": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
@ -81,43 +96,52 @@ func TestParsePackage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidComposerFile", func(t *testing.T) {
|
t.Run("InvalidComposerFile", func(t *testing.T) {
|
||||||
data := createArchive("composer.json", "")
|
data := createArchive(map[string]string{"composer.json": ""})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Valid", func(t *testing.T) {
|
t.Run("InvalidPackageName", func(t *testing.T) {
|
||||||
data := createArchive("composer.json", composerContent)
|
data := createArchive(map[string]string{"composer.json": "{}"})
|
||||||
|
|
||||||
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, cp)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseComposerFile(t *testing.T) {
|
|
||||||
t.Run("InvalidPackageName", func(t *testing.T) {
|
|
||||||
cp, err := ParseComposerFile(strings.NewReader(`{}`))
|
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.ErrorIs(t, err, ErrInvalidName)
|
assert.ErrorIs(t, err, ErrInvalidName)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("InvalidPackageVersion", func(t *testing.T) {
|
t.Run("InvalidPackageVersion", func(t *testing.T) {
|
||||||
cp, err := ParseComposerFile(strings.NewReader(`{"name": "gitea/composer-package", "version": "1.a.3"}`))
|
data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "version": "1.a.3"}`})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.Nil(t, cp)
|
assert.Nil(t, cp)
|
||||||
assert.ErrorIs(t, err, ErrInvalidVersion)
|
assert.ErrorIs(t, err, ErrInvalidVersion)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidReadmePath", func(t *testing.T) {
|
||||||
|
data := createArchive(map[string]string{"composer.json": `{"name": "gitea/composer-package", "readme": "sub/README.md"}`})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, cp)
|
||||||
|
|
||||||
|
assert.Empty(t, cp.Metadata.Readme)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Valid", func(t *testing.T) {
|
t.Run("Valid", func(t *testing.T) {
|
||||||
cp, err := ParseComposerFile(strings.NewReader(composerContent))
|
data := createArchive(map[string]string{"composer.json": composerContent, "README.md": readme})
|
||||||
|
|
||||||
|
cp, err := ParsePackage(bytes.NewReader(data), int64(len(data)))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, cp)
|
assert.NotNil(t, cp)
|
||||||
|
|
||||||
assert.Equal(t, name, cp.Name)
|
assert.Equal(t, name, cp.Name)
|
||||||
assert.Empty(t, cp.Version)
|
assert.Empty(t, cp.Version)
|
||||||
assert.Equal(t, description, cp.Metadata.Description)
|
assert.Equal(t, description, cp.Metadata.Description)
|
||||||
|
assert.Equal(t, readme, cp.Metadata.Readme)
|
||||||
|
assert.Len(t, cp.Metadata.Comments, 1)
|
||||||
|
assert.Equal(t, comments, cp.Metadata.Comments[0])
|
||||||
assert.Len(t, cp.Metadata.Authors, 1)
|
assert.Len(t, cp.Metadata.Authors, 1)
|
||||||
assert.Equal(t, author, cp.Metadata.Authors[0].Name)
|
assert.Equal(t, author, cp.Metadata.Authors[0].Name)
|
||||||
assert.Equal(t, email, cp.Metadata.Authors[0].Email)
|
assert.Equal(t, email, cp.Metadata.Authors[0].Email)
|
||||||
|
@ -185,8 +185,6 @@ func ParseDescription(r io.Reader) (*Package, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setField(p *Package, data string) error {
|
func setField(p *Package, data string) error {
|
||||||
const listDelimiter = ", "
|
|
||||||
|
|
||||||
if data == "" {
|
if data == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -215,19 +213,19 @@ func setField(p *Package, data string) error {
|
|||||||
case "Description":
|
case "Description":
|
||||||
p.Metadata.Description = value
|
p.Metadata.Description = value
|
||||||
case "URL":
|
case "URL":
|
||||||
p.Metadata.ProjectURL = splitAndTrim(value, listDelimiter)
|
p.Metadata.ProjectURL = splitAndTrim(value)
|
||||||
case "License":
|
case "License":
|
||||||
p.Metadata.License = value
|
p.Metadata.License = value
|
||||||
case "Author":
|
case "Author":
|
||||||
p.Metadata.Authors = splitAndTrim(authorReplacePattern.ReplaceAllString(value, ""), listDelimiter)
|
p.Metadata.Authors = splitAndTrim(authorReplacePattern.ReplaceAllString(value, ""))
|
||||||
case "Depends":
|
case "Depends":
|
||||||
p.Metadata.Depends = splitAndTrim(value, listDelimiter)
|
p.Metadata.Depends = splitAndTrim(value)
|
||||||
case "Imports":
|
case "Imports":
|
||||||
p.Metadata.Imports = splitAndTrim(value, listDelimiter)
|
p.Metadata.Imports = splitAndTrim(value)
|
||||||
case "Suggests":
|
case "Suggests":
|
||||||
p.Metadata.Suggests = splitAndTrim(value, listDelimiter)
|
p.Metadata.Suggests = splitAndTrim(value)
|
||||||
case "LinkingTo":
|
case "LinkingTo":
|
||||||
p.Metadata.LinkingTo = splitAndTrim(value, listDelimiter)
|
p.Metadata.LinkingTo = splitAndTrim(value)
|
||||||
case "NeedsCompilation":
|
case "NeedsCompilation":
|
||||||
p.Metadata.NeedsCompilation = value == "yes"
|
p.Metadata.NeedsCompilation = value == "yes"
|
||||||
}
|
}
|
||||||
@ -235,8 +233,8 @@ func setField(p *Package, data string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitAndTrim(s, sep string) []string {
|
func splitAndTrim(s string) []string {
|
||||||
items := strings.Split(s, sep)
|
items := strings.Split(s, ", ")
|
||||||
for i := range items {
|
for i := range items {
|
||||||
items[i] = strings.TrimSpace(items[i])
|
items[i] = strings.TrimSpace(items[i])
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/markup/mdstripper"
|
"code.gitea.io/gitea/modules/markup/mdstripper"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -341,7 +340,7 @@ func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r := getCrossReference(util.StringToReadOnlyBytes(content), match[2], match[3], false, prOnly)
|
r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
return 0, fmt.Errorf("UpdateRepository: %w", err)
|
||||||
}
|
}
|
||||||
|
repo.ObjectFormatName = objFmt.Name() // keep consistent with db
|
||||||
|
|
||||||
allBranches := container.Set[string]{}
|
allBranches := container.Set[string]{}
|
||||||
{
|
{
|
||||||
|
@ -97,7 +97,7 @@ func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
|
|||||||
|
|
||||||
// decodeEnvironmentKey decode the environment key to section and key
|
// decodeEnvironmentKey decode the environment key to section and key
|
||||||
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
|
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
|
||||||
func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) {
|
func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { //nolint:unparam
|
||||||
if !strings.HasPrefix(envKey, prefixGitea) {
|
if !strings.HasPrefix(envKey, prefixGitea) {
|
||||||
return false, "", "", false
|
return false, "", "", false
|
||||||
}
|
}
|
||||||
|
18
modules/setting/global.go
Normal file
18
modules/setting/global.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
// Global settings
|
||||||
|
var (
|
||||||
|
// RunUser is the OS user that Gitea is running as. ini:"RUN_USER"
|
||||||
|
RunUser string
|
||||||
|
// RunMode is the running mode of Gitea, it only accepts two values: "dev" and "prod".
|
||||||
|
// Non-dev values will be replaced by "prod". ini: "RUN_MODE"
|
||||||
|
RunMode string
|
||||||
|
// IsProd is true if RunMode is not "dev"
|
||||||
|
IsProd bool
|
||||||
|
|
||||||
|
// AppName is the Application name, used in the page title. ini: "APP_NAME"
|
||||||
|
AppName string
|
||||||
|
)
|
@ -6,7 +6,6 @@ package setting
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -19,7 +18,6 @@ var (
|
|||||||
Storage *Storage
|
Storage *Storage
|
||||||
Enabled bool
|
Enabled bool
|
||||||
ChunkedUploadPath string
|
ChunkedUploadPath string
|
||||||
RegistryHost string
|
|
||||||
|
|
||||||
LimitTotalOwnerCount int64
|
LimitTotalOwnerCount int64
|
||||||
LimitTotalOwnerSize int64
|
LimitTotalOwnerSize int64
|
||||||
@ -66,9 +64,6 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
appURL, _ := url.Parse(AppURL)
|
|
||||||
Packages.RegistryHost = appURL.Host
|
|
||||||
|
|
||||||
Packages.ChunkedUploadPath = filepath.ToSlash(sec.Key("CHUNKED_UPLOAD_PATH").MustString("tmp/package-upload"))
|
Packages.ChunkedUploadPath = filepath.ToSlash(sec.Key("CHUNKED_UPLOAD_PATH").MustString("tmp/package-upload"))
|
||||||
if !filepath.IsAbs(Packages.ChunkedUploadPath) {
|
if !filepath.IsAbs(Packages.ChunkedUploadPath) {
|
||||||
Packages.ChunkedUploadPath = filepath.ToSlash(filepath.Join(AppDataPath, Packages.ChunkedUploadPath))
|
Packages.ChunkedUploadPath = filepath.ToSlash(filepath.Join(AppDataPath, Packages.ChunkedUploadPath))
|
||||||
|
@ -40,16 +40,16 @@ const (
|
|||||||
LandingPageLogin LandingPage = "/user/login"
|
LandingPageLogin LandingPage = "/user/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Server settings
|
||||||
var (
|
var (
|
||||||
// AppName is the Application name, used in the page title.
|
|
||||||
// It maps to ini:"APP_NAME"
|
|
||||||
AppName string
|
|
||||||
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
||||||
// It maps to ini:"ROOT_URL"
|
// It maps to ini:"ROOT_URL"
|
||||||
AppURL string
|
AppURL string
|
||||||
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL string
|
AppSubURL string
|
||||||
|
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...", to make it easier to debug sub-path related problems without a reverse proxy.
|
||||||
|
UseSubURLPath bool
|
||||||
// AppDataPath is the default path for storing data.
|
// AppDataPath is the default path for storing data.
|
||||||
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
||||||
AppDataPath string
|
AppDataPath string
|
||||||
@ -59,8 +59,6 @@ var (
|
|||||||
// AssetVersion holds a opaque value that is used for cache-busting assets
|
// AssetVersion holds a opaque value that is used for cache-busting assets
|
||||||
AssetVersion string
|
AssetVersion string
|
||||||
|
|
||||||
// Server settings
|
|
||||||
|
|
||||||
Protocol Scheme
|
Protocol Scheme
|
||||||
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
||||||
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
||||||
@ -275,9 +273,10 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
||||||
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
||||||
|
|
||||||
// Suburl should start with '/' and end without '/', such as '/{subpath}'.
|
// AppSubURL should start with '/' and end without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
||||||
|
UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false)
|
||||||
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
||||||
|
|
||||||
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
||||||
|
@ -25,12 +25,7 @@ var (
|
|||||||
// AppStartTime store time gitea has started
|
// AppStartTime store time gitea has started
|
||||||
AppStartTime time.Time
|
AppStartTime time.Time
|
||||||
|
|
||||||
// Other global setting objects
|
|
||||||
|
|
||||||
CfgProvider ConfigProvider
|
CfgProvider ConfigProvider
|
||||||
RunMode string
|
|
||||||
RunUser string
|
|
||||||
IsProd bool
|
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
|
|
||||||
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
||||||
|
@ -161,7 +161,7 @@ const (
|
|||||||
targetSecIsSec // target section is from the name seciont [name]
|
targetSecIsSec // target section is from the name seciont [name]
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) {
|
func getStorageSectionByType(rootCfg ConfigProvider, typ string) (ConfigSection, targetSecType, error) { //nolint:unparam
|
||||||
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
|
targetSec, err := rootCfg.GetSection(storageSectionName + "." + typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsValidStorageType(StorageType(typ)) {
|
if !IsValidStorageType(StorageType(typ)) {
|
||||||
|
@ -163,10 +163,7 @@ func (a *AzureBlobStorage) getObjectNameFromPath(path string) string {
|
|||||||
|
|
||||||
// Open opens a file
|
// Open opens a file
|
||||||
func (a *AzureBlobStorage) Open(path string) (Object, error) {
|
func (a *AzureBlobStorage) Open(path string) (Object, error) {
|
||||||
blobClient, err := a.getBlobClient(path)
|
blobClient := a.getBlobClient(path)
|
||||||
if err != nil {
|
|
||||||
return nil, convertAzureBlobErr(err)
|
|
||||||
}
|
|
||||||
res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
|
res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, convertAzureBlobErr(err)
|
return nil, convertAzureBlobErr(err)
|
||||||
@ -229,10 +226,7 @@ func (a azureBlobFileInfo) Sys() any {
|
|||||||
|
|
||||||
// Stat returns the stat information of the object
|
// Stat returns the stat information of the object
|
||||||
func (a *AzureBlobStorage) Stat(path string) (os.FileInfo, error) {
|
func (a *AzureBlobStorage) Stat(path string) (os.FileInfo, error) {
|
||||||
blobClient, err := a.getBlobClient(path)
|
blobClient := a.getBlobClient(path)
|
||||||
if err != nil {
|
|
||||||
return nil, convertAzureBlobErr(err)
|
|
||||||
}
|
|
||||||
res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
|
res, err := blobClient.GetProperties(a.ctx, &blob.GetPropertiesOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, convertAzureBlobErr(err)
|
return nil, convertAzureBlobErr(err)
|
||||||
@ -247,20 +241,14 @@ func (a *AzureBlobStorage) Stat(path string) (os.FileInfo, error) {
|
|||||||
|
|
||||||
// Delete delete a file
|
// Delete delete a file
|
||||||
func (a *AzureBlobStorage) Delete(path string) error {
|
func (a *AzureBlobStorage) Delete(path string) error {
|
||||||
blobClient, err := a.getBlobClient(path)
|
blobClient := a.getBlobClient(path)
|
||||||
if err != nil {
|
_, err := blobClient.Delete(a.ctx, nil)
|
||||||
return convertAzureBlobErr(err)
|
|
||||||
}
|
|
||||||
_, err = blobClient.Delete(a.ctx, nil)
|
|
||||||
return convertAzureBlobErr(err)
|
return convertAzureBlobErr(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
|
||||||
func (a *AzureBlobStorage) URL(path, name string) (*url.URL, error) {
|
func (a *AzureBlobStorage) URL(path, name string) (*url.URL, error) {
|
||||||
blobClient, err := a.getBlobClient(path)
|
blobClient := a.getBlobClient(path)
|
||||||
if err != nil {
|
|
||||||
return nil, convertAzureBlobErr(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
u, err := blobClient.GetSASURL(sas.BlobPermissions{
|
u, err := blobClient.GetSASURL(sas.BlobPermissions{
|
||||||
@ -290,10 +278,7 @@ func (a *AzureBlobStorage) IterateObjects(dirName string, fn func(path string, o
|
|||||||
return convertAzureBlobErr(err)
|
return convertAzureBlobErr(err)
|
||||||
}
|
}
|
||||||
for _, object := range resp.Segment.BlobItems {
|
for _, object := range resp.Segment.BlobItems {
|
||||||
blobClient, err := a.getBlobClient(*object.Name)
|
blobClient := a.getBlobClient(*object.Name)
|
||||||
if err != nil {
|
|
||||||
return convertAzureBlobErr(err)
|
|
||||||
}
|
|
||||||
object := &azureBlobObject{
|
object := &azureBlobObject{
|
||||||
Context: a.ctx,
|
Context: a.ctx,
|
||||||
blobClient: blobClient,
|
blobClient: blobClient,
|
||||||
@ -313,8 +298,8 @@ func (a *AzureBlobStorage) IterateObjects(dirName string, fn func(path string, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete delete a file
|
// Delete delete a file
|
||||||
func (a *AzureBlobStorage) getBlobClient(path string) (*blob.Client, error) {
|
func (a *AzureBlobStorage) getBlobClient(path string) *blob.Client {
|
||||||
return a.client.ServiceClient().NewContainerClient(a.cfg.Container).NewBlobClient(a.buildAzureBlobPath(path)), nil
|
return a.client.ServiceClient().NewContainerClient(a.cfg.Container).NewBlobClient(a.buildAzureBlobPath(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -25,7 +25,8 @@ type MarkupOption struct {
|
|||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
@ -53,7 +54,8 @@ type MarkdownOption struct {
|
|||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// Tag represents a repository tag
|
// Tag represents a repository tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -38,3 +40,29 @@ type CreateTagOption struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtection represents a tag protection
|
||||||
|
type TagProtection struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Created time.Time `json:"created_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtectionOption options for creating a tag protection
|
||||||
|
type CreateTagProtectionOption struct {
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtectionOption options for editing a tag protection
|
||||||
|
type EditTagProtectionOption struct {
|
||||||
|
NamePattern *string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
@ -8,8 +8,7 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/system"
|
"code.gitea.io/gitea/models/system"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBStore can be used to store app state items in local filesystem
|
// DBStore can be used to store app state items in local filesystem
|
||||||
@ -24,7 +23,7 @@ func (f *DBStore) Get(ctx context.Context, item StateItem) error {
|
|||||||
if content == "" {
|
if content == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return json.Unmarshal(util.StringToReadOnlyBytes(content), item)
|
return json.Unmarshal(util.UnsafeStringToBytes(content), item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set saves the state item
|
// Set saves the state item
|
||||||
@ -33,5 +32,5 @@ func (f *DBStore) Set(ctx context.Context, item StateItem) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return system.SaveAppStateContent(ctx, item.Name(), util.BytesToReadOnlyString(b))
|
return system.SaveAppStateContent(ctx, item.Name(), util.UnsafeBytesToString(b))
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -237,8 +238,8 @@ func DotEscape(raw string) string {
|
|||||||
|
|
||||||
// Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
|
// Iif is an "inline-if", similar util.Iif[T] but templates need the non-generic version,
|
||||||
// and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
|
// and it could be simply used as "{{Iif expr trueVal}}" (omit the falseVal).
|
||||||
func Iif(condition bool, vals ...any) any {
|
func Iif(condition any, vals ...any) any {
|
||||||
if condition {
|
if isTemplateTruthy(condition) {
|
||||||
return vals[0]
|
return vals[0]
|
||||||
} else if len(vals) > 1 {
|
} else if len(vals) > 1 {
|
||||||
return vals[1]
|
return vals[1]
|
||||||
@ -246,6 +247,32 @@ func Iif(condition bool, vals ...any) any {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTemplateTruthy(v any) bool {
|
||||||
|
if v == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return rv.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return rv.Int() != 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return rv.Uint() != 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return rv.Float() != 0
|
||||||
|
case reflect.Complex64, reflect.Complex128:
|
||||||
|
return rv.Complex() != 0
|
||||||
|
case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
|
||||||
|
return rv.Len() > 0
|
||||||
|
case reflect.Struct:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return !rv.IsNil()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Eval the expression and return the result, see the comment of eval.Expr for details.
|
// Eval the expression and return the result, see the comment of eval.Expr for details.
|
||||||
// To use this helper function in templates, pass each token as a separate parameter.
|
// To use this helper function in templates, pass each token as a separate parameter.
|
||||||
//
|
//
|
||||||
|
@ -5,8 +5,11 @@ package templates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,3 +68,41 @@ func TestHTMLFormat(t *testing.T) {
|
|||||||
func TestSanitizeHTML(t *testing.T) {
|
func TestSanitizeHTML(t *testing.T) {
|
||||||
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
|
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateTruthy(t *testing.T) {
|
||||||
|
tmpl := template.New("test")
|
||||||
|
tmpl.Funcs(template.FuncMap{"Iif": Iif})
|
||||||
|
template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`))
|
||||||
|
|
||||||
|
cases := []any{
|
||||||
|
nil, false, true, "", "string", 0, 1,
|
||||||
|
byte(0), byte(1), int64(0), int64(1), float64(0), float64(1),
|
||||||
|
complex(0, 0), complex(1, 0),
|
||||||
|
(chan int)(nil), make(chan int),
|
||||||
|
(func())(nil), func() {},
|
||||||
|
util.ToPointer(0), util.ToPointer(util.ToPointer(0)),
|
||||||
|
util.ToPointer(1), util.ToPointer(util.ToPointer(1)),
|
||||||
|
[0]int{},
|
||||||
|
[1]int{0},
|
||||||
|
[]int(nil),
|
||||||
|
[]int{},
|
||||||
|
[]int{0},
|
||||||
|
map[any]any(nil),
|
||||||
|
map[any]any{},
|
||||||
|
map[any]any{"k": "v"},
|
||||||
|
(*struct{})(nil),
|
||||||
|
struct{}{},
|
||||||
|
util.ToPointer(struct{}{}),
|
||||||
|
}
|
||||||
|
w := &strings.Builder{}
|
||||||
|
truthyCount := 0
|
||||||
|
for i, v := range cases {
|
||||||
|
w.Reset()
|
||||||
|
assert.NoError(t, tmpl.Execute(w, struct{ Value any }{v}), "case %d (%T) %#v fails", i, v, v)
|
||||||
|
out := w.String()
|
||||||
|
truthyCount += util.Iif(out == "true:true", 1, 0)
|
||||||
|
truthyMatches := out == "true:true" || out == "false:false"
|
||||||
|
assert.True(t, truthyMatches, "case %d (%T) %#v fail: %s", i, v, v, out)
|
||||||
|
}
|
||||||
|
assert.True(t, truthyCount != 0 && truthyCount != len(cases))
|
||||||
|
}
|
||||||
|
@ -34,8 +34,10 @@ func IsNormalPageCompleted(s string) bool {
|
|||||||
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
|
return strings.Contains(s, `<footer class="page-footer"`) && strings.Contains(s, `</html>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MockVariableValue[T any](p *T, v T) (reset func()) {
|
func MockVariableValue[T any](p *T, v ...T) (reset func()) {
|
||||||
old := *p
|
old := *p
|
||||||
*p = v
|
if len(v) > 0 {
|
||||||
|
*p = v[0]
|
||||||
|
}
|
||||||
return func() { *p = old }
|
return func() { *p = old }
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,7 @@ import (
|
|||||||
// GenerateKeyPair generates a public and private keypair
|
// GenerateKeyPair generates a public and private keypair
|
||||||
func GenerateKeyPair(bits int) (string, string, error) {
|
func GenerateKeyPair(bits int) (string, string, error) {
|
||||||
priv, _ := rsa.GenerateKey(rand.Reader, bits)
|
priv, _ := rsa.GenerateKey(rand.Reader, bits)
|
||||||
privPem, err := pemBlockForPriv(priv)
|
privPem := pemBlockForPriv(priv)
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
pubPem, err := pemBlockForPub(&priv.PublicKey)
|
pubPem, err := pemBlockForPub(&priv.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
@ -26,12 +23,12 @@ func GenerateKeyPair(bits int) (string, string, error) {
|
|||||||
return privPem, pubPem, nil
|
return privPem, pubPem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pemBlockForPriv(priv *rsa.PrivateKey) (string, error) {
|
func pemBlockForPriv(priv *rsa.PrivateKey) string {
|
||||||
privBytes := pem.EncodeToMemory(&pem.Block{
|
privBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
Type: "RSA PRIVATE KEY",
|
Type: "RSA PRIVATE KEY",
|
||||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||||
})
|
})
|
||||||
return string(privBytes), nil
|
return string(privBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
|
func pemBlockForPub(pub *rsa.PublicKey) (string, error) {
|
||||||
|
@ -6,8 +6,6 @@ package util
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type sanitizedError struct {
|
type sanitizedError struct {
|
||||||
@ -33,7 +31,7 @@ var schemeSep = []byte("://")
|
|||||||
|
|
||||||
// SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://") for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
|
// SanitizeCredentialURLs remove all credentials in URLs (starting with "scheme://") for the input string: "https://user:pass@domain.com" => "https://sanitized-credential@domain.com"
|
||||||
func SanitizeCredentialURLs(s string) string {
|
func SanitizeCredentialURLs(s string) string {
|
||||||
bs := util.StringToReadOnlyBytes(s)
|
bs := UnsafeStringToBytes(s)
|
||||||
schemeSepPos := bytes.Index(bs, schemeSep)
|
schemeSepPos := bytes.Index(bs, schemeSep)
|
||||||
if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
|
if schemeSepPos == -1 || bytes.IndexByte(bs[schemeSepPos:], '@') == -1 {
|
||||||
return s // fast return if there is no URL scheme or no userinfo
|
return s // fast return if there is no URL scheme or no userinfo
|
||||||
@ -70,5 +68,5 @@ func SanitizeCredentialURLs(s string) string {
|
|||||||
schemeSepPos = bytes.Index(bs, schemeSep)
|
schemeSepPos = bytes.Index(bs, schemeSep)
|
||||||
}
|
}
|
||||||
out = append(out, bs...)
|
out = append(out, bs...)
|
||||||
return util.BytesToReadOnlyString(out)
|
return UnsafeBytesToString(out)
|
||||||
}
|
}
|
||||||
|
@ -87,11 +87,11 @@ func ToSnakeCase(input string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
|
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
|
||||||
// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach
|
|
||||||
func UnsafeBytesToString(b []byte) string {
|
func UnsafeBytesToString(b []byte) string {
|
||||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsafeStringToBytes uses Go's unsafe package to convert a string to a byte slice.
|
||||||
func UnsafeStringToBytes(s string) []byte {
|
func UnsafeStringToBytes(s string) []byte {
|
||||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ func GetSiteCookie(req *http.Request, name string) string {
|
|||||||
|
|
||||||
// SetSiteCookie returns given cookie value from request header.
|
// SetSiteCookie returns given cookie value from request header.
|
||||||
func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
||||||
|
// Previous versions would use a cookie path with a trailing /.
|
||||||
|
// These are more specific than cookies without a trailing /, so
|
||||||
|
// we need to delete these if they exist.
|
||||||
|
deleteLegacySiteCookie(resp, name)
|
||||||
cookie := &http.Cookie{
|
cookie := &http.Cookie{
|
||||||
Name: name,
|
Name: name,
|
||||||
Value: url.QueryEscape(value),
|
Value: url.QueryEscape(value),
|
||||||
@ -46,10 +50,6 @@ func SetSiteCookie(resp http.ResponseWriter, name, value string, maxAge int) {
|
|||||||
SameSite: setting.SessionConfig.SameSite,
|
SameSite: setting.SessionConfig.SameSite,
|
||||||
}
|
}
|
||||||
resp.Header().Add("Set-Cookie", cookie.String())
|
resp.Header().Add("Set-Cookie", cookie.String())
|
||||||
// Previous versions would use a cookie path with a trailing /.
|
|
||||||
// These are more specific than cookies without a trailing /, so
|
|
||||||
// we need to delete these if they exist.
|
|
||||||
deleteLegacySiteCookie(resp, name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
|
// deleteLegacySiteCookie deletes the cookie with the given name at the cookie
|
||||||
|
47
options/gitignore/IAR
Normal file
47
options/gitignore/IAR
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Compiled binaries
|
||||||
|
*.o
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
*.hex
|
||||||
|
*.map
|
||||||
|
*.out
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Trash
|
||||||
|
*.bak
|
||||||
|
thumbs.db
|
||||||
|
*.~*
|
||||||
|
|
||||||
|
# IAR Settings
|
||||||
|
**/settings/*.crun
|
||||||
|
**/settings/*.dbgdt
|
||||||
|
**/settings/*.cspy
|
||||||
|
**/settings/*.cspy.*
|
||||||
|
**/settings/*.xcl
|
||||||
|
**/settings/*.dni
|
||||||
|
**/settings/*.wsdt
|
||||||
|
**/settings/*.wspos
|
||||||
|
|
||||||
|
# IAR Debug Exe
|
||||||
|
**/Exe/*.sim
|
||||||
|
|
||||||
|
# IAR Debug Obj
|
||||||
|
**/Obj/*.pbd
|
||||||
|
**/Obj/*.pbd.*
|
||||||
|
**/Obj/*.pbi
|
||||||
|
**/Obj/*.pbi.*
|
||||||
|
|
||||||
|
# IAR project "Debug" directory
|
||||||
|
Debug/
|
||||||
|
|
||||||
|
# IAR project "Release" directory
|
||||||
|
Release/
|
||||||
|
|
||||||
|
# IAR project settings directory
|
||||||
|
settings/
|
||||||
|
|
||||||
|
# IAR backup files
|
||||||
|
Backup*
|
||||||
|
|
||||||
|
# IAR .dep files
|
||||||
|
*.dep
|
@ -42,10 +42,3 @@ fastlane/report.xml
|
|||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
fastlane/screenshots/**/*.png
|
fastlane/screenshots/**/*.png
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
|
|
||||||
# Code Injection
|
|
||||||
#
|
|
||||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
|
||||||
# https://github.com/johnno1962/injectionforxcode
|
|
||||||
|
|
||||||
iOSInjectionProject/
|
|
||||||
|
@ -35,6 +35,3 @@ override.tf.json
|
|||||||
# Ignore CLI configuration files
|
# Ignore CLI configuration files
|
||||||
.terraformrc
|
.terraformrc
|
||||||
terraform.rc
|
terraform.rc
|
||||||
|
|
||||||
# Ignore hcl file
|
|
||||||
.terraform.lock.hcl
|
|
||||||
|
@ -164,6 +164,8 @@ search=Hledat...
|
|||||||
type_tooltip=Druh vyhledávání
|
type_tooltip=Druh vyhledávání
|
||||||
fuzzy=Fuzzy
|
fuzzy=Fuzzy
|
||||||
fuzzy_tooltip=Zahrnout výsledky, které také úzce odpovídají hledanému výrazu
|
fuzzy_tooltip=Zahrnout výsledky, které také úzce odpovídají hledanému výrazu
|
||||||
|
exact=Přesně
|
||||||
|
exact_tooltip=Zahrnout pouze výsledky, které přesně odpovídají hledanému výrazu
|
||||||
repo_kind=Hledat repozitáře...
|
repo_kind=Hledat repozitáře...
|
||||||
user_kind=Hledat uživatele...
|
user_kind=Hledat uživatele...
|
||||||
org_kind=Hledat organizace...
|
org_kind=Hledat organizace...
|
||||||
@ -177,6 +179,8 @@ branch_kind=Hledat větve...
|
|||||||
commit_kind=Hledat commity...
|
commit_kind=Hledat commity...
|
||||||
runner_kind=Hledat runnery...
|
runner_kind=Hledat runnery...
|
||||||
no_results=Nebyly nalezeny žádné odpovídající výsledky.
|
no_results=Nebyly nalezeny žádné odpovídající výsledky.
|
||||||
|
issue_kind=Hledat úkoly...
|
||||||
|
pull_kind=Hledat pull request...
|
||||||
keyword_search_unavailable=Hledání podle klíčového slova není momentálně dostupné. Obraťte se na správce webu.
|
keyword_search_unavailable=Hledání podle klíčového slova není momentálně dostupné. Obraťte se na správce webu.
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
@ -432,6 +436,7 @@ oauth_signin_submit=Propojit účet
|
|||||||
oauth.signin.error=Došlo k chybě při zpracování žádosti o autorizaci. Pokud tato chyba přetrvává, obraťte se na správce webu.
|
oauth.signin.error=Došlo k chybě při zpracování žádosti o autorizaci. Pokud tato chyba přetrvává, obraťte se na správce webu.
|
||||||
oauth.signin.error.access_denied=Žádost o autorizaci byla zamítnuta.
|
oauth.signin.error.access_denied=Žádost o autorizaci byla zamítnuta.
|
||||||
oauth.signin.error.temporarily_unavailable=Autorizace se nezdařila, protože ověřovací server je dočasně nedostupný. Opakujte akci později.
|
oauth.signin.error.temporarily_unavailable=Autorizace se nezdařila, protože ověřovací server je dočasně nedostupný. Opakujte akci později.
|
||||||
|
oauth_callback_unable_auto_reg=Automatická registrace je povolena, ale OAuth2 poskytovatel %[1]s vrátil chybějící pole: %[2]s, nelze vytvořit účet automaticky, vytvořte účet nebo se připojte k účtu, nebo kontaktujte správce webu.
|
||||||
openid_connect_submit=Připojit
|
openid_connect_submit=Připojit
|
||||||
openid_connect_title=Připojení k existujícímu účtu
|
openid_connect_title=Připojení k existujícímu účtu
|
||||||
openid_connect_desc=Zvolené OpenID URI není známé. Přidružte nový účet zde.
|
openid_connect_desc=Zvolené OpenID URI není známé. Přidružte nový účet zde.
|
||||||
@ -712,8 +717,9 @@ cancel=Zrušit
|
|||||||
language=Jazyk
|
language=Jazyk
|
||||||
ui=Motiv vzhledu
|
ui=Motiv vzhledu
|
||||||
hidden_comment_types=Skryté typy komentářů
|
hidden_comment_types=Skryté typy komentářů
|
||||||
|
hidden_comment_types_description=Zde zaškrtnuté typy komentářů nebudou zobrazeny na stránkách úkolů. Zaškrtnutím položky „Štítek“ například odstraní všechny komentáře „{uživatel} přidal/odstranil {štítek}“.
|
||||||
hidden_comment_types.ref_tooltip=Komentáře, na které se odkazovalo z jiného úkolu/commitu/…
|
hidden_comment_types.ref_tooltip=Komentáře, na které se odkazovalo z jiného úkolu/commitu/…
|
||||||
hidden_comment_types.issue_ref_tooltip=Komentáře, kde uživatel změní větev/značku spojenou s problémem
|
hidden_comment_types.issue_ref_tooltip=Komentáře, kde uživatel změní větev/značku spojenou s úkolem
|
||||||
comment_type_group_reference=Reference
|
comment_type_group_reference=Reference
|
||||||
comment_type_group_label=Štítek
|
comment_type_group_label=Štítek
|
||||||
comment_type_group_milestone=Milník
|
comment_type_group_milestone=Milník
|
||||||
@ -758,6 +764,8 @@ manage_themes=Vyberte výchozí motiv vzhledu
|
|||||||
manage_openid=Správa OpenID adres
|
manage_openid=Správa OpenID adres
|
||||||
email_desc=Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla, a pokud není skrytá, pro operace Gitu.
|
email_desc=Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla, a pokud není skrytá, pro operace Gitu.
|
||||||
theme_desc=Toto bude váš výchozí motiv vzhledu napříč stránkou.
|
theme_desc=Toto bude váš výchozí motiv vzhledu napříč stránkou.
|
||||||
|
theme_colorblindness_help=Podpora šablony pro barvoslepost
|
||||||
|
theme_colorblindness_prompt=Gitea právě získala některé motivy se základní podporou barvosleposti, které mají pouze několik barev. Práce stále probíhá. Další vylepšení by bylo možné provést definováním více barev v CSS souborů.
|
||||||
primary=Hlavní
|
primary=Hlavní
|
||||||
activated=Aktivován
|
activated=Aktivován
|
||||||
requires_activation=Vyžaduje aktivaci
|
requires_activation=Vyžaduje aktivaci
|
||||||
@ -882,6 +890,7 @@ repo_and_org_access=Repozitář a přístup organizace
|
|||||||
permissions_public_only=Pouze veřejnost
|
permissions_public_only=Pouze veřejnost
|
||||||
permissions_access_all=Vše (veřejné, soukromé a omezené)
|
permissions_access_all=Vše (veřejné, soukromé a omezené)
|
||||||
select_permissions=Vyberte oprávnění
|
select_permissions=Vyberte oprávnění
|
||||||
|
permission_not_set=Není nastaveno
|
||||||
permission_no_access=Bez přístupu
|
permission_no_access=Bez přístupu
|
||||||
permission_read=Přečtené
|
permission_read=Přečtené
|
||||||
permission_write=čtení i zápis
|
permission_write=čtení i zápis
|
||||||
@ -1061,6 +1070,7 @@ watchers=Sledující
|
|||||||
stargazers=Sledující
|
stargazers=Sledující
|
||||||
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře.
|
stars_remove_warning=Tímto odstraníte všechny hvězdičky z tohoto repozitáře.
|
||||||
forks=Rozštěpení
|
forks=Rozštěpení
|
||||||
|
stars=Oblíbené
|
||||||
reactions_more=a %d dalších
|
reactions_more=a %d dalších
|
||||||
unit_disabled=Správce webu zakázal tuto sekci repozitáře.
|
unit_disabled=Správce webu zakázal tuto sekci repozitáře.
|
||||||
language_other=Jiný
|
language_other=Jiný
|
||||||
@ -1108,7 +1118,7 @@ template.one_item=Musíte vybrat alespoň jednu položku šablony
|
|||||||
template.invalid=Musíte vybrat repositář šablony
|
template.invalid=Musíte vybrat repositář šablony
|
||||||
|
|
||||||
archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové úkoly nebo pull requesty.
|
archive.title=Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové úkoly nebo pull requesty.
|
||||||
archive.title_date=Tento repositář byl archivován %s. Můžete zobrazit soubory a klonovat je, ale nemůžete nahrávat ani otevírat problémy nebo pull requesty.
|
archive.title_date=Tento repositář byl archivován %s. Můžete zobrazit soubory a klonovat je, ale nemůžete nahrávat ani otevírat úkoly nebo pull requesty.
|
||||||
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly.
|
archive.issue.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat úkoly.
|
||||||
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat pull requesty.
|
archive.pull.nocomment=Tento repozitář je archivovaný. Nemůžete komentovat pull requesty.
|
||||||
|
|
||||||
@ -1228,6 +1238,9 @@ file_view_rendered=Zobrazit vykreslené
|
|||||||
file_view_raw=Zobrazit v surovém stavu
|
file_view_raw=Zobrazit v surovém stavu
|
||||||
file_permalink=Trvalý odkaz
|
file_permalink=Trvalý odkaz
|
||||||
file_too_large=Soubor je příliš velký pro zobrazení.
|
file_too_large=Soubor je příliš velký pro zobrazení.
|
||||||
|
file_is_empty=Soubor je prázdný.
|
||||||
|
code_preview_line_from_to=Řádky %[1]d do%[2]d v %[3]s
|
||||||
|
code_preview_line_in=Řádek %[1]d v %[2]s
|
||||||
invisible_runes_header=`Tento soubor obsahuje neviditelné znaky Unicode`
|
invisible_runes_header=`Tento soubor obsahuje neviditelné znaky Unicode`
|
||||||
invisible_runes_description=`Tento soubor obsahuje neviditelné Unicode znaky, které jsou pro člověka nerozeznatelné, ale mohou být zpracovány jiným způsobem. Pokud si myslíte, že je to záměrné, můžete toto varování bezpečně ignorovat. Použijte tlačítko Escape sekvence k jejich zobrazení.`
|
invisible_runes_description=`Tento soubor obsahuje neviditelné Unicode znaky, které jsou pro člověka nerozeznatelné, ale mohou být zpracovány jiným způsobem. Pokud si myslíte, že je to záměrné, můžete toto varování bezpečně ignorovat. Použijte tlačítko Escape sekvence k jejich zobrazení.`
|
||||||
ambiguous_runes_header=`Tento soubor obsahuje nejednoznačné znaky Unicode`
|
ambiguous_runes_header=`Tento soubor obsahuje nejednoznačné znaky Unicode`
|
||||||
@ -1282,6 +1295,7 @@ editor.or=nebo
|
|||||||
editor.cancel_lower=Zrušit
|
editor.cancel_lower=Zrušit
|
||||||
editor.commit_signed_changes=Odevzdat podepsané změny
|
editor.commit_signed_changes=Odevzdat podepsané změny
|
||||||
editor.commit_changes=Odevzdat změny
|
editor.commit_changes=Odevzdat změny
|
||||||
|
editor.add_tmpl=Přidán „{nazev_souboru}“
|
||||||
editor.add=Přidat %s
|
editor.add=Přidat %s
|
||||||
editor.update=Aktualizovat %s
|
editor.update=Aktualizovat %s
|
||||||
editor.delete=Odstranit %s
|
editor.delete=Odstranit %s
|
||||||
@ -1310,6 +1324,7 @@ editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není
|
|||||||
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
|
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
|
||||||
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
||||||
editor.commit_id_not_matching=ID commitu se neshoduje s ID, když jsi začal/a s úpravami. Odevzdat do záplatové větve a poté sloučit.
|
editor.commit_id_not_matching=ID commitu se neshoduje s ID, když jsi začal/a s úpravami. Odevzdat do záplatové větve a poté sloučit.
|
||||||
|
editor.push_out_of_date=Nahrání se zdá být zastaralé.
|
||||||
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
||||||
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
||||||
editor.no_changes_to_show=Žádné změny k zobrazení.
|
editor.no_changes_to_show=Žádné změny k zobrazení.
|
||||||
@ -1364,6 +1379,7 @@ commitstatus.success=Úspěch
|
|||||||
ext_issues=Přístup k externím úkolům
|
ext_issues=Přístup k externím úkolům
|
||||||
ext_issues.desc=Odkaz na externí systém úkolů.
|
ext_issues.desc=Odkaz na externí systém úkolů.
|
||||||
|
|
||||||
|
projects.desc=Spravujte úkoly a pull requesty v projektech.
|
||||||
projects.description=Popis (volitelné)
|
projects.description=Popis (volitelné)
|
||||||
projects.description_placeholder=Popis
|
projects.description_placeholder=Popis
|
||||||
projects.create=Vytvořit projekt
|
projects.create=Vytvořit projekt
|
||||||
@ -1391,6 +1407,7 @@ projects.column.new=Nový sloupec
|
|||||||
projects.column.set_default=Nastavit jako výchozí
|
projects.column.set_default=Nastavit jako výchozí
|
||||||
projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekategorizované úkoly a požadavky na natažení
|
projects.column.set_default_desc=Nastavit tento sloupec jako výchozí pro nekategorizované úkoly a požadavky na natažení
|
||||||
projects.column.delete=Smazat sloupec
|
projects.column.delete=Smazat sloupec
|
||||||
|
projects.column.deletion_desc=Smazání sloupce projektu přesune všechny související úkoly do výchozího sloupce. Pokračovat?
|
||||||
projects.column.color=Barva
|
projects.column.color=Barva
|
||||||
projects.open=Otevřít
|
projects.open=Otevřít
|
||||||
projects.close=Zavřít
|
projects.close=Zavřít
|
||||||
@ -1426,6 +1443,7 @@ issues.new.clear_assignees=Smazat zpracovatele
|
|||||||
issues.new.no_assignees=Bez zpracovatelů
|
issues.new.no_assignees=Bez zpracovatelů
|
||||||
issues.new.no_reviewers=Žádní posuzovatelé
|
issues.new.no_reviewers=Žádní posuzovatelé
|
||||||
issues.new.blocked_user=Nemůžete vytvořit úkol, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
issues.new.blocked_user=Nemůžete vytvořit úkol, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
||||||
|
issues.edit.already_changed=Nelze uložit změny v úkolu. Zdá se, že obsah byl již změněn jiným uživatelem. Aktualizujte stránku a zkuste ji znovu problém upravit, abyste se vyhnuli přepsání jejich změn
|
||||||
issues.edit.blocked_user=Nemůžete upravovat obsah, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
issues.edit.blocked_user=Nemůžete upravovat obsah, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
||||||
issues.choose.get_started=Začínáme
|
issues.choose.get_started=Začínáme
|
||||||
issues.choose.open_external_link=Otevřít
|
issues.choose.open_external_link=Otevřít
|
||||||
@ -1433,7 +1451,7 @@ issues.choose.blank=Výchozí
|
|||||||
issues.choose.blank_about=Vytvořit úkol z výchozí šablony.
|
issues.choose.blank_about=Vytvořit úkol z výchozí šablony.
|
||||||
issues.choose.ignore_invalid_templates=Neplatné šablony byly ignorovány
|
issues.choose.ignore_invalid_templates=Neplatné šablony byly ignorovány
|
||||||
issues.choose.invalid_templates=%v nalezených neplatných šablon
|
issues.choose.invalid_templates=%v nalezených neplatných šablon
|
||||||
issues.choose.invalid_config=Nastavení problému obsahuje chyby:
|
issues.choose.invalid_config=Nastavení úkolu obsahuje chyby:
|
||||||
issues.no_ref=Není určena žádná větev/značka
|
issues.no_ref=Není určena žádná větev/značka
|
||||||
issues.create=Vytvořit úkol
|
issues.create=Vytvořit úkol
|
||||||
issues.new_label=Nový štítek
|
issues.new_label=Nový štítek
|
||||||
@ -1534,10 +1552,12 @@ issues.context.reference_issue=Odkázat v novém úkolu
|
|||||||
issues.context.edit=Upravit
|
issues.context.edit=Upravit
|
||||||
issues.context.delete=Smazat
|
issues.context.delete=Smazat
|
||||||
issues.no_content=K dispozici není žádný popis.
|
issues.no_content=K dispozici není žádný popis.
|
||||||
issues.close=Zavřít problém
|
issues.close=Zavřít úkol
|
||||||
issues.comment_pull_merged_at=sloučený commit %[1]s do %[2]s %[3]s
|
issues.comment_pull_merged_at=sloučený commit %[1]s do %[2]s %[3]s
|
||||||
issues.comment_manually_pull_merged_at=ručně sloučený commit %[1]s do %[2]s %[3]s
|
issues.comment_manually_pull_merged_at=ručně sloučený commit %[1]s do %[2]s %[3]s
|
||||||
|
issues.close_comment_issue=Okomentovat a zavřít
|
||||||
issues.reopen_issue=Znovuotevřít
|
issues.reopen_issue=Znovuotevřít
|
||||||
|
issues.reopen_comment_issue=Znovu otevřít s komentářem
|
||||||
issues.create_comment=Okomentovat
|
issues.create_comment=Okomentovat
|
||||||
issues.comment.blocked_user=Nemůžete vytvořit nebo upravovat komentář, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
issues.comment.blocked_user=Nemůžete vytvořit nebo upravovat komentář, protože jste zablokováni zadavatelem příspěvku nebo vlastníkem repozitáře.
|
||||||
issues.closed_at=`uzavřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at=`uzavřel/a tento úkol <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
@ -1598,7 +1618,7 @@ issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
|
|||||||
issues.attachment.download=`Klikněte pro stažení „%s“`
|
issues.attachment.download=`Klikněte pro stažení „%s“`
|
||||||
issues.subscribe=Odebírat
|
issues.subscribe=Odebírat
|
||||||
issues.unsubscribe=Zrušit odběr
|
issues.unsubscribe=Zrušit odběr
|
||||||
issues.unpin_issue=Odepnout problém
|
issues.unpin_issue=Odepnout úkol
|
||||||
issues.max_pinned=Nemůžete připnout další úkoly
|
issues.max_pinned=Nemůžete připnout další úkoly
|
||||||
issues.pin_comment=připnuto %s
|
issues.pin_comment=připnuto %s
|
||||||
issues.unpin_comment=odepnul/a tento %s
|
issues.unpin_comment=odepnul/a tento %s
|
||||||
@ -1657,7 +1677,7 @@ issues.due_date_form=rrrr-mm-dd
|
|||||||
issues.due_date_form_add=Přidat termín dokončení
|
issues.due_date_form_add=Přidat termín dokončení
|
||||||
issues.due_date_form_edit=Upravit
|
issues.due_date_form_edit=Upravit
|
||||||
issues.due_date_form_remove=Odstranit
|
issues.due_date_form_remove=Odstranit
|
||||||
issues.due_date_not_writer=Potřebujete přístup k zápisu do tohoto repozitáře, abyste mohli aktualizovat datum dokončení problému.
|
issues.due_date_not_writer=Potřebujete přístup k zápisu do tohoto repozitáře, abyste mohli aktualizovat datum dokončení úkolu.
|
||||||
issues.due_date_not_set=Žádný termín dokončení.
|
issues.due_date_not_set=Žádný termín dokončení.
|
||||||
issues.due_date_added=přidal/a termín dokončení %s %s
|
issues.due_date_added=přidal/a termín dokončení %s %s
|
||||||
issues.due_date_modified=upravil/a termín termínu z %[2]s na %[1]s %[3]s
|
issues.due_date_modified=upravil/a termín termínu z %[2]s na %[1]s %[3]s
|
||||||
@ -1739,6 +1759,7 @@ compare.compare_head=porovnat
|
|||||||
pulls.desc=Povolit pull requesty a posuzování kódu.
|
pulls.desc=Povolit pull requesty a posuzování kódu.
|
||||||
pulls.new=Nový pull request
|
pulls.new=Nový pull request
|
||||||
pulls.new.blocked_user=Nemůžete vytvořit pull request, protože jste zablokování vlastníkem repozitáře.
|
pulls.new.blocked_user=Nemůžete vytvořit pull request, protože jste zablokování vlastníkem repozitáře.
|
||||||
|
pulls.edit.already_changed=Nelze uložit změny v pull requestu. Zdá se, že obsah byl již změněn jiným uživatelem. Aktualizujte stránku a zkuste znovu komentář upravit, abyste se vyhnuli přepsání jejich změn
|
||||||
pulls.view=Zobrazit pull request
|
pulls.view=Zobrazit pull request
|
||||||
pulls.compare_changes=Nový pull request
|
pulls.compare_changes=Nový pull request
|
||||||
pulls.allow_edits_from_maintainers=Povolit úpravy od správců
|
pulls.allow_edits_from_maintainers=Povolit úpravy od správců
|
||||||
@ -1858,6 +1879,7 @@ pulls.close=Zavřít pull request
|
|||||||
pulls.closed_at=`uzavřel/a tento pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
pulls.closed_at=`uzavřel/a tento pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
pulls.reopened_at=`znovuotevřel/a tento pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
pulls.reopened_at=`znovuotevřel/a tento pull request <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
pulls.cmd_instruction_hint=`Zobrazit <a class="show-instruction">instrukce příkazové řádky</a>.`
|
pulls.cmd_instruction_hint=`Zobrazit <a class="show-instruction">instrukce příkazové řádky</a>.`
|
||||||
|
pulls.cmd_instruction_checkout_title=Checkout
|
||||||
pulls.cmd_instruction_checkout_desc=Z vašeho repositáře projektu se podívejte na novou větev a vyzkoušejte změny.
|
pulls.cmd_instruction_checkout_desc=Z vašeho repositáře projektu se podívejte na novou větev a vyzkoušejte změny.
|
||||||
pulls.cmd_instruction_merge_title=Sloučit
|
pulls.cmd_instruction_merge_title=Sloučit
|
||||||
pulls.cmd_instruction_merge_desc=Slučte změny a aktualizujte je na Gitea.
|
pulls.cmd_instruction_merge_desc=Slučte změny a aktualizujte je na Gitea.
|
||||||
@ -1883,6 +1905,7 @@ pulls.recently_pushed_new_branches=Nahráli jste větev <strong>%[1]s</strong> %
|
|||||||
|
|
||||||
pull.deleted_branch=(odstraněno):%s
|
pull.deleted_branch=(odstraněno):%s
|
||||||
|
|
||||||
|
comments.edit.already_changed=Nelze uložit změny v komentáři. Zdá se, že obsah byl již změněn jiným uživatelem. Aktualizujte stránku a zkuste znovu komentář upravit, abyste se vyhnuli přepsání jejich změn
|
||||||
|
|
||||||
milestones.new=Nový milník
|
milestones.new=Nový milník
|
||||||
milestones.closed=Zavřen dne %s
|
milestones.closed=Zavřen dne %s
|
||||||
@ -1959,6 +1982,7 @@ wiki.page_name_desc=Zadejte název této Wiki stránky. Některé speciální n
|
|||||||
wiki.original_git_entry_tooltip=Zobrazit originální Git soubor namísto použití přátelského odkazu.
|
wiki.original_git_entry_tooltip=Zobrazit originální Git soubor namísto použití přátelského odkazu.
|
||||||
|
|
||||||
activity=Aktivita
|
activity=Aktivita
|
||||||
|
activity.navbar.pulse=Pulz
|
||||||
activity.navbar.code_frequency=Frekvence kódu
|
activity.navbar.code_frequency=Frekvence kódu
|
||||||
activity.navbar.contributors=Přispěvatelé
|
activity.navbar.contributors=Přispěvatelé
|
||||||
activity.navbar.recent_commits=Nedávné commity
|
activity.navbar.recent_commits=Nedávné commity
|
||||||
@ -2052,11 +2076,13 @@ settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Právě t
|
|||||||
settings.mirror_settings.docs.disabled_push_mirror.info=Push zrcadla byla zakázána administrátorem vašeho webu.
|
settings.mirror_settings.docs.disabled_push_mirror.info=Push zrcadla byla zakázána administrátorem vašeho webu.
|
||||||
settings.mirror_settings.docs.no_new_mirrors=Váš repozitář zrcadlí změny do nebo z jiného repozitáře. Mějte prosím na paměti, že v tuto chvíli nemůžete vytvořit žádná nová zrcadla.
|
settings.mirror_settings.docs.no_new_mirrors=Váš repozitář zrcadlí změny do nebo z jiného repozitáře. Mějte prosím na paměti, že v tuto chvíli nemůžete vytvořit žádná nová zrcadla.
|
||||||
settings.mirror_settings.docs.can_still_use=I když nemůžete upravit stávající zrcadla nebo vytvořit nová, stále můžete použít své stávající zrcadlo.
|
settings.mirror_settings.docs.can_still_use=I když nemůžete upravit stávající zrcadla nebo vytvořit nová, stále můžete použít své stávající zrcadlo.
|
||||||
|
settings.mirror_settings.docs.pull_mirror_instructions=Chcete-li nastavit zrcadlo pro natažení, konzultujte prosím:
|
||||||
settings.mirror_settings.docs.more_information_if_disabled=Více informací o zrcadlech pro nahrání a natažení naleznete zde:
|
settings.mirror_settings.docs.more_information_if_disabled=Více informací o zrcadlech pro nahrání a natažení naleznete zde:
|
||||||
settings.mirror_settings.docs.doc_link_title=Jak mohu zrcadlit repozitáře?
|
settings.mirror_settings.docs.doc_link_title=Jak mohu zrcadlit repozitáře?
|
||||||
settings.mirror_settings.docs.doc_link_pull_section=sekci "stahovat ze vzdáleného úložiště" v dokumentaci.
|
settings.mirror_settings.docs.doc_link_pull_section=sekci "stahovat ze vzdáleného úložiště" v dokumentaci.
|
||||||
settings.mirror_settings.docs.pulling_remote_title=Stažení ze vzdáleného úložiště
|
settings.mirror_settings.docs.pulling_remote_title=Stažení ze vzdáleného úložiště
|
||||||
settings.mirror_settings.mirrored_repository=Zrcadlený repozitář
|
settings.mirror_settings.mirrored_repository=Zrcadlený repozitář
|
||||||
|
settings.mirror_settings.pushed_repository=Odeslaný repozitář
|
||||||
settings.mirror_settings.direction=Směr
|
settings.mirror_settings.direction=Směr
|
||||||
settings.mirror_settings.direction.pull=Natáhnout
|
settings.mirror_settings.direction.pull=Natáhnout
|
||||||
settings.mirror_settings.direction.push=Nahrát
|
settings.mirror_settings.direction.push=Nahrát
|
||||||
@ -2079,6 +2105,7 @@ settings.advanced_settings=Pokročilá nastavení
|
|||||||
settings.wiki_desc=Povolit Wiki repozitáře
|
settings.wiki_desc=Povolit Wiki repozitáře
|
||||||
settings.use_internal_wiki=Používat vestavěnou Wiki
|
settings.use_internal_wiki=Používat vestavěnou Wiki
|
||||||
settings.default_wiki_branch_name=Výchozí název větve Wiki
|
settings.default_wiki_branch_name=Výchozí název větve Wiki
|
||||||
|
settings.default_wiki_everyone_access=Výchozí přístupová práva pro přihlášené uživatele:
|
||||||
settings.failed_to_change_default_wiki_branch=Změna výchozí větve wiki se nezdařila.
|
settings.failed_to_change_default_wiki_branch=Změna výchozí větve wiki se nezdařila.
|
||||||
settings.use_external_wiki=Používat externí Wiki
|
settings.use_external_wiki=Používat externí Wiki
|
||||||
settings.external_wiki_url=URL externí Wiki
|
settings.external_wiki_url=URL externí Wiki
|
||||||
@ -2760,6 +2787,7 @@ teams.invite.by=Pozvání od %s
|
|||||||
teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže.
|
teams.invite.description=Pro připojení k týmu klikněte na tlačítko níže.
|
||||||
|
|
||||||
[admin]
|
[admin]
|
||||||
|
maintenance=Údržba
|
||||||
dashboard=Přehled
|
dashboard=Přehled
|
||||||
self_check=Samokontrola
|
self_check=Samokontrola
|
||||||
identity_access=Identita a přístup
|
identity_access=Identita a přístup
|
||||||
@ -2782,6 +2810,7 @@ settings=Nastavení správce
|
|||||||
|
|
||||||
dashboard.new_version_hint=Gitea %s je nyní k dispozici, právě u vás běži %s. Podívej se na <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blogu</a> pro více informací.
|
dashboard.new_version_hint=Gitea %s je nyní k dispozici, právě u vás běži %s. Podívej se na <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">blogu</a> pro více informací.
|
||||||
dashboard.statistic=Souhrn
|
dashboard.statistic=Souhrn
|
||||||
|
dashboard.maintenance_operations=Operace údržby
|
||||||
dashboard.system_status=Status systému
|
dashboard.system_status=Status systému
|
||||||
dashboard.operation_name=Název operace
|
dashboard.operation_name=Název operace
|
||||||
dashboard.operation_switch=Přepnout
|
dashboard.operation_switch=Přepnout
|
||||||
@ -3067,12 +3096,14 @@ auths.tips=Tipy
|
|||||||
auths.tips.oauth2.general=Ověřování OAuth2
|
auths.tips.oauth2.general=Ověřování OAuth2
|
||||||
auths.tips.oauth2.general.tip=Při registraci nové OAuth2 autentizace by URL callbacku/přesměrování měla být:
|
auths.tips.oauth2.general.tip=Při registraci nové OAuth2 autentizace by URL callbacku/přesměrování měla být:
|
||||||
auths.tip.oauth2_provider=Poskytovatel OAuth2
|
auths.tip.oauth2_provider=Poskytovatel OAuth2
|
||||||
|
auths.tip.bitbucket=Vytvořte nového OAuth konzumenta na https://bitbucket.org/account/user/{vase-uzivatelske-jmeno}/oauth-consumers/new a přidejte oprávnění „Account“ - „Read“
|
||||||
auths.tip.nextcloud=Zaregistrujte nového OAuth konzumenta na vaší instanci pomocí následujícího menu „Nastavení -> Zabezpečení -> OAuth 2.0 klient“
|
auths.tip.nextcloud=Zaregistrujte nového OAuth konzumenta na vaší instanci pomocí následujícího menu „Nastavení -> Zabezpečení -> OAuth 2.0 klient“
|
||||||
auths.tip.dropbox=Vytvořte novou aplikaci na https://www.dropbox.com/developers/apps
|
auths.tip.dropbox=Vytvořte novou aplikaci na https://www.dropbox.com/developers/apps
|
||||||
auths.tip.facebook=Registrujte novou aplikaci na https://developers.facebook.com/apps a přidejte produkt „Facebook Login“
|
auths.tip.facebook=Registrujte novou aplikaci na https://developers.facebook.com/apps a přidejte produkt „Facebook Login“
|
||||||
auths.tip.github=Registrujte novou OAuth aplikaci na https://github.com/settings/applications/new
|
auths.tip.github=Registrujte novou OAuth aplikaci na https://github.com/settings/applications/new
|
||||||
auths.tip.gitlab_new=Zaregistrujte novou aplikaci na https://gitlab.com/-/profile/applications
|
auths.tip.gitlab_new=Zaregistrujte novou aplikaci na https://gitlab.com/-/profile/applications
|
||||||
auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzole na https://console.developers.google.com/
|
auths.tip.google_plus=Získejte klientské pověření OAuth2 z Google API konzole na https://console.developers.google.com/
|
||||||
|
auths.tip.openid_connect=Použijte OpenID Connect URL pro objevování spojení „https://{server}/.well-known/openid-configuration“ k nastavení koncových bodů
|
||||||
auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
|
auths.tip.twitter=Jděte na https://dev.twitter.com/apps, vytvořte aplikaci a ujistěte se, že volba „Allow this application to be used to Sign in with Twitter“ je povolená
|
||||||
auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me
|
auths.tip.discord=Registrujte novou aplikaci na https://discordapp.com/developers/applications/me
|
||||||
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.com/development/oauth2-provider
|
auths.tip.gitea=Registrovat novou Oauth2 aplikaci. Návod naleznete na https://docs.gitea.com/development/oauth2-provider
|
||||||
@ -3256,6 +3287,7 @@ monitor.queue.name=Název
|
|||||||
monitor.queue.type=Typ
|
monitor.queue.type=Typ
|
||||||
monitor.queue.exemplar=Typ vzoru
|
monitor.queue.exemplar=Typ vzoru
|
||||||
monitor.queue.numberworkers=Počet workerů
|
monitor.queue.numberworkers=Počet workerů
|
||||||
|
monitor.queue.activeworkers=Aktivní workery
|
||||||
monitor.queue.maxnumberworkers=Maximální počet workerů
|
monitor.queue.maxnumberworkers=Maximální počet workerů
|
||||||
monitor.queue.numberinqueue=Číslo ve frontě
|
monitor.queue.numberinqueue=Číslo ve frontě
|
||||||
monitor.queue.review_add=Posoudit / přidat workery
|
monitor.queue.review_add=Posoudit / přidat workery
|
||||||
@ -3285,11 +3317,13 @@ notices.op=Akce
|
|||||||
notices.delete_success=Systémové upozornění bylo smazáno.
|
notices.delete_success=Systémové upozornění bylo smazáno.
|
||||||
|
|
||||||
self_check.no_problem_found=Zatím nebyl nalezen žádný problém.
|
self_check.no_problem_found=Zatím nebyl nalezen žádný problém.
|
||||||
|
self_check.startup_warnings=Upozornění při spuštění:
|
||||||
self_check.database_collation_mismatch=Očekávejte, že databáze použije collation: %s
|
self_check.database_collation_mismatch=Očekávejte, že databáze použije collation: %s
|
||||||
self_check.database_collation_case_insensitive=Databáze používá collation %s, což je collation nerozlišující velká a malá písmena. Ačkoli s ní Gitea může pracovat, mohou se vyskytnout vzácné případy, kdy nebude fungovat podle očekávání.
|
self_check.database_collation_case_insensitive=Databáze používá collation %s, což je collation nerozlišující velká a malá písmena. Ačkoli s ní Gitea může pracovat, mohou se vyskytnout vzácné případy, kdy nebude fungovat podle očekávání.
|
||||||
self_check.database_inconsistent_collation_columns=Databáze používá collation %s, ale tyto sloupce používají chybné collation. To může způsobit neočekávané problémy.
|
self_check.database_inconsistent_collation_columns=Databáze používá collation %s, ale tyto sloupce používají chybné collation. To může způsobit neočekávané problémy.
|
||||||
self_check.database_fix_mysql=Pro uživatele MySQL/MariaDB můžete použít příkaz "gitea doctor convert", který opraví problémy s collation, nebo můžete také problém vyřešit příkazem "ALTER ... COLLATE ..." SQL ručně.
|
self_check.database_fix_mysql=Pro uživatele MySQL/MariaDB můžete použít příkaz "gitea doctor convert", který opraví problémy s collation, nebo můžete také problém vyřešit příkazem "ALTER ... COLLATE ..." SQL ručně.
|
||||||
self_check.database_fix_mssql=Uživatelé MSSQL mohou problém vyřešit pouze pomocí příkazu "ALTER ... COLLATE ..." SQL ručně.
|
self_check.database_fix_mssql=Uživatelé MSSQL mohou problém vyřešit pouze pomocí příkazu "ALTER ... COLLATE ..." SQL ručně.
|
||||||
|
self_check.location_origin_mismatch=Aktuální URL (%[1]s) se neshoduje s URL viditelnou pro Gitea (%[2]s). Pokud používáte reverzní proxy, ujistěte se, že hlavičky „Host“ a „X-Forwarded-Proto“ jsou nastaveny správně.
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
|
create_repo=vytvořil/a repozitář <a href="%s">%s</a>
|
||||||
@ -3301,7 +3335,7 @@ reopen_issue=`znovuotevřel/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
|
|||||||
create_pull_request=`vytvořil/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
create_pull_request=`vytvořil/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
close_pull_request=`uzavřel/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
close_pull_request=`uzavřel/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reopen_pull_request=`znovuotevřel/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
reopen_pull_request=`znovuotevřel/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
comment_issue=`okomentoval/a problém <a href="%[1]s">%[3]s#%[2]s</a>`
|
comment_issue=`okomentoval/a úkol <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
comment_pull=`okomentoval/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
comment_pull=`okomentoval/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
merge_pull_request=`sloučil/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
merge_pull_request=`sloučil/a pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
auto_merge_pull_request=`automaticky sloučen pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
auto_merge_pull_request=`automaticky sloučen pull request <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
@ -3317,6 +3351,7 @@ mirror_sync_create=synchronizoval/a novou referenci <a href="%[2]s">%[3]s</a> do
|
|||||||
mirror_sync_delete=synchronizoval/a a smazal/a referenci <code>%[2]s</code> v <a href="%[1]s">%[3]s</a> ze zrcadla
|
mirror_sync_delete=synchronizoval/a a smazal/a referenci <code>%[2]s</code> v <a href="%[1]s">%[3]s</a> ze zrcadla
|
||||||
approve_pull_request=`schválil/a <a href="%[1]s">%[3]s#%[2]s</a>`
|
approve_pull_request=`schválil/a <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reject_pull_request=`navrhl/a změny pro <a href="%[1]s">%[3]s#%[2]s</a>`
|
reject_pull_request=`navrhl/a změny pro <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
|
publish_release=`vydal/a <a href="%[2]s"> "%[4]s" </a> v <a href="%[1]s">%[3]s</a>`
|
||||||
review_dismissed=`zamítl/a posouzení z <b>%[4]s</b> pro <a href="%[1]s">%[3]s#%[2]s</a>`
|
review_dismissed=`zamítl/a posouzení z <b>%[4]s</b> pro <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
review_dismissed_reason=Důvod:
|
review_dismissed_reason=Důvod:
|
||||||
create_branch=vytvořil/a větev <a href="%[2]s">%[3]s</a> v <a href="%[1]s">%[4]s</a>
|
create_branch=vytvořil/a větev <a href="%[2]s">%[3]s</a> v <a href="%[1]s">%[4]s</a>
|
||||||
@ -3383,6 +3418,7 @@ error.unit_not_allowed=Nejste oprávněni přistupovat k této části repozitá
|
|||||||
title=Balíčky
|
title=Balíčky
|
||||||
desc=Správa balíčků repozitáře.
|
desc=Správa balíčků repozitáře.
|
||||||
empty=Zatím nejsou žádné balíčky.
|
empty=Zatím nejsou žádné balíčky.
|
||||||
|
no_metadata=Žádná metadata.
|
||||||
empty.documentation=Další informace o registru balíčků naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
|
empty.documentation=Další informace o registru balíčků naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
|
||||||
empty.repo=Nahráli jste balíček, ale nezobrazil se zde? Přejděte na <a href="%[1]s">nastavení balíčku</a> a propojte jej s tímto repozitářem.
|
empty.repo=Nahráli jste balíček, ale nezobrazil se zde? Přejděte na <a href="%[1]s">nastavení balíčku</a> a propojte jej s tímto repozitářem.
|
||||||
registry.documentation=Další informace o registru %s naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
|
registry.documentation=Další informace o registru %s naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
|
||||||
@ -3464,6 +3500,7 @@ npm.install=Pro instalaci balíčku pomocí npm spusťte následující příkaz
|
|||||||
npm.install2=nebo ho přidejte do souboru package.json:
|
npm.install2=nebo ho přidejte do souboru package.json:
|
||||||
npm.dependencies=Závislosti
|
npm.dependencies=Závislosti
|
||||||
npm.dependencies.development=Vývojové závislosti
|
npm.dependencies.development=Vývojové závislosti
|
||||||
|
npm.dependencies.bundle=Vnitřní závislosti
|
||||||
npm.dependencies.peer=Vzájemné závislosti
|
npm.dependencies.peer=Vzájemné závislosti
|
||||||
npm.dependencies.optional=Volitelné závislosti
|
npm.dependencies.optional=Volitelné závislosti
|
||||||
npm.details.tag=Značka
|
npm.details.tag=Značka
|
||||||
@ -3560,6 +3597,8 @@ status.cancelled=Zrušeno
|
|||||||
status.skipped=Přeskočeno
|
status.skipped=Přeskočeno
|
||||||
status.blocked=Blokováno
|
status.blocked=Blokováno
|
||||||
|
|
||||||
|
runners=Runnery
|
||||||
|
runners.runner_manage_panel=Správa runnerů
|
||||||
runners.new=Vytvořit nový runner
|
runners.new=Vytvořit nový runner
|
||||||
runners.new_notice=Jak spustit runner
|
runners.new_notice=Jak spustit runner
|
||||||
runners.status=Status
|
runners.status=Status
|
||||||
@ -3586,6 +3625,7 @@ runners.delete_runner_success=Runner byl úspěšně odstraněn
|
|||||||
runners.delete_runner_failed=Odstranění runneru selhalo
|
runners.delete_runner_failed=Odstranění runneru selhalo
|
||||||
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
|
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
|
||||||
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
|
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
|
||||||
|
runners.none=Žádné runnery nejsou k dispozici
|
||||||
runners.status.unspecified=Neznámý
|
runners.status.unspecified=Neznámý
|
||||||
runners.status.idle=Nečinný
|
runners.status.idle=Nečinný
|
||||||
runners.status.active=Aktivní
|
runners.status.active=Aktivní
|
||||||
@ -3601,6 +3641,7 @@ runs.pushed_by=náhrán
|
|||||||
runs.invalid_workflow_helper=Konfigurační soubor pracovního postupu je neplatný. Zkontrolujte prosím konfigurační soubor: %s
|
runs.invalid_workflow_helper=Konfigurační soubor pracovního postupu je neplatný. Zkontrolujte prosím konfigurační soubor: %s
|
||||||
runs.no_matching_online_runner_helper=Žádný odpovídající online runner s popiskem: %s
|
runs.no_matching_online_runner_helper=Žádný odpovídající online runner s popiskem: %s
|
||||||
runs.no_job_without_needs=Pracovní postup musí obsahovat alespoň jednu úlohu bez závislostí.
|
runs.no_job_without_needs=Pracovní postup musí obsahovat alespoň jednu úlohu bez závislostí.
|
||||||
|
runs.no_job=Pracovní postup musí obsahovat alespoň jednu úlohu
|
||||||
runs.actor=Aktér
|
runs.actor=Aktér
|
||||||
runs.status=Status
|
runs.status=Status
|
||||||
runs.actors_no_select=Všichni aktéři
|
runs.actors_no_select=Všichni aktéři
|
||||||
|
@ -93,6 +93,7 @@ remove_all = Remove All
|
|||||||
remove_label_str = Remove item "%s"
|
remove_label_str = Remove item "%s"
|
||||||
edit = Edit
|
edit = Edit
|
||||||
view = View
|
view = View
|
||||||
|
test = Test
|
||||||
|
|
||||||
enabled = Enabled
|
enabled = Enabled
|
||||||
disabled = Disabled
|
disabled = Disabled
|
||||||
@ -1238,6 +1239,7 @@ file_view_rendered = View Rendered
|
|||||||
file_view_raw = View Raw
|
file_view_raw = View Raw
|
||||||
file_permalink = Permalink
|
file_permalink = Permalink
|
||||||
file_too_large = The file is too large to be shown.
|
file_too_large = The file is too large to be shown.
|
||||||
|
file_is_empty = The file is empty.
|
||||||
code_preview_line_from_to = Lines %[1]d to %[2]d in %[3]s
|
code_preview_line_from_to = Lines %[1]d to %[2]d in %[3]s
|
||||||
code_preview_line_in = Line %[1]d in %[2]s
|
code_preview_line_in = Line %[1]d in %[2]s
|
||||||
invisible_runes_header = `This file contains invisible Unicode characters`
|
invisible_runes_header = `This file contains invisible Unicode characters`
|
||||||
@ -3224,6 +3226,10 @@ config.cache_adapter = Cache Adapter
|
|||||||
config.cache_interval = Cache Interval
|
config.cache_interval = Cache Interval
|
||||||
config.cache_conn = Cache Connection
|
config.cache_conn = Cache Connection
|
||||||
config.cache_item_ttl = Cache Item TTL
|
config.cache_item_ttl = Cache Item TTL
|
||||||
|
config.cache_test = Test Cache
|
||||||
|
config.cache_test_failed = Failed to probe the cache: %v.
|
||||||
|
config.cache_test_slow = Cache test successful, but response is slow: %s.
|
||||||
|
config.cache_test_succeeded = Cache test successful, got a response in %s.
|
||||||
|
|
||||||
config.session_config = Session Configuration
|
config.session_config = Session Configuration
|
||||||
config.session_provider = Session Provider
|
config.session_provider = Session Provider
|
||||||
|
@ -25,6 +25,7 @@ enable_javascript=Ce site Web nécessite JavaScript.
|
|||||||
toc=Sommaire
|
toc=Sommaire
|
||||||
licenses=Licences
|
licenses=Licences
|
||||||
return_to_gitea=Revenir à Gitea
|
return_to_gitea=Revenir à Gitea
|
||||||
|
more_items=Plus d'éléments
|
||||||
|
|
||||||
username=Nom d'utilisateur
|
username=Nom d'utilisateur
|
||||||
email=Courriel
|
email=Courriel
|
||||||
@ -113,6 +114,7 @@ loading=Chargement…
|
|||||||
error=Erreur
|
error=Erreur
|
||||||
error404=La page que vous essayez d'atteindre <strong>n'existe pas</strong> ou <strong>vous n'êtes pas autorisé</strong> à la voir.
|
error404=La page que vous essayez d'atteindre <strong>n'existe pas</strong> ou <strong>vous n'êtes pas autorisé</strong> à la voir.
|
||||||
go_back=Retour
|
go_back=Retour
|
||||||
|
invalid_data=Données invalides : %v
|
||||||
|
|
||||||
never=Jamais
|
never=Jamais
|
||||||
unknown=Inconnu
|
unknown=Inconnu
|
||||||
@ -143,17 +145,43 @@ name=Nom
|
|||||||
value=Valeur
|
value=Valeur
|
||||||
|
|
||||||
filter=Filtrer
|
filter=Filtrer
|
||||||
|
filter.clear=Effacer le filtre
|
||||||
filter.is_archived=Archivé
|
filter.is_archived=Archivé
|
||||||
|
filter.not_archived=Non archivé
|
||||||
|
filter.is_fork=Bifurqué
|
||||||
|
filter.not_fork=Non bifurqué
|
||||||
|
filter.is_mirror=Miroité
|
||||||
|
filter.not_mirror=Non miroité
|
||||||
filter.is_template=Modèle
|
filter.is_template=Modèle
|
||||||
|
filter.not_template=Pas un modèle
|
||||||
filter.public=Public
|
filter.public=Public
|
||||||
filter.private=Privé
|
filter.private=Privé
|
||||||
|
|
||||||
|
no_results_found=Aucun résultat trouvé.
|
||||||
|
|
||||||
[search]
|
[search]
|
||||||
|
search=Rechercher…
|
||||||
|
type_tooltip=Type de recherche
|
||||||
|
fuzzy=Approximative
|
||||||
|
fuzzy_tooltip=Inclure également les résultats proches de la recherche
|
||||||
exact=Exact
|
exact=Exact
|
||||||
exact_tooltip=Inclure uniquement les résultats qui correspondent exactement au terme de recherche
|
exact_tooltip=Inclure uniquement les résultats qui correspondent exactement au terme de recherche
|
||||||
|
repo_kind=Chercher des dépôts…
|
||||||
|
user_kind=Chercher des utilisateurs…
|
||||||
|
org_kind=Chercher des organisations…
|
||||||
|
team_kind=Chercher des équipes…
|
||||||
|
code_kind=Chercher du code…
|
||||||
|
code_search_unavailable=La recherche dans le code n’est pas disponible actuellement. Veuillez contacter l’administrateur de votre instance Gitea.
|
||||||
|
code_search_by_git_grep=Les résultats de recherche de code actuels sont fournis par « git grep ». L’administrateur peut activer l’indexeur de dépôt, qui pourrait fournir de meilleurs résultats.
|
||||||
|
package_kind=Chercher des paquets…
|
||||||
|
project_kind=Chercher des projets…
|
||||||
|
branch_kind=Chercher des branches…
|
||||||
|
commit_kind=Chercher des révisions…
|
||||||
|
runner_kind=Chercher des exécuteurs…
|
||||||
|
no_results=Aucun résultat correspondant trouvé.
|
||||||
issue_kind=Recherche de tickets…
|
issue_kind=Recherche de tickets…
|
||||||
pull_kind=Recherche de demandes d’ajouts…
|
pull_kind=Recherche de demandes d’ajouts…
|
||||||
|
keyword_search_unavailable=La recherche par mot clé n’est pas disponible actuellement. Veuillez contacter l’administrateur de votre instance Gitea.
|
||||||
|
|
||||||
[aria]
|
[aria]
|
||||||
navbar=Barre de navigation
|
navbar=Barre de navigation
|
||||||
@ -260,6 +288,7 @@ email_title=Paramètres de Messagerie
|
|||||||
smtp_addr=Hôte SMTP
|
smtp_addr=Hôte SMTP
|
||||||
smtp_port=Port SMTP
|
smtp_port=Port SMTP
|
||||||
smtp_from=Envoyer les courriels en tant que
|
smtp_from=Envoyer les courriels en tant que
|
||||||
|
smtp_from_invalid=L’adresse « Envoyer le courriel sous » est invalide
|
||||||
smtp_from_helper=Adresse courriel utilisée par Gitea. Utilisez directement votre adresse ou la forme « Nom <email@example.com> ».
|
smtp_from_helper=Adresse courriel utilisée par Gitea. Utilisez directement votre adresse ou la forme « Nom <email@example.com> ».
|
||||||
mailer_user=Utilisateur SMTP
|
mailer_user=Utilisateur SMTP
|
||||||
mailer_password=Mot de passe SMTP
|
mailer_password=Mot de passe SMTP
|
||||||
@ -319,6 +348,7 @@ env_config_keys=Configuration de l'environnement
|
|||||||
env_config_keys_prompt=Les variables d'environnement suivantes seront également ajoutées à votre fichier de configuration :
|
env_config_keys_prompt=Les variables d'environnement suivantes seront également ajoutées à votre fichier de configuration :
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
|
nav_menu=Menu de navigation
|
||||||
uname_holder=Nom d’utilisateur ou adresse courriel
|
uname_holder=Nom d’utilisateur ou adresse courriel
|
||||||
password_holder=Mot de passe
|
password_holder=Mot de passe
|
||||||
switch_dashboard_context=Basculer le contexte du tableau de bord
|
switch_dashboard_context=Basculer le contexte du tableau de bord
|
||||||
@ -333,14 +363,14 @@ filter_by_team_repositories=Dépôts filtrés par équipe
|
|||||||
feed_of=Flux de « %s »
|
feed_of=Flux de « %s »
|
||||||
|
|
||||||
show_archived=Archivé
|
show_archived=Archivé
|
||||||
show_both_archived_unarchived=Afficher à la fois archivé et non archivé
|
show_both_archived_unarchived=Afficher à la fois les dépôts archivés et non archivés
|
||||||
show_only_archived=Afficher uniquement les archivés
|
show_only_archived=Afficher uniquement les dépôts archivés
|
||||||
show_only_unarchived=Afficher uniquement les non archivés
|
show_only_unarchived=Afficher uniquement les dépôts non archivés
|
||||||
|
|
||||||
show_private=Privé
|
show_private=Privé
|
||||||
show_both_private_public=Afficher les publics et privés
|
show_both_private_public=Afficher les dépôts publics et privés
|
||||||
show_only_private=Afficher uniquement les privés
|
show_only_private=Afficher uniquement les dépôts privés
|
||||||
show_only_public=Afficher uniquement les publics
|
show_only_public=Afficher uniquement les dépôts publics
|
||||||
|
|
||||||
issues.in_your_repos=Dans vos dépôts
|
issues.in_your_repos=Dans vos dépôts
|
||||||
|
|
||||||
@ -367,6 +397,7 @@ forgot_password_title=Mot de passe oublié
|
|||||||
forgot_password=Mot de passe oublié ?
|
forgot_password=Mot de passe oublié ?
|
||||||
sign_up_now=Pas de compte ? Inscrivez-vous maintenant.
|
sign_up_now=Pas de compte ? Inscrivez-vous maintenant.
|
||||||
sign_up_successful=Le compte a été créé avec succès. Bienvenue !
|
sign_up_successful=Le compte a été créé avec succès. Bienvenue !
|
||||||
|
confirmation_mail_sent_prompt_ex=Un nouveau courriel de confirmation a été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans la prochaine %s pour terminer le processus d’inscription. Si votre adresse courriel est incorrecte, vous pouvez vous reconnecter et la modifier.
|
||||||
must_change_password=Réinitialisez votre mot de passe
|
must_change_password=Réinitialisez votre mot de passe
|
||||||
allow_password_change=Demande à l'utilisateur de changer son mot de passe (recommandé)
|
allow_password_change=Demande à l'utilisateur de changer son mot de passe (recommandé)
|
||||||
reset_password_mail_sent_prompt=Un mail de confirmation a été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans les prochaines %s pour terminer la procédure de récupération du compte.
|
reset_password_mail_sent_prompt=Un mail de confirmation a été envoyé à <b>%s</b>. Veuillez vérifier votre boîte de réception dans les prochaines %s pour terminer la procédure de récupération du compte.
|
||||||
@ -376,6 +407,7 @@ prohibit_login=Connexion interdite
|
|||||||
prohibit_login_desc=Votre compte n'autorise pas la connexion, veuillez contacter l'administrateur de votre site.
|
prohibit_login_desc=Votre compte n'autorise pas la connexion, veuillez contacter l'administrateur de votre site.
|
||||||
resent_limit_prompt=Désolé, vous avez récemment demandé un courriel d'activation. Veuillez réessayer dans 3 minutes.
|
resent_limit_prompt=Désolé, vous avez récemment demandé un courriel d'activation. Veuillez réessayer dans 3 minutes.
|
||||||
has_unconfirmed_mail=Bonjour %s, votre adresse courriel (<b>%s</b>) n’a pas été confirmée. Si vous n’avez reçu aucun mail de confirmation ou souhaitez renouveler l’envoi, cliquez sur le bouton ci-dessous.
|
has_unconfirmed_mail=Bonjour %s, votre adresse courriel (<b>%s</b>) n’a pas été confirmée. Si vous n’avez reçu aucun mail de confirmation ou souhaitez renouveler l’envoi, cliquez sur le bouton ci-dessous.
|
||||||
|
change_unconfirmed_mail_address=Si votre adresse courriel d’inscription est incorrecte, vous pouvez la modifier ici et renvoyer un nouvel courriel de confirmation.
|
||||||
resend_mail=Cliquez ici pour renvoyer un mail de confirmation
|
resend_mail=Cliquez ici pour renvoyer un mail de confirmation
|
||||||
email_not_associate=L’adresse courriel n’est associée à aucun compte.
|
email_not_associate=L’adresse courriel n’est associée à aucun compte.
|
||||||
send_reset_mail=Envoyer un courriel de récupération du compte
|
send_reset_mail=Envoyer un courriel de récupération du compte
|
||||||
@ -404,6 +436,7 @@ oauth_signin_submit=Lier un compte
|
|||||||
oauth.signin.error=Une erreur s'est produite lors du traitement de la demande d'autorisation. Si cette erreur persiste, veuillez contacter l'administrateur du site.
|
oauth.signin.error=Une erreur s'est produite lors du traitement de la demande d'autorisation. Si cette erreur persiste, veuillez contacter l'administrateur du site.
|
||||||
oauth.signin.error.access_denied=La demande d'autorisation a été refusée.
|
oauth.signin.error.access_denied=La demande d'autorisation a été refusée.
|
||||||
oauth.signin.error.temporarily_unavailable=L'autorisation a échoué car le serveur d'authentification est temporairement indisponible. Veuillez réessayer plus tard.
|
oauth.signin.error.temporarily_unavailable=L'autorisation a échoué car le serveur d'authentification est temporairement indisponible. Veuillez réessayer plus tard.
|
||||||
|
oauth_callback_unable_auto_reg=L’inscription automatique est activée, mais le fournisseur OAuth2 %[1]s a signalé des champs manquants : %[2]s, impossible de créer un compte automatiquement, veuillez créer ou lier un compte, ou bien contacter l’administrateur du site.
|
||||||
openid_connect_submit=Se connecter
|
openid_connect_submit=Se connecter
|
||||||
openid_connect_title=Se connecter à un compte existant
|
openid_connect_title=Se connecter à un compte existant
|
||||||
openid_connect_desc=L'URI OpenID choisie est inconnue. Associez-le à un nouveau compte ici.
|
openid_connect_desc=L'URI OpenID choisie est inconnue. Associez-le à un nouveau compte ici.
|
||||||
@ -556,6 +589,7 @@ team_name_been_taken=Le nom d'équipe est déjà pris.
|
|||||||
team_no_units_error=Autoriser l’accès à au moins une section du dépôt.
|
team_no_units_error=Autoriser l’accès à au moins une section du dépôt.
|
||||||
email_been_used=Cette adresse courriel est déjà utilisée.
|
email_been_used=Cette adresse courriel est déjà utilisée.
|
||||||
email_invalid=Cette adresse courriel est invalide.
|
email_invalid=Cette adresse courriel est invalide.
|
||||||
|
email_domain_is_not_allowed=Le domaine <b>%s</b> du courriel utilisateur entre en conflit avec EMAIL_DOMAIN_ALLOWLIST ou EMAIL_DOMAIN_BLOCKLIST. Veuillez vous assurer que votre opération est attendue.
|
||||||
openid_been_used=Adresse OpenID "%s" déjà utilisée.
|
openid_been_used=Adresse OpenID "%s" déjà utilisée.
|
||||||
username_password_incorrect=Identifiant ou mot de passe invalide.
|
username_password_incorrect=Identifiant ou mot de passe invalide.
|
||||||
password_complexity=Le mot de passe ne respecte pas les exigences de complexité:
|
password_complexity=Le mot de passe ne respecte pas les exigences de complexité:
|
||||||
@ -567,6 +601,8 @@ enterred_invalid_repo_name=Le nom de dépôt saisi est incorrect.
|
|||||||
enterred_invalid_org_name=Le nom de l'organisation que vous avez entré est incorrect.
|
enterred_invalid_org_name=Le nom de l'organisation que vous avez entré est incorrect.
|
||||||
enterred_invalid_owner_name=Le nom du nouveau propriétaire est invalide.
|
enterred_invalid_owner_name=Le nom du nouveau propriétaire est invalide.
|
||||||
enterred_invalid_password=Le mot de passe saisi est incorrect.
|
enterred_invalid_password=Le mot de passe saisi est incorrect.
|
||||||
|
unset_password=L’utilisateur n’a pas défini de mot de passe.
|
||||||
|
unsupported_login_type=Le type de connexion n’est pas pris en charge pour supprimer le compte.
|
||||||
user_not_exist=Cet utilisateur n'existe pas.
|
user_not_exist=Cet utilisateur n'existe pas.
|
||||||
team_not_exist=L'équipe n'existe pas.
|
team_not_exist=L'équipe n'existe pas.
|
||||||
last_org_owner=Vous ne pouvez pas retirer le dernier utilisateur de l’équipe « propriétaires ». Il doit y avoir au moins un propriétaire dans chaque organisation.
|
last_org_owner=Vous ne pouvez pas retirer le dernier utilisateur de l’équipe « propriétaires ». Il doit y avoir au moins un propriétaire dans chaque organisation.
|
||||||
@ -616,6 +652,29 @@ form.name_reserved=Le nom d’utilisateur "%s" est réservé.
|
|||||||
form.name_pattern_not_allowed=Le motif « %s » n’est pas autorisé dans un nom de d'utilisateur.
|
form.name_pattern_not_allowed=Le motif « %s » n’est pas autorisé dans un nom de d'utilisateur.
|
||||||
form.name_chars_not_allowed=Le nom d'utilisateur "%s" contient des caractères non valides.
|
form.name_chars_not_allowed=Le nom d'utilisateur "%s" contient des caractères non valides.
|
||||||
|
|
||||||
|
block.block=Bloquer
|
||||||
|
block.block.user=Bloquer l’utilisateur
|
||||||
|
block.block.org=Bloquer l’utilisateur pour l’organisation
|
||||||
|
block.block.failure=Impossible de bloquer l’utilisateur : %s
|
||||||
|
block.unblock=Débloquer
|
||||||
|
block.unblock.failure=Impossible de débloquer l’utilisateur : %s
|
||||||
|
block.blocked=Vous avez bloqué cet utilisateur.
|
||||||
|
block.title=Bloquer un utilisateur
|
||||||
|
block.info=Bloquer un utilisateur l’empêche d’interagir avec des dépôts, comme ouvrir ou commenter des demandes de fusion ou des tickets. Apprenez-en plus sur le blocage d’un utilisateur.
|
||||||
|
block.info_1=Bloquer un utilisateur empêche les actions suivantes sur votre compte et vos dépôts :
|
||||||
|
block.info_2=suivre votre compte
|
||||||
|
block.info_3=vous envoyer des notifications en vous @mentionnant
|
||||||
|
block.info_4=vous inviter en tant que collaborateur de son(ses) dépôt(s)
|
||||||
|
block.info_5=aimer, bifurquer ou suivre vos dépôts
|
||||||
|
block.info_6=ouvrir ou commenter vos tickets et demandes d’ajouts
|
||||||
|
block.info_7=réagir à vos commentaires dans les tickets ou les demandes d’ajout
|
||||||
|
block.user_to_block=Utilisateur à bloquer
|
||||||
|
block.note=Note
|
||||||
|
block.note.title=Note facultative :
|
||||||
|
block.note.info=La note n’est pas visible par l’utilisateur bloqué.
|
||||||
|
block.note.edit=Modifier la note
|
||||||
|
block.list=Utilisateurs bloqués
|
||||||
|
block.list.none=Vous n’avez bloqué aucun utilisateur.
|
||||||
|
|
||||||
[settings]
|
[settings]
|
||||||
profile=Profil
|
profile=Profil
|
||||||
@ -658,6 +717,7 @@ cancel=Annuler
|
|||||||
language=Langue
|
language=Langue
|
||||||
ui=Thème
|
ui=Thème
|
||||||
hidden_comment_types=Catégories de commentaires masqués
|
hidden_comment_types=Catégories de commentaires masqués
|
||||||
|
hidden_comment_types_description=Cochez les catégories suivantes pour masquer les commentaires correspondants des fils d'activité. Par exemple, « Label » cache les commentaires du genre « Cerise a attribué le label Bug il y a 2 heures. »
|
||||||
hidden_comment_types.ref_tooltip=Commentaires où ce ticket a été référencé sur un autre ticket, révision, etc.
|
hidden_comment_types.ref_tooltip=Commentaires où ce ticket a été référencé sur un autre ticket, révision, etc.
|
||||||
hidden_comment_types.issue_ref_tooltip=Commentaires où l’utilisateur change la branche/étiquette associée au ticket
|
hidden_comment_types.issue_ref_tooltip=Commentaires où l’utilisateur change la branche/étiquette associée au ticket
|
||||||
comment_type_group_reference=Référence
|
comment_type_group_reference=Référence
|
||||||
@ -704,6 +764,8 @@ manage_themes=Sélectionner le thème par défaut
|
|||||||
manage_openid=Gérer les adresses OpenID
|
manage_openid=Gérer les adresses OpenID
|
||||||
email_desc=Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et, à condition qu'elle ne soit pas cachée, les opérations Git basées sur le Web.
|
email_desc=Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et, à condition qu'elle ne soit pas cachée, les opérations Git basées sur le Web.
|
||||||
theme_desc=Ce sera votre thème par défaut sur le site.
|
theme_desc=Ce sera votre thème par défaut sur le site.
|
||||||
|
theme_colorblindness_help=Support du thème daltonien
|
||||||
|
theme_colorblindness_prompt=Gitea fournit depuis peu des thèmes daltonien basé sur un spectre coloré réduit. Encore en développement, de futures améliorations devraient enrichir les fichiers de thèmes CSS.
|
||||||
primary=Principale
|
primary=Principale
|
||||||
activated=Activé
|
activated=Activé
|
||||||
requires_activation=Nécessite une activation
|
requires_activation=Nécessite une activation
|
||||||
@ -953,7 +1015,9 @@ fork_visibility_helper=La visibilité d'un dépôt bifurqué ne peut pas être m
|
|||||||
fork_branch=Branche à cloner sur la bifurcation
|
fork_branch=Branche à cloner sur la bifurcation
|
||||||
all_branches=Toutes les branches
|
all_branches=Toutes les branches
|
||||||
fork_no_valid_owners=Ce dépôt ne peut pas être bifurqué car il n’a pas de propriétaire valide.
|
fork_no_valid_owners=Ce dépôt ne peut pas être bifurqué car il n’a pas de propriétaire valide.
|
||||||
|
fork.blocked_user=Impossible de bifurquer le dépôt car vous êtes bloqué par son propriétaire.
|
||||||
use_template=Utiliser ce modèle
|
use_template=Utiliser ce modèle
|
||||||
|
open_with_editor=Ouvrir avec %s
|
||||||
download_zip=Télécharger le ZIP
|
download_zip=Télécharger le ZIP
|
||||||
download_tar=Télécharger le TAR.GZ
|
download_tar=Télécharger le TAR.GZ
|
||||||
download_bundle=Télécharger le BUNDLE
|
download_bundle=Télécharger le BUNDLE
|
||||||
@ -1006,6 +1070,7 @@ watchers=Observateurs
|
|||||||
stargazers=Fans
|
stargazers=Fans
|
||||||
stars_remove_warning=Ceci supprimera toutes les étoiles de ce dépôt.
|
stars_remove_warning=Ceci supprimera toutes les étoiles de ce dépôt.
|
||||||
forks=Bifurcations
|
forks=Bifurcations
|
||||||
|
stars=Favoris
|
||||||
reactions_more=et %d de plus
|
reactions_more=et %d de plus
|
||||||
unit_disabled=L'administrateur du site a désactivé cette section du dépôt.
|
unit_disabled=L'administrateur du site a désactivé cette section du dépôt.
|
||||||
language_other=Autre
|
language_other=Autre
|
||||||
@ -1127,6 +1192,7 @@ watch=Suivre
|
|||||||
unstar=Retirer des favoris
|
unstar=Retirer des favoris
|
||||||
star=Ajouter aux favoris
|
star=Ajouter aux favoris
|
||||||
fork=Bifurcation
|
fork=Bifurcation
|
||||||
|
action.blocked_user=Impossible d’effectuer cette action car vous êtes bloqué par le propriétaire du dépôt.
|
||||||
download_archive=Télécharger ce dépôt
|
download_archive=Télécharger ce dépôt
|
||||||
more_operations=Plus d'opérations
|
more_operations=Plus d'opérations
|
||||||
|
|
||||||
@ -1172,6 +1238,8 @@ file_view_rendered=Voir le rendu
|
|||||||
file_view_raw=Voir le Raw
|
file_view_raw=Voir le Raw
|
||||||
file_permalink=Lien permanent
|
file_permalink=Lien permanent
|
||||||
file_too_large=Le fichier est trop gros pour être affiché.
|
file_too_large=Le fichier est trop gros pour être affiché.
|
||||||
|
code_preview_line_from_to=Lignes %[1]d à %[2]d dans %[3]s
|
||||||
|
code_preview_line_in=Ligne %[1]d dans %[2]s
|
||||||
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.`
|
invisible_runes_header=`Ce fichier contient des caractères Unicode invisibles.`
|
||||||
invisible_runes_description=`Ce fichier contient des caractères Unicode invisibles à l'œil nu, mais peuvent être traités différemment par un ordinateur. Si vous pensez que c'est intentionnel, vous pouvez ignorer cet avertissement. Utilisez le bouton Échappe pour les dévoiler.`
|
invisible_runes_description=`Ce fichier contient des caractères Unicode invisibles à l'œil nu, mais peuvent être traités différemment par un ordinateur. Si vous pensez que c'est intentionnel, vous pouvez ignorer cet avertissement. Utilisez le bouton Échappe pour les dévoiler.`
|
||||||
ambiguous_runes_header=`Ce fichier contient des caractères Unicode ambigus.`
|
ambiguous_runes_header=`Ce fichier contient des caractères Unicode ambigus.`
|
||||||
@ -1226,6 +1294,7 @@ editor.or=ou
|
|||||||
editor.cancel_lower=Annuler
|
editor.cancel_lower=Annuler
|
||||||
editor.commit_signed_changes=Réviser les changements (signé)
|
editor.commit_signed_changes=Réviser les changements (signé)
|
||||||
editor.commit_changes=Réviser les changements
|
editor.commit_changes=Réviser les changements
|
||||||
|
editor.add_tmpl=Ajouter {filename}
|
||||||
editor.add=Ajouter %s
|
editor.add=Ajouter %s
|
||||||
editor.update=Actualiser %s
|
editor.update=Actualiser %s
|
||||||
editor.delete=Supprimer %s
|
editor.delete=Supprimer %s
|
||||||
@ -1253,6 +1322,8 @@ editor.file_editing_no_longer_exists=Impossible de modifier le fichier « %s
|
|||||||
editor.file_deleting_no_longer_exists=Impossible de supprimer le fichier « %s » car il n’existe plus dans ce dépôt.
|
editor.file_deleting_no_longer_exists=Impossible de supprimer le fichier « %s » car il n’existe plus dans ce dépôt.
|
||||||
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
|
editor.file_changed_while_editing=Le contenu du fichier a changé depuis que vous avez commencé à éditer. <a target="_blank" rel="noopener noreferrer" href="%s">Cliquez ici</a> pour voir les changements ou <strong>soumettez de nouveau</strong> pour les écraser.
|
||||||
editor.file_already_exists=Un fichier nommé "%s" existe déjà dans ce dépôt.
|
editor.file_already_exists=Un fichier nommé "%s" existe déjà dans ce dépôt.
|
||||||
|
editor.commit_id_not_matching=L’ID de la révision ne correspond pas à l’ID lorsque vous avez commencé à éditer. Faites une révision dans une branche de correctif puis fusionnez.
|
||||||
|
editor.push_out_of_date=Cet envoi semble être obsolète.
|
||||||
editor.commit_empty_file_header=Réviser un fichier vide
|
editor.commit_empty_file_header=Réviser un fichier vide
|
||||||
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ?
|
editor.commit_empty_file_text=Le fichier que vous allez réviser est vide. Continuer ?
|
||||||
editor.no_changes_to_show=Il n’y a aucune modification à afficher.
|
editor.no_changes_to_show=Il n’y a aucune modification à afficher.
|
||||||
@ -1277,6 +1348,7 @@ commits.commits=Révisions
|
|||||||
commits.no_commits=Pas de révisions en commun. "%s" et "%s" ont des historiques entièrement différents.
|
commits.no_commits=Pas de révisions en commun. "%s" et "%s" ont des historiques entièrement différents.
|
||||||
commits.nothing_to_compare=Ces branches sont égales.
|
commits.nothing_to_compare=Ces branches sont égales.
|
||||||
commits.search.tooltip=Vous pouvez utiliser les mots-clés "author:", "committer:", "after:", ou "before:" pour filtrer votre recherche, ex.: "revert author:Alice before:2019-01-13".
|
commits.search.tooltip=Vous pouvez utiliser les mots-clés "author:", "committer:", "after:", ou "before:" pour filtrer votre recherche, ex.: "revert author:Alice before:2019-01-13".
|
||||||
|
commits.search_branch=Cette branche
|
||||||
commits.search_all=Toutes les branches
|
commits.search_all=Toutes les branches
|
||||||
commits.author=Auteur
|
commits.author=Auteur
|
||||||
commits.message=Message
|
commits.message=Message
|
||||||
@ -1306,6 +1378,7 @@ commitstatus.success=Succès
|
|||||||
ext_issues=Accès aux tickets externes
|
ext_issues=Accès aux tickets externes
|
||||||
ext_issues.desc=Lien vers un gestionnaire de tickets externe.
|
ext_issues.desc=Lien vers un gestionnaire de tickets externe.
|
||||||
|
|
||||||
|
projects.desc=Gérer les tickets et les demandes d’ajouts dans les projets.
|
||||||
projects.description=Description (facultative)
|
projects.description=Description (facultative)
|
||||||
projects.description_placeholder=Description
|
projects.description_placeholder=Description
|
||||||
projects.create=Créer un projet
|
projects.create=Créer un projet
|
||||||
@ -1333,6 +1406,7 @@ projects.column.new=Nouvelle colonne
|
|||||||
projects.column.set_default=Définir par défaut
|
projects.column.set_default=Définir par défaut
|
||||||
projects.column.set_default_desc=Les tickets et demandes d’ajout non-catégorisés seront placés dans cette colonne.
|
projects.column.set_default_desc=Les tickets et demandes d’ajout non-catégorisés seront placés dans cette colonne.
|
||||||
projects.column.delete=Supprimer la colonne
|
projects.column.delete=Supprimer la colonne
|
||||||
|
projects.column.deletion_desc=La suppression d’une colonne déplace tous ses tickets dans la colonne par défaut. Continuer ?
|
||||||
projects.column.color=Couleur
|
projects.column.color=Couleur
|
||||||
projects.open=Ouvrir
|
projects.open=Ouvrir
|
||||||
projects.close=Fermer
|
projects.close=Fermer
|
||||||
@ -1367,6 +1441,9 @@ issues.new.assignees=Assignés
|
|||||||
issues.new.clear_assignees=Supprimer les affectations
|
issues.new.clear_assignees=Supprimer les affectations
|
||||||
issues.new.no_assignees=Sans assignation
|
issues.new.no_assignees=Sans assignation
|
||||||
issues.new.no_reviewers=Sans évaluateur
|
issues.new.no_reviewers=Sans évaluateur
|
||||||
|
issues.new.blocked_user=Impossible de créer un ticket car vous êtes bloqué par le propriétaire du dépôt.
|
||||||
|
issues.edit.already_changed=Impossible d’enregistrer le ticket. Il semble que le contenu ait été modifié par un autre utilisateur. Veuillez rafraîchir la page et réessayer pour éviter d’écraser ses modifications.
|
||||||
|
issues.edit.blocked_user=Impossible de modifier ce contenu car vous êtes bloqué par son propriétaire.
|
||||||
issues.choose.get_started=Démarrons
|
issues.choose.get_started=Démarrons
|
||||||
issues.choose.open_external_link=Ouvrir
|
issues.choose.open_external_link=Ouvrir
|
||||||
issues.choose.blank=Par défaut
|
issues.choose.blank=Par défaut
|
||||||
@ -1477,8 +1554,11 @@ issues.no_content=Sans contenu.
|
|||||||
issues.close=Fermer le ticket
|
issues.close=Fermer le ticket
|
||||||
issues.comment_pull_merged_at=a fusionné la révision %[1]s dans %[2]s %[3]s
|
issues.comment_pull_merged_at=a fusionné la révision %[1]s dans %[2]s %[3]s
|
||||||
issues.comment_manually_pull_merged_at=a fusionné manuellement la révision %[1]s dans %[2]s %[3]s
|
issues.comment_manually_pull_merged_at=a fusionné manuellement la révision %[1]s dans %[2]s %[3]s
|
||||||
|
issues.close_comment_issue=Commenter et Fermer
|
||||||
issues.reopen_issue=Rouvrir
|
issues.reopen_issue=Rouvrir
|
||||||
|
issues.reopen_comment_issue=Commenter et Réouvrir
|
||||||
issues.create_comment=Commenter
|
issues.create_comment=Commenter
|
||||||
|
issues.comment.blocked_user=Impossible créer ou de modifier un commentaire car vous êtes bloqué par le propriétaire du dépôt.
|
||||||
issues.closed_at=`a fermé ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
issues.closed_at=`a fermé ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
||||||
issues.reopened_at=`a réouvert ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
issues.reopened_at=`a réouvert ce ticket <a id="%[1]s" href="#%[1]s">%[2]s</a>.`
|
||||||
issues.commit_ref_at=`a référencé ce ticket depuis une révision <a id="%[1]s" href="#%[1]s"> %[2]s</a>.`
|
issues.commit_ref_at=`a référencé ce ticket depuis une révision <a id="%[1]s" href="#%[1]s"> %[2]s</a>.`
|
||||||
@ -1677,6 +1757,8 @@ compare.compare_head=comparer
|
|||||||
|
|
||||||
pulls.desc=Active les demandes d’ajouts et l’évaluation du code.
|
pulls.desc=Active les demandes d’ajouts et l’évaluation du code.
|
||||||
pulls.new=Nouvelle demande d'ajout
|
pulls.new=Nouvelle demande d'ajout
|
||||||
|
pulls.new.blocked_user=Impossible de créer une demande d’ajout car vous êtes bloqué par le propriétaire du dépôt.
|
||||||
|
pulls.edit.already_changed=Impossible d’enregistrer la demande d’ajout. Il semble que le contenu ait été modifié par un autre utilisateur. Veuillez rafraîchir la page et réessayer afin d’éviter d’écraser leurs modifications.
|
||||||
pulls.view=Voir la demande d'ajout
|
pulls.view=Voir la demande d'ajout
|
||||||
pulls.compare_changes=Nouvelle demande d’ajout
|
pulls.compare_changes=Nouvelle demande d’ajout
|
||||||
pulls.allow_edits_from_maintainers=Autoriser les modifications des mainteneurs
|
pulls.allow_edits_from_maintainers=Autoriser les modifications des mainteneurs
|
||||||
@ -1822,6 +1904,7 @@ pulls.recently_pushed_new_branches=Vous avez soumis sur la branche <strong>%[1]s
|
|||||||
|
|
||||||
pull.deleted_branch=(supprimé) : %s
|
pull.deleted_branch=(supprimé) : %s
|
||||||
|
|
||||||
|
comments.edit.already_changed=Impossible d’enregistrer ce commentaire. Il semble que le contenu ait été modifié par un autre utilisateur. Veuillez rafraîchir la page et réessayer afin d’éviter d’écraser leurs modifications.
|
||||||
|
|
||||||
milestones.new=Nouveau jalon
|
milestones.new=Nouveau jalon
|
||||||
milestones.closed=%s fermé
|
milestones.closed=%s fermé
|
||||||
@ -1898,7 +1981,10 @@ wiki.page_name_desc=Entrez un nom pour cette page Wiki. Certains noms spéciaux
|
|||||||
wiki.original_git_entry_tooltip=Voir le fichier Git original au lieu d'utiliser un lien convivial.
|
wiki.original_git_entry_tooltip=Voir le fichier Git original au lieu d'utiliser un lien convivial.
|
||||||
|
|
||||||
activity=Activité
|
activity=Activité
|
||||||
|
activity.navbar.pulse=Impulsion
|
||||||
|
activity.navbar.code_frequency=Fréquence du code
|
||||||
activity.navbar.contributors=Contributeurs
|
activity.navbar.contributors=Contributeurs
|
||||||
|
activity.navbar.recent_commits=Révisions récentes
|
||||||
activity.period.filter_label=Période :
|
activity.period.filter_label=Période :
|
||||||
activity.period.daily=1 jour
|
activity.period.daily=1 jour
|
||||||
activity.period.halfweekly=3 jours
|
activity.period.halfweekly=3 jours
|
||||||
@ -2017,7 +2103,9 @@ settings.branches.add_new_rule=Ajouter une nouvelle règle
|
|||||||
settings.advanced_settings=Paramètres avancés
|
settings.advanced_settings=Paramètres avancés
|
||||||
settings.wiki_desc=Activer le wiki du dépôt
|
settings.wiki_desc=Activer le wiki du dépôt
|
||||||
settings.use_internal_wiki=Utiliser le wiki interne
|
settings.use_internal_wiki=Utiliser le wiki interne
|
||||||
|
settings.default_wiki_branch_name=Nom de la branche du Wiki par défaut
|
||||||
settings.default_wiki_everyone_access=Autorisation d’accès par défaut pour les utilisateurs connectés :
|
settings.default_wiki_everyone_access=Autorisation d’accès par défaut pour les utilisateurs connectés :
|
||||||
|
settings.failed_to_change_default_wiki_branch=Impossible de modifier la branche du wiki par défaut.
|
||||||
settings.use_external_wiki=Utiliser un wiki externe
|
settings.use_external_wiki=Utiliser un wiki externe
|
||||||
settings.external_wiki_url=URL Wiki externe
|
settings.external_wiki_url=URL Wiki externe
|
||||||
settings.external_wiki_url_error=L’URL du wiki externe n’est pas une URL valide.
|
settings.external_wiki_url_error=L’URL du wiki externe n’est pas une URL valide.
|
||||||
@ -2048,6 +2136,9 @@ settings.pulls.default_allow_edits_from_maintainers=Autoriser les modifications
|
|||||||
settings.releases_desc=Activer les publications du dépôt
|
settings.releases_desc=Activer les publications du dépôt
|
||||||
settings.packages_desc=Activer le registre des paquets du dépôt
|
settings.packages_desc=Activer le registre des paquets du dépôt
|
||||||
settings.projects_desc=Activer les projets de dépôt
|
settings.projects_desc=Activer les projets de dépôt
|
||||||
|
settings.projects_mode_desc=Mode Projets (type de projets à afficher)
|
||||||
|
settings.projects_mode_repo=Projets de dépôt uniquement
|
||||||
|
settings.projects_mode_owner=Projets d’utilisateur ou d’organisation uniquement
|
||||||
settings.projects_mode_all=Tous les projets
|
settings.projects_mode_all=Tous les projets
|
||||||
settings.actions_desc=Activer les actions du dépôt
|
settings.actions_desc=Activer les actions du dépôt
|
||||||
settings.admin_settings=Paramètres administrateur
|
settings.admin_settings=Paramètres administrateur
|
||||||
@ -2074,6 +2165,7 @@ settings.convert_fork_succeed=La bifurcation a été convertie en dépôt standa
|
|||||||
settings.transfer=Changer de propriétaire
|
settings.transfer=Changer de propriétaire
|
||||||
settings.transfer.rejected=Le transfert du dépôt a été rejeté.
|
settings.transfer.rejected=Le transfert du dépôt a été rejeté.
|
||||||
settings.transfer.success=Le transfert du dépôt a réussi.
|
settings.transfer.success=Le transfert du dépôt a réussi.
|
||||||
|
settings.transfer.blocked_user=Impossible de transférer ce dépôt car vous êtes bloqué par l’acquéreur.
|
||||||
settings.transfer_abort=Annuler le transfert
|
settings.transfer_abort=Annuler le transfert
|
||||||
settings.transfer_abort_invalid=Vous ne pouvez pas annuler un transfert de dépôt inexistant.
|
settings.transfer_abort_invalid=Vous ne pouvez pas annuler un transfert de dépôt inexistant.
|
||||||
settings.transfer_abort_success=Le transfert du dépôt vers %s a bien été stoppé.
|
settings.transfer_abort_success=Le transfert du dépôt vers %s a bien été stoppé.
|
||||||
@ -2119,6 +2211,7 @@ settings.add_collaborator_success=Le collaborateur a été ajouté.
|
|||||||
settings.add_collaborator_inactive_user=Impossible d'ajouter un utilisateur inactif en tant que collaborateur.
|
settings.add_collaborator_inactive_user=Impossible d'ajouter un utilisateur inactif en tant que collaborateur.
|
||||||
settings.add_collaborator_owner=Impossible d'ajouter un propriétaire en tant que collaborateur.
|
settings.add_collaborator_owner=Impossible d'ajouter un propriétaire en tant que collaborateur.
|
||||||
settings.add_collaborator_duplicate=Le collaborateur est déjà ajouté à ce dépôt.
|
settings.add_collaborator_duplicate=Le collaborateur est déjà ajouté à ce dépôt.
|
||||||
|
settings.add_collaborator.blocked_user=Ce collaborateur est bloqué par le propriétaire du dépôt ou inversement.
|
||||||
settings.delete_collaborator=Supprimer
|
settings.delete_collaborator=Supprimer
|
||||||
settings.collaborator_deletion=Supprimer le collaborateur
|
settings.collaborator_deletion=Supprimer le collaborateur
|
||||||
settings.collaborator_deletion_desc=La suppression d'un collaborateur révoque son accès à ce dépôt. Continuer ?
|
settings.collaborator_deletion_desc=La suppression d'un collaborateur révoque son accès à ce dépôt. Continuer ?
|
||||||
@ -2557,13 +2650,16 @@ find_file.no_matching=Aucun fichier correspondant trouvé
|
|||||||
error.csv.too_large=Impossible de visualiser le fichier car il est trop volumineux.
|
error.csv.too_large=Impossible de visualiser le fichier car il est trop volumineux.
|
||||||
error.csv.unexpected=Impossible de visualiser ce fichier car il contient un caractère inattendu ligne %d, colonne %d.
|
error.csv.unexpected=Impossible de visualiser ce fichier car il contient un caractère inattendu ligne %d, colonne %d.
|
||||||
error.csv.invalid_field_count=Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d.
|
error.csv.invalid_field_count=Impossible de visualiser ce fichier car il contient un nombre de champs incorrect à la ligne %d.
|
||||||
|
error.broken_git_hook=Les crochets Git de ce dépôt semblent cassés. Veuillez suivre la <a target="_blank" rel="noreferrer" href="%s">documentation</a> pour les corriger, puis pousser des révisions pour actualiser le statut.
|
||||||
|
|
||||||
[graphs]
|
[graphs]
|
||||||
component_loading=Chargement de %s…
|
component_loading=Chargement de %s…
|
||||||
component_loading_failed=Impossible de charger %s.
|
component_loading_failed=Impossible de charger %s.
|
||||||
component_loading_info=Ça prend son temps…
|
component_loading_info=Ça prend son temps…
|
||||||
component_failed_to_load=Une erreur inattendue s’est produite.
|
component_failed_to_load=Une erreur inattendue s’est produite.
|
||||||
|
code_frequency.what=fréquence du code
|
||||||
contributors.what=contributions
|
contributors.what=contributions
|
||||||
|
recent_commits.what=révisions récentes
|
||||||
|
|
||||||
[org]
|
[org]
|
||||||
org_name_holder=Nom de l'organisation
|
org_name_holder=Nom de l'organisation
|
||||||
@ -2677,6 +2773,7 @@ teams.add_nonexistent_repo=Le dépôt que vous essayez d'ajouter n'existe pas, v
|
|||||||
teams.add_duplicate_users=L’utilisateur est déjà un membre de l’équipe.
|
teams.add_duplicate_users=L’utilisateur est déjà un membre de l’équipe.
|
||||||
teams.repos.none=Aucun dépôt n'est accessible par cette équipe.
|
teams.repos.none=Aucun dépôt n'est accessible par cette équipe.
|
||||||
teams.members.none=Aucun membre dans cette équipe.
|
teams.members.none=Aucun membre dans cette équipe.
|
||||||
|
teams.members.blocked_user=Impossible d’ajouter l’utilisateur car il est bloqué par l’organisation.
|
||||||
teams.specific_repositories=Dépôts spécifiques
|
teams.specific_repositories=Dépôts spécifiques
|
||||||
teams.specific_repositories_helper=Les membres auront seulement accès aux dépôts explicitement ajoutés à l'équipe. Sélectionner ceci <strong>ne supprimera pas automatiquement</strong> les dépôts déjà ajoutés avec <i>Tous les dépôts</i>.
|
teams.specific_repositories_helper=Les membres auront seulement accès aux dépôts explicitement ajoutés à l'équipe. Sélectionner ceci <strong>ne supprimera pas automatiquement</strong> les dépôts déjà ajoutés avec <i>Tous les dépôts</i>.
|
||||||
teams.all_repositories=Tous les dépôts
|
teams.all_repositories=Tous les dépôts
|
||||||
@ -2689,6 +2786,7 @@ teams.invite.by=Invité par %s
|
|||||||
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre l’équipe.
|
teams.invite.description=Veuillez cliquer sur le bouton ci-dessous pour rejoindre l’équipe.
|
||||||
|
|
||||||
[admin]
|
[admin]
|
||||||
|
maintenance=Maintenance
|
||||||
dashboard=Tableau de bord
|
dashboard=Tableau de bord
|
||||||
self_check=Autodiagnostique
|
self_check=Autodiagnostique
|
||||||
identity_access=Identité et accès
|
identity_access=Identité et accès
|
||||||
@ -2712,6 +2810,7 @@ settings=Paramètres administrateur
|
|||||||
|
|
||||||
dashboard.new_version_hint=Gitea %s est maintenant disponible, vous utilisez %s. Consultez <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">le blog</a> pour plus de détails.
|
dashboard.new_version_hint=Gitea %s est maintenant disponible, vous utilisez %s. Consultez <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">le blog</a> pour plus de détails.
|
||||||
dashboard.statistic=Résumé
|
dashboard.statistic=Résumé
|
||||||
|
dashboard.maintenance_operations=Opérations de maintenance
|
||||||
dashboard.system_status=État du système
|
dashboard.system_status=État du système
|
||||||
dashboard.operation_name=Nom de l'Opération
|
dashboard.operation_name=Nom de l'Opération
|
||||||
dashboard.operation_switch=Basculer
|
dashboard.operation_switch=Basculer
|
||||||
@ -2997,11 +3096,14 @@ auths.tips=Conseils
|
|||||||
auths.tips.oauth2.general=Authentification OAuth2
|
auths.tips.oauth2.general=Authentification OAuth2
|
||||||
auths.tips.oauth2.general.tip=Lors de l'enregistrement d'une nouvelle authentification OAuth2, l'URL de rappel/redirection doit être :
|
auths.tips.oauth2.general.tip=Lors de l'enregistrement d'une nouvelle authentification OAuth2, l'URL de rappel/redirection doit être :
|
||||||
auths.tip.oauth2_provider=Fournisseur OAuth2
|
auths.tip.oauth2_provider=Fournisseur OAuth2
|
||||||
|
auths.tip.bitbucket=Créez un nouveau jeton OAuth sur https://bitbucket.org/account/user/{your username}/oauth-consumers/new et ajoutez la permission “Compte” - “Lecture”.
|
||||||
auths.tip.nextcloud=`Enregistrez un nouveau consommateur OAuth sur votre instance en utilisant le menu "Paramètres -> Sécurité -> Client OAuth 2.0"`
|
auths.tip.nextcloud=`Enregistrez un nouveau consommateur OAuth sur votre instance en utilisant le menu "Paramètres -> Sécurité -> Client OAuth 2.0"`
|
||||||
auths.tip.dropbox=Créez une nouvelle application sur https://www.dropbox.com/developers/apps
|
auths.tip.dropbox=Créez une nouvelle application sur https://www.dropbox.com/developers/apps
|
||||||
auths.tip.facebook=`Enregistrez une nouvelle application sur https://developers.facebook.com/apps et ajoutez le produit "Facebook Login"`
|
auths.tip.facebook=`Enregistrez une nouvelle application sur https://developers.facebook.com/apps et ajoutez le produit "Facebook Login"`
|
||||||
auths.tip.github=Créez une nouvelle application OAuth sur https://github.com/settings/applications/new
|
auths.tip.github=Créez une nouvelle application OAuth sur https://github.com/settings/applications/new
|
||||||
|
auths.tip.gitlab_new=Enregistrez une nouvelle application sur https://gitlab.com/-/profile/applications
|
||||||
auths.tip.google_plus=Obtenez des identifiants OAuth2 sur la console API de Google (https://console.developers.google.com/)
|
auths.tip.google_plus=Obtenez des identifiants OAuth2 sur la console API de Google (https://console.developers.google.com/)
|
||||||
|
auths.tip.openid_connect=Utilisez l’URL de découverte OpenID « https://{server}/.well-known/openid-configuration » pour spécifier les points d'accès.
|
||||||
auths.tip.twitter=Rendez-vous sur https://dev.twitter.com/apps, créez une application et assurez-vous que l'option "Autoriser l'application à être utilisée avec Twitter Connect" est activée
|
auths.tip.twitter=Rendez-vous sur https://dev.twitter.com/apps, créez une application et assurez-vous que l'option "Autoriser l'application à être utilisée avec Twitter Connect" est activée
|
||||||
auths.tip.discord=Enregistrer une nouvelle application sur https://discordapp.com/developers/applications/me
|
auths.tip.discord=Enregistrer une nouvelle application sur https://discordapp.com/developers/applications/me
|
||||||
auths.tip.gitea=Enregistrez une nouvelle application OAuth2. Le guide peut être trouvé sur https://docs.gitea.com/development/oauth2-provider
|
auths.tip.gitea=Enregistrez une nouvelle application OAuth2. Le guide peut être trouvé sur https://docs.gitea.com/development/oauth2-provider
|
||||||
@ -3135,6 +3237,7 @@ config.picture_config=Configuration de l'avatar
|
|||||||
config.picture_service=Service d'Imagerie
|
config.picture_service=Service d'Imagerie
|
||||||
config.disable_gravatar=Désactiver Gravatar
|
config.disable_gravatar=Désactiver Gravatar
|
||||||
config.enable_federated_avatar=Activer les avatars unifiés
|
config.enable_federated_avatar=Activer les avatars unifiés
|
||||||
|
config.open_with_editor_app_help=Les éditeurs disponibles via « Ouvrir avec ». Si laissé vide, la valeur par défaut sera utilisée. Développez pour voir la valeur par défaut.
|
||||||
|
|
||||||
config.git_config=Configuration de Git
|
config.git_config=Configuration de Git
|
||||||
config.git_disable_diff_highlight=Désactiver la surbrillance syntaxique de Diff
|
config.git_disable_diff_highlight=Désactiver la surbrillance syntaxique de Diff
|
||||||
@ -3214,11 +3317,13 @@ notices.op=Opération
|
|||||||
notices.delete_success=Les informations systèmes ont été supprimées.
|
notices.delete_success=Les informations systèmes ont été supprimées.
|
||||||
|
|
||||||
self_check.no_problem_found=Aucun problème trouvé pour l’instant.
|
self_check.no_problem_found=Aucun problème trouvé pour l’instant.
|
||||||
|
self_check.startup_warnings=Avertissements au démarrage :
|
||||||
self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s.
|
self_check.database_collation_mismatch=Exige que la base de données utilise la collation %s.
|
||||||
self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Gitea soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
|
self_check.database_collation_case_insensitive=La base de données utilise la collation %s, insensible à la casse. Bien que Gitea soit compatible, il peut y avoir quelques rares cas qui ne fonctionnent pas comme prévu.
|
||||||
self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus.
|
self_check.database_inconsistent_collation_columns=La base de données utilise la collation %s, mais ces colonnes utilisent des collations différentes. Cela peut causer des problèmes imprévus.
|
||||||
self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « gitea doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
|
self_check.database_fix_mysql=Pour les utilisateurs de MySQL ou MariaDB, vous pouvez utiliser la commande « gitea doctor convert » dans un terminal ou exécuter une requête du type « ALTER … COLLATE ... » pour résoudre les problèmes de collation.
|
||||||
self_check.database_fix_mssql=Pour les utilisateurs de MSSQL, vous ne pouvez résoudre le problème qu’en exécutant une requête SQL du type « ALTER … COLLATE … ».
|
self_check.database_fix_mssql=Pour les utilisateurs de MSSQL, vous ne pouvez résoudre le problème qu’en exécutant une requête SQL du type « ALTER … COLLATE … ».
|
||||||
|
self_check.location_origin_mismatch=L’URL actuelle (%[1]s) ne correspond pas à l’URL vue par Gitea (%[2]). Si vous utilisez un proxy inverse, assurez-vous que les en-têtes « Host » et « X-Forwarded-Proto » sont correctement définis.
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
create_repo=a créé le dépôt <a href="%s">%s</a>
|
create_repo=a créé le dépôt <a href="%s">%s</a>
|
||||||
@ -3246,6 +3351,7 @@ mirror_sync_create=a synchronisé la nouvelle référence <a href="%[2]s">%[3]s<
|
|||||||
mirror_sync_delete=a synchronisé puis supprimé la nouvelle référence <code>%[2]s</code> vers <a href="%[1]s">%[3]s</a> depuis le miroir
|
mirror_sync_delete=a synchronisé puis supprimé la nouvelle référence <code>%[2]s</code> vers <a href="%[1]s">%[3]s</a> depuis le miroir
|
||||||
approve_pull_request=`a approuvé <a href="%[1]s">%[3]s#%[2]s</a>`
|
approve_pull_request=`a approuvé <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
reject_pull_request=`a suggérés des changements pour <a href="%[1]s">%[3]s#%[2]s</a>`
|
reject_pull_request=`a suggérés des changements pour <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
|
publish_release=`a publié <a href="%[2]s"> "%[4]s" </a> dans <a href="%[1]s">%[3]s</a>`
|
||||||
review_dismissed=`a révoqué l’évaluation de <b>%[4]s</b> dans <a href="%[1]s">%[3]s#%[2]s</a>`
|
review_dismissed=`a révoqué l’évaluation de <b>%[4]s</b> dans <a href="%[1]s">%[3]s#%[2]s</a>`
|
||||||
review_dismissed_reason=Raison :
|
review_dismissed_reason=Raison :
|
||||||
create_branch=a créé la branche <a href="%[2]s">%[3]s</a> dans <a href="%[1]s">%[4]s</a>
|
create_branch=a créé la branche <a href="%[2]s">%[3]s</a> dans <a href="%[1]s">%[4]s</a>
|
||||||
@ -3312,6 +3418,7 @@ error.unit_not_allowed=Vous n'êtes pas autorisé à accéder à cette section d
|
|||||||
title=Paquets
|
title=Paquets
|
||||||
desc=Gérer les paquets du dépôt.
|
desc=Gérer les paquets du dépôt.
|
||||||
empty=Il n'y pas de paquet pour le moment.
|
empty=Il n'y pas de paquet pour le moment.
|
||||||
|
no_metadata=Pas de métadonnées.
|
||||||
empty.documentation=Pour plus d'informations sur le registre de paquets, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
|
empty.documentation=Pour plus d'informations sur le registre de paquets, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
|
||||||
empty.repo=Avez-vous téléchargé un paquet, mais il n'est pas affiché ici? Allez dans les <a href="%[1]s">paramètres du paquet</a> et liez le à ce dépôt.
|
empty.repo=Avez-vous téléchargé un paquet, mais il n'est pas affiché ici? Allez dans les <a href="%[1]s">paramètres du paquet</a> et liez le à ce dépôt.
|
||||||
registry.documentation=Pour plus d’informations sur le registre %s, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
|
registry.documentation=Pour plus d’informations sur le registre %s, voir <a target="_blank" rel="noopener noreferrer" href="%s">la documentation</a>.
|
||||||
@ -3393,6 +3500,7 @@ npm.install=Pour installer le paquet en utilisant npm, exécutez la commande sui
|
|||||||
npm.install2=ou ajoutez-le au fichier package.json :
|
npm.install2=ou ajoutez-le au fichier package.json :
|
||||||
npm.dependencies=Dépendances
|
npm.dependencies=Dépendances
|
||||||
npm.dependencies.development=Dépendances de développement
|
npm.dependencies.development=Dépendances de développement
|
||||||
|
npm.dependencies.bundle=Dépendances emballées
|
||||||
npm.dependencies.peer=Dépendances de pairs
|
npm.dependencies.peer=Dépendances de pairs
|
||||||
npm.dependencies.optional=Dépendances optionnelles
|
npm.dependencies.optional=Dépendances optionnelles
|
||||||
npm.details.tag=Balise
|
npm.details.tag=Balise
|
||||||
@ -3532,6 +3640,8 @@ runs.scheduled=Planifié
|
|||||||
runs.pushed_by=soumis par
|
runs.pushed_by=soumis par
|
||||||
runs.invalid_workflow_helper=La configuration du flux de travail est invalide. Veuillez vérifier votre fichier %s.
|
runs.invalid_workflow_helper=La configuration du flux de travail est invalide. Veuillez vérifier votre fichier %s.
|
||||||
runs.no_matching_online_runner_helper=Aucun exécuteur en ligne correspondant au libellé %s
|
runs.no_matching_online_runner_helper=Aucun exécuteur en ligne correspondant au libellé %s
|
||||||
|
runs.no_job_without_needs=Le flux de travail doit contenir au moins une tâche sans dépendance.
|
||||||
|
runs.no_job=Le flux de travail doit contenir au moins une tâche
|
||||||
runs.actor=Acteur
|
runs.actor=Acteur
|
||||||
runs.status=Statut
|
runs.status=Statut
|
||||||
runs.actors_no_select=Tous les acteurs
|
runs.actors_no_select=Tous les acteurs
|
||||||
|
@ -1238,6 +1238,7 @@ file_view_rendered=レンダリング表示
|
|||||||
file_view_raw=Rawデータを見る
|
file_view_raw=Rawデータを見る
|
||||||
file_permalink=パーマリンク
|
file_permalink=パーマリンク
|
||||||
file_too_large=このファイルは大きすぎるため、表示できません。
|
file_too_large=このファイルは大きすぎるため、表示できません。
|
||||||
|
file_is_empty=ファイルは空です。
|
||||||
code_preview_line_from_to=%[1]d 行目から %[2]d 行目 in %[3]s
|
code_preview_line_from_to=%[1]d 行目から %[2]d 行目 in %[3]s
|
||||||
code_preview_line_in=%[1]d 行目 in %[2]s
|
code_preview_line_in=%[1]d 行目 in %[2]s
|
||||||
invisible_runes_header=このファイルには不可視のUnicode文字が含まれています
|
invisible_runes_header=このファイルには不可視のUnicode文字が含まれています
|
||||||
@ -1378,6 +1379,7 @@ commitstatus.success=成功
|
|||||||
ext_issues=外部イシューへのアクセス
|
ext_issues=外部イシューへのアクセス
|
||||||
ext_issues.desc=外部のイシュートラッカーへのリンク。
|
ext_issues.desc=外部のイシュートラッカーへのリンク。
|
||||||
|
|
||||||
|
projects.desc=プロジェクトでイシューとプルリクエストを管理します。
|
||||||
projects.description=説明 (オプション)
|
projects.description=説明 (オプション)
|
||||||
projects.description_placeholder=説明
|
projects.description_placeholder=説明
|
||||||
projects.create=プロジェクトを作成
|
projects.create=プロジェクトを作成
|
||||||
@ -1441,6 +1443,7 @@ issues.new.clear_assignees=担当者をクリア
|
|||||||
issues.new.no_assignees=担当者なし
|
issues.new.no_assignees=担当者なし
|
||||||
issues.new.no_reviewers=レビューアなし
|
issues.new.no_reviewers=レビューアなし
|
||||||
issues.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、イシューを作成できません。
|
issues.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、イシューを作成できません。
|
||||||
|
issues.edit.already_changed=イシューの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
issues.edit.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、内容を編集できません。
|
issues.edit.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、内容を編集できません。
|
||||||
issues.choose.get_started=始める
|
issues.choose.get_started=始める
|
||||||
issues.choose.open_external_link=オープン
|
issues.choose.open_external_link=オープン
|
||||||
@ -1552,7 +1555,9 @@ issues.no_content=説明はありません。
|
|||||||
issues.close=イシューをクローズ
|
issues.close=イシューをクローズ
|
||||||
issues.comment_pull_merged_at=がコミット %[1]s を %[2]s にマージ %[3]s
|
issues.comment_pull_merged_at=がコミット %[1]s を %[2]s にマージ %[3]s
|
||||||
issues.comment_manually_pull_merged_at=がコミット %[1]s を %[2]s に手動マージ %[3]s
|
issues.comment_manually_pull_merged_at=がコミット %[1]s を %[2]s に手動マージ %[3]s
|
||||||
|
issues.close_comment_issue=コメントしてクローズ
|
||||||
issues.reopen_issue=再オープンする
|
issues.reopen_issue=再オープンする
|
||||||
|
issues.reopen_comment_issue=コメントして再オープン
|
||||||
issues.create_comment=コメントする
|
issues.create_comment=コメントする
|
||||||
issues.comment.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、コメントの作成や編集はできません。
|
issues.comment.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、コメントの作成や編集はできません。
|
||||||
issues.closed_at=`がイシューをクローズ <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at=`がイシューをクローズ <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
@ -1754,6 +1759,7 @@ compare.compare_head=比較
|
|||||||
pulls.desc=プルリクエストとコードレビューの有効化。
|
pulls.desc=プルリクエストとコードレビューの有効化。
|
||||||
pulls.new=新しいプルリクエスト
|
pulls.new=新しいプルリクエスト
|
||||||
pulls.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。
|
pulls.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。
|
||||||
|
pulls.edit.already_changed=プルリクエストの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
pulls.view=プルリクエストを表示
|
pulls.view=プルリクエストを表示
|
||||||
pulls.compare_changes=新規プルリクエスト
|
pulls.compare_changes=新規プルリクエスト
|
||||||
pulls.allow_edits_from_maintainers=メンテナーからの編集を許可する
|
pulls.allow_edits_from_maintainers=メンテナーからの編集を許可する
|
||||||
@ -1899,6 +1905,7 @@ pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ <strong>%[1
|
|||||||
|
|
||||||
pull.deleted_branch=(削除済み):%s
|
pull.deleted_branch=(削除済み):%s
|
||||||
|
|
||||||
|
comments.edit.already_changed=コメントの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
|
|
||||||
milestones.new=新しいマイルストーン
|
milestones.new=新しいマイルストーン
|
||||||
milestones.closed=%s にクローズ
|
milestones.closed=%s にクローズ
|
||||||
@ -3412,6 +3419,7 @@ error.unit_not_allowed=このセクションへのアクセスが許可されて
|
|||||||
title=パッケージ
|
title=パッケージ
|
||||||
desc=リポジトリ パッケージを管理します。
|
desc=リポジトリ パッケージを管理します。
|
||||||
empty=パッケージはまだありません。
|
empty=パッケージはまだありません。
|
||||||
|
no_metadata=メタデータがありません。
|
||||||
empty.documentation=パッケージレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
|
empty.documentation=パッケージレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
|
||||||
empty.repo=パッケージはアップロード済みで、ここに表示されていないですか? <a href="%[1]s">パッケージ設定</a>を開いて、パッケージをこのリポジトリにリンクしてください。
|
empty.repo=パッケージはアップロード済みで、ここに表示されていないですか? <a href="%[1]s">パッケージ設定</a>を開いて、パッケージをこのリポジトリにリンクしてください。
|
||||||
registry.documentation=%sレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
|
registry.documentation=%sレジストリの詳細については、 <a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a> を参照してください。
|
||||||
@ -3634,6 +3642,7 @@ runs.pushed_by=pushed by
|
|||||||
runs.invalid_workflow_helper=ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s
|
runs.invalid_workflow_helper=ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s
|
||||||
runs.no_matching_online_runner_helper=ラベルに一致するオンラインのランナーが見つかりません: %s
|
runs.no_matching_online_runner_helper=ラベルに一致するオンラインのランナーが見つかりません: %s
|
||||||
runs.no_job_without_needs=ワークフローには依存関係のないジョブが少なくとも1つ含まれている必要があります。
|
runs.no_job_without_needs=ワークフローには依存関係のないジョブが少なくとも1つ含まれている必要があります。
|
||||||
|
runs.no_job=ワークフローには少なくとも1つのジョブが含まれている必要があります
|
||||||
runs.actor=アクター
|
runs.actor=アクター
|
||||||
runs.status=ステータス
|
runs.status=ステータス
|
||||||
runs.actors_no_select=すべてのアクター
|
runs.actors_no_select=すべてのアクター
|
||||||
|
@ -1238,6 +1238,7 @@ file_view_rendered=Ver resultado processado
|
|||||||
file_view_raw=Ver em bruto
|
file_view_raw=Ver em bruto
|
||||||
file_permalink=Ligação permanente
|
file_permalink=Ligação permanente
|
||||||
file_too_large=O ficheiro é demasiado grande para ser apresentado.
|
file_too_large=O ficheiro é demasiado grande para ser apresentado.
|
||||||
|
file_is_empty=O ficheiro está vazio.
|
||||||
code_preview_line_from_to=Linhas %[1]d até %[2]d em %[3]s
|
code_preview_line_from_to=Linhas %[1]d até %[2]d em %[3]s
|
||||||
code_preview_line_in=Linha %[1]d em %[2]s
|
code_preview_line_in=Linha %[1]d em %[2]s
|
||||||
invisible_runes_header=`Este ficheiro contém caracteres Unicode invisíveis`
|
invisible_runes_header=`Este ficheiro contém caracteres Unicode invisíveis`
|
||||||
@ -1554,7 +1555,9 @@ issues.no_content=Nenhuma descrição fornecida.
|
|||||||
issues.close=Encerrar questão
|
issues.close=Encerrar questão
|
||||||
issues.comment_pull_merged_at=cometimento %[1]s integrado em %[2]s %[3]s
|
issues.comment_pull_merged_at=cometimento %[1]s integrado em %[2]s %[3]s
|
||||||
issues.comment_manually_pull_merged_at=cometimento %[1]s integrado manualmente em %[2]s %[3]s
|
issues.comment_manually_pull_merged_at=cometimento %[1]s integrado manualmente em %[2]s %[3]s
|
||||||
|
issues.close_comment_issue=Fechar com comentário
|
||||||
issues.reopen_issue=Reabrir
|
issues.reopen_issue=Reabrir
|
||||||
|
issues.reopen_comment_issue=Reabrir com comentário
|
||||||
issues.create_comment=Comentar
|
issues.create_comment=Comentar
|
||||||
issues.comment.blocked_user=Não pode criar ou editar o comentário porque foi bloqueado/a pelo remetente ou pelo/a proprietário/a do repositório.
|
issues.comment.blocked_user=Não pode criar ou editar o comentário porque foi bloqueado/a pelo remetente ou pelo/a proprietário/a do repositório.
|
||||||
issues.closed_at=`encerrou esta questão <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at=`encerrou esta questão <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -10,7 +10,7 @@
|
|||||||
"@citation-js/plugin-csl": "0.7.11",
|
"@citation-js/plugin-csl": "0.7.11",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.1",
|
"@github/relative-time-element": "4.4.2",
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
@ -1028,9 +1028,9 @@
|
|||||||
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A=="
|
"integrity": "sha512-AlquKGee+IWiAMYVB0xyHFZRMnu4n3X4HTvJHu79GiVJ1ojTukCWyxMlF5NMsecoLcBKsuBhx3QPv2vkE/zQ0A=="
|
||||||
},
|
},
|
||||||
"node_modules/@github/relative-time-element": {
|
"node_modules/@github/relative-time-element": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.2.tgz",
|
||||||
"integrity": "sha512-E2vRcIgDj8AHv/iHpQMLJ/RqKOJ704OXkKw6+Zdhk3X+kVQhOf3Wj8KVz4DfCQ1eOJR8XxY6XVv73yd+pjMfXA=="
|
"integrity": "sha512-wTXunu3hmuGljA5CHaaoUIKV0oI35wno0FKJl2yqKplTRnsCA5bPNj4bDeVIubkuskql6jwionWLlGM1Y6QLaw=="
|
||||||
},
|
},
|
||||||
"node_modules/@github/text-expander-element": {
|
"node_modules/@github/text-expander-element": {
|
||||||
"version": "2.6.1",
|
"version": "2.6.1",
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"@citation-js/plugin-csl": "0.7.11",
|
"@citation-js/plugin-csl": "0.7.11",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.1",
|
"@github/relative-time-element": "4.4.2",
|
||||||
"@github/text-expander-element": "2.6.1",
|
"@github/text-expander-element": "2.6.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.9.0",
|
"@primer/octicons": "19.9.0",
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "gitea"
|
package-mode = false
|
||||||
version = "0.0.0"
|
|
||||||
description = ""
|
|
||||||
authors = []
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
|
@ -242,16 +242,12 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get upload file size
|
// get upload file size
|
||||||
fileRealTotalSize, contentLength, err := getUploadFileSize(ctx)
|
fileRealTotalSize, contentLength := getUploadFileSize(ctx)
|
||||||
if err != nil {
|
|
||||||
log.Error("Error get upload file size: %v", err)
|
|
||||||
ctx.Error(http.StatusInternalServerError, "Error get upload file size")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get artifact retention days
|
// get artifact retention days
|
||||||
expiredDays := setting.Actions.ArtifactRetentionDays
|
expiredDays := setting.Actions.ArtifactRetentionDays
|
||||||
if queryRetentionDays := ctx.Req.URL.Query().Get("retentionDays"); queryRetentionDays != "" {
|
if queryRetentionDays := ctx.Req.URL.Query().Get("retentionDays"); queryRetentionDays != "" {
|
||||||
|
var err error
|
||||||
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
|
expiredDays, err = strconv.ParseInt(queryRetentionDays, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error parse retention days: %v", err)
|
log.Error("Error parse retention days: %v", err)
|
||||||
|
@ -39,7 +39,7 @@ func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext,
|
|||||||
r = io.TeeReader(r, hasher)
|
r = io.TeeReader(r, hasher)
|
||||||
}
|
}
|
||||||
// save chunk to storage
|
// save chunk to storage
|
||||||
writtenSize, err := st.Save(storagePath, r, -1)
|
writtenSize, err := st.Save(storagePath, r, contentSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, fmt.Errorf("save chunk to storage error: %v", err)
|
return -1, fmt.Errorf("save chunk to storage error: %v", err)
|
||||||
}
|
}
|
||||||
@ -208,7 +208,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st
|
|||||||
|
|
||||||
// save merged file
|
// save merged file
|
||||||
storagePath := fmt.Sprintf("%d/%d/%d.%s", artifact.RunID%255, artifact.ID%255, time.Now().UnixNano(), extension)
|
storagePath := fmt.Sprintf("%d/%d/%d.%s", artifact.RunID%255, artifact.ID%255, time.Now().UnixNano(), extension)
|
||||||
written, err := st.Save(storagePath, mergedReader, -1)
|
written, err := st.Save(storagePath, mergedReader, artifact.FileCompressedSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("save merged file error: %v", err)
|
return fmt.Errorf("save merged file error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ func validateRunID(ctx *ArtifactContext) (*actions.ActionTask, int64, bool) {
|
|||||||
return task, runID, true
|
return task, runID, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) {
|
func validateRunIDV4(ctx *ArtifactContext, rawRunID string) (*actions.ActionTask, int64, bool) { //nolint:unparam
|
||||||
task := ctx.ActionTask
|
task := ctx.ActionTask
|
||||||
runID, err := strconv.ParseInt(rawRunID, 10, 64)
|
runID, err := strconv.ParseInt(rawRunID, 10, 64)
|
||||||
if err != nil || task.Job.RunID != runID {
|
if err != nil || task.Job.RunID != runID {
|
||||||
@ -84,11 +84,11 @@ func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) {
|
|||||||
|
|
||||||
// getUploadFileSize returns the size of the file to be uploaded.
|
// getUploadFileSize returns the size of the file to be uploaded.
|
||||||
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
|
// The raw size is the size of the file as reported by the header X-TFS-FileLength.
|
||||||
func getUploadFileSize(ctx *ArtifactContext) (int64, int64, error) {
|
func getUploadFileSize(ctx *ArtifactContext) (int64, int64) {
|
||||||
contentLength := ctx.Req.ContentLength
|
contentLength := ctx.Req.ContentLength
|
||||||
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
|
xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64)
|
||||||
if xTfsLength > 0 {
|
if xTfsLength > 0 {
|
||||||
return xTfsLength, contentLength, nil
|
return xTfsLength, contentLength
|
||||||
}
|
}
|
||||||
return contentLength, contentLength, nil
|
return contentLength, contentLength
|
||||||
}
|
}
|
||||||
|
@ -588,6 +588,8 @@ func CommonRoutes() *web.Route {
|
|||||||
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
||||||
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
||||||
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
||||||
|
r.Get("/info/{packagename}", rubygems.GetPackageInfo)
|
||||||
|
r.Get("/versions", rubygems.GetAllPackagesVersions)
|
||||||
r.Group("/api/v1/gems", func() {
|
r.Group("/api/v1/gems", func() {
|
||||||
r.Post("/", rubygems.UploadPackageFile)
|
r.Post("/", rubygems.UploadPackageFile)
|
||||||
r.Delete("/yank", rubygems.DeletePackage)
|
r.Delete("/yank", rubygems.DeletePackage)
|
||||||
|
@ -26,7 +26,7 @@ var uploadVersionMutex sync.Mutex
|
|||||||
|
|
||||||
// saveAsPackageBlob creates a package blob from an upload
|
// saveAsPackageBlob creates a package blob from an upload
|
||||||
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
// The uploaded blob gets stored in a special upload version to link them to the package/image
|
||||||
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) {
|
func saveAsPackageBlob(ctx context.Context, hsr packages_module.HashedSizeReader, pci *packages_service.PackageCreationInfo) (*packages_model.PackageBlob, error) { //nolint:unparam
|
||||||
pb := packages_service.NewPackageBlob(hsr)
|
pb := packages_service.NewPackageBlob(hsr)
|
||||||
|
|
||||||
exists := false
|
exists := false
|
||||||
|
@ -116,9 +116,9 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apiUnauthorizedError(ctx *context.Context) {
|
func apiUnauthorizedError(ctx *context.Context) {
|
||||||
// TODO: it doesn't seem quite right but it doesn't really cause problem at the moment.
|
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed
|
||||||
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed, ideally.
|
realmURL := httplib.GuessCurrentHostURL(ctx) + "/v2/token"
|
||||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+httplib.GuessCurrentAppURL(ctx)+`v2/token",service="container_registry",scope="*"`)
|
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`)
|
||||||
apiErrorDefined(ctx, errUnauthorized)
|
apiErrorDefined(ctx, errUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ func apiError(ctx *context.Context, status int, obj any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func xmlResponse(ctx *context.Context, status int, obj any) {
|
func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam
|
||||||
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
|
ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
|
||||||
ctx.Resp.WriteHeader(status)
|
ctx.Resp.WriteHeader(status)
|
||||||
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
|
if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
|
||||||
|
@ -6,6 +6,7 @@ package rubygems
|
|||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
|
"crypto/md5"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -227,12 +228,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename string
|
filename := makeGemFullFileName(rp.Name, rp.Version, rp.Metadata.Platform)
|
||||||
if rp.Metadata.Platform == "" || rp.Metadata.Platform == "ruby" {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s.gem", rp.Name, rp.Version))
|
|
||||||
} else {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s-%s.gem", rp.Name, rp.Version, rp.Metadata.Platform))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = packages_service.CreatePackageAndAddFile(
|
_, _, err = packages_service.CreatePackageAndAddFile(
|
||||||
ctx,
|
ctx,
|
||||||
@ -300,6 +296,136 @@ func DeletePackage(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPackageInfo returns a custom text based format for the single rubygem with a line for each version of the rubygem
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetPackageInfo(ctx *context.Context) {
|
||||||
|
packageName := ctx.Params("packagename")
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, packageName)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
apiError(ctx, http.StatusNotFound, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
infoContent, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.PlainText(http.StatusOK, infoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems.
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetAllPackagesVersions(ctx *context.Context) {
|
||||||
|
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &strings.Builder{}
|
||||||
|
out.WriteString("---\n")
|
||||||
|
for _, pkg := range packages {
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, pkg.Name)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// format: RUBYGEM [-]VERSION_PLATFORM[,VERSION_PLATFORM],...] MD5
|
||||||
|
_, _ = fmt.Fprintf(out, "%s ", pkg.Name)
|
||||||
|
for i, v := range versions {
|
||||||
|
sep := util.Iif(i == len(versions)-1, "", ",")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s", v.Version, sep)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.PlainText(http.StatusOK, out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
|
||||||
|
out.WriteString(prefix)
|
||||||
|
if len(reqs) == 0 {
|
||||||
|
reqs = []rubygems_module.VersionRequirement{{Restriction: ">=", Version: "0"}}
|
||||||
|
}
|
||||||
|
for i, req := range reqs {
|
||||||
|
sep := util.Iif(i == 0, "", "&")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s %s", sep, req.Restriction, req.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) {
|
||||||
|
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
|
||||||
|
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
|
||||||
|
// REQUIREMENT: KEY:VALUE (always contains "checksum")
|
||||||
|
pd, err := packages_model.GetPackageDescriptor(ctx, version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := pd.Metadata.(*rubygems_module.Metadata)
|
||||||
|
fullFilename := makeGemFullFileName(pd.Package.Name, version.Version, metadata.Platform)
|
||||||
|
file, err := packages_model.GetFileForVersionByName(ctx, version.ID, fullFilename, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
blob, err := packages_model.GetBlobByID(ctx, file.BlobID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &strings.Builder{}
|
||||||
|
buf.WriteString(version.Version)
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
for i, dep := range metadata.RuntimeDependencies {
|
||||||
|
sep := util.Iif(i == 0, "", ",")
|
||||||
|
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(buf, "|checksum:%s", blob.HashSHA256)
|
||||||
|
if len(metadata.RequiredRubyVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",ruby:", metadata.RequiredRubyVersion, buf)
|
||||||
|
}
|
||||||
|
if len(metadata.RequiredRubygemsVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",rubygems:", metadata.RequiredRubygemsVersion, buf)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) {
|
||||||
|
ret := "---\n"
|
||||||
|
for _, v := range versions {
|
||||||
|
dep, err := makePackageVersionDependency(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ret += dep + "\n"
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeGemFullFileName(gemName, version, platform string) string {
|
||||||
|
var basename string
|
||||||
|
if platform == "" || platform == "ruby" {
|
||||||
|
basename = fmt.Sprintf("%s-%s", gemName, version)
|
||||||
|
} else {
|
||||||
|
basename = fmt.Sprintf("%s-%s-%s", gemName, version, platform)
|
||||||
|
}
|
||||||
|
return strings.ToLower(basename) + ".gem"
|
||||||
|
}
|
||||||
|
|
||||||
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
||||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
|
@ -1168,6 +1168,15 @@ func Routes() *web.Route {
|
|||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
|
m.Group("/tag_protections", func() {
|
||||||
|
m.Combo("").Get(repo.ListTagProtection).
|
||||||
|
Post(bind(api.CreateTagProtectionOption{}), mustNotBeArchived, repo.CreateTagProtection)
|
||||||
|
m.Group("/{id}", func() {
|
||||||
|
m.Combo("").Get(repo.GetTagProtection).
|
||||||
|
Patch(bind(api.EditTagProtectionOption{}), mustNotBeArchived, repo.EditTagProtection).
|
||||||
|
Delete(repo.DeleteTagProtection)
|
||||||
|
})
|
||||||
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
go_context "context"
|
go_context "context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -19,36 +20,40 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const AppURL = "http://localhost:3000/"
|
||||||
AppURL = "http://localhost:3000/"
|
|
||||||
Repo = "gogits/gogs"
|
|
||||||
FullURL = AppURL + Repo + "/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
|
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += path.Join("/src/branch/main", path.Dir(filePath))
|
||||||
|
}
|
||||||
options := api.MarkupOption{
|
options := api.MarkupOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
Markup(ctx)
|
Markup(ctx)
|
||||||
assert.Equal(t, responseBody, resp.Body.String())
|
assert.Equal(t, expectedBody, resp.Body.String())
|
||||||
assert.Equal(t, responseCode, resp.Code)
|
assert.Equal(t, expectedCode, resp.Code)
|
||||||
resp.Body.Reset()
|
resp.Body.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
|
func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += "/src/branch/main"
|
||||||
|
}
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
@ -65,7 +70,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
testCasesCommon := []string{
|
testCasesWiki := []string{
|
||||||
// dear imgui wiki markdown extract: special wiki syntax
|
// dear imgui wiki markdown extract: special wiki syntax
|
||||||
`Wiki! Enjoy :)
|
`Wiki! Enjoy :)
|
||||||
- [[Links, Language bindings, Engine bindings|Links]]
|
- [[Links, Language bindings, Engine bindings|Links]]
|
||||||
@ -74,20 +79,20 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
// rendered
|
// rendered
|
||||||
`<p>Wiki! Enjoy :)</p>
|
`<p>Wiki! Enjoy :)</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||||
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</a></li>
|
||||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
<li>Bezier widget (by <a href="http://localhost:3000/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
// Guard wiki sidebar: special syntax
|
// Guard wiki sidebar: special syntax
|
||||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||||
`,
|
`,
|
||||||
// special syntax
|
// special syntax
|
||||||
`[[Name|Link]]`,
|
`[[Name|Link]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Link" rel="nofollow">Name</a></p>
|
||||||
`,
|
`,
|
||||||
// empty
|
// empty
|
||||||
``,
|
``,
|
||||||
@ -95,7 +100,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
``,
|
``,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCasesDocument := []string{
|
testCasesWikiDocument := []string{
|
||||||
// wine-staging wiki home extract: special wiki syntax, images
|
// wine-staging wiki home extract: special wiki syntax, images
|
||||||
`## What is Wine Staging?
|
`## What is Wine Staging?
|
||||||
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
||||||
@ -111,31 +116,48 @@ Here are some links to the most important topics. You can find the full list of
|
|||||||
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
||||||
<h2 id="user-content-quick-links">Quick Links</h2>
|
<h2 id="user-content-quick-links">Quick Links</h2>
|
||||||
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
||||||
<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
|
<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
|
||||||
<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
<a href="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesCommon); i += 2 {
|
for i := 0; i < len(testCasesWiki); i += 2 {
|
||||||
text := testCasesCommon[i]
|
text := testCasesWiki[i]
|
||||||
response := testCasesCommon[i+1]
|
response := testCasesWiki[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkdown(t, "comment", text, response, http.StatusOK)
|
testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesDocument); i += 2 {
|
for i := 0; i < len(testCasesWikiDocument); i += 2 {
|
||||||
text := testCasesDocument[i]
|
text := testCasesWikiDocument[i]
|
||||||
response := testCasesDocument[i+1]
|
response := testCasesWikiDocument[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
input := "[Link](test.md)\n"
|
||||||
testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", false, "path/new-file.md", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/path/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", true, "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
||||||
|
testRenderMarkup(t, "unknown", true, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleCases = []string{
|
var simpleCases = []string{
|
||||||
@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
|
|||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: "markdown",
|
Mode: "markdown",
|
||||||
Text: "",
|
Text: "",
|
||||||
Context: Repo,
|
Context: "/gogits/gogs",
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
for i := 0; i < len(simpleCases); i += 2 {
|
for i := 0; i < len(simpleCases); i += 2 {
|
||||||
|
@ -64,7 +64,7 @@ func CompareDiff(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
|
_, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{
|
||||||
Base: infos[0],
|
Base: infos[0],
|
||||||
Head: infos[1],
|
Head: infos[1],
|
||||||
})
|
})
|
||||||
|
@ -408,7 +408,7 @@ func CreatePullRequest(ctx *context.APIContext) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Get repo/branch information
|
// Get repo/branch information
|
||||||
_, headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
|
headRepo, headGitRepo, compareInfo, baseBranch, headBranch := parseCompareInfo(ctx, form)
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1054,7 +1054,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
|||||||
ctx.Status(http.StatusOK)
|
ctx.Status(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*user_model.User, *repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
|
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (*repo_model.Repository, *git.Repository, *git.CompareInfo, string, string) {
|
||||||
baseRepo := ctx.Repo.Repository
|
baseRepo := ctx.Repo.Repository
|
||||||
|
|
||||||
// Get compared branches information
|
// Get compared branches information
|
||||||
@ -1087,14 +1087,14 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
} else {
|
} else {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||||
}
|
}
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
headBranch = headInfos[1]
|
headBranch = headInfos[1]
|
||||||
// The head repository can also point to the same repo
|
// The head repository can also point to the same repo
|
||||||
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
isSameRepo = ctx.Repo.Owner.ID == headUser.ID
|
||||||
} else {
|
} else {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
ctx.Repo.PullRequest.SameRepo = isSameRepo
|
||||||
@ -1102,7 +1102,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
// Check if base branch is valid.
|
// Check if base branch is valid.
|
||||||
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) {
|
||||||
ctx.NotFound("BaseNotExist")
|
ctx.NotFound("BaseNotExist")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if current user has fork of repository or in the same repository.
|
// Check if current user has fork of repository or in the same repository.
|
||||||
@ -1110,7 +1110,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
if headRepo == nil && !isSameRepo {
|
if headRepo == nil && !isSameRepo {
|
||||||
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID)
|
||||||
ctx.NotFound("GetForkedRepo")
|
ctx.NotFound("GetForkedRepo")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var headGitRepo *git.Repository
|
var headGitRepo *git.Repository
|
||||||
@ -1121,7 +1121,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
|
headGitRepo, err = gitrepo.OpenRepository(ctx, headRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1130,7 +1130,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(unit.TypeCode) {
|
||||||
if log.IsTrace() {
|
if log.IsTrace() {
|
||||||
@ -1141,7 +1141,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
}
|
}
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
ctx.NotFound("Can't read pulls or can't read UnitTypeCode")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// user should have permission to read headrepo's codes
|
// user should have permission to read headrepo's codes
|
||||||
@ -1149,7 +1149,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
if !permHead.CanRead(unit.TypeCode) {
|
if !permHead.CanRead(unit.TypeCode) {
|
||||||
if log.IsTrace() {
|
if log.IsTrace() {
|
||||||
@ -1160,24 +1160,24 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
|||||||
}
|
}
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound("Can't read headRepo UnitTypeCode")
|
ctx.NotFound("Can't read headRepo UnitTypeCode")
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if head branch is valid.
|
// Check if head branch is valid.
|
||||||
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
headGitRepo.Close()
|
headGitRepo.Close()
|
||||||
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
ctx.Error(http.StatusInternalServerError, "GetCompareInfo", err)
|
||||||
return nil, nil, nil, nil, "", ""
|
return nil, nil, nil, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return headUser, headRepo, headGitRepo, compareInfo, baseBranch, headBranch
|
return headRepo, headGitRepo, compareInfo, baseBranch, headBranch
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||||
|
@ -107,7 +107,7 @@ func Search(ctx *context.APIContext) {
|
|||||||
// - name: sort
|
// - name: sort
|
||||||
// in: query
|
// in: query
|
||||||
// description: sort repos by attribute. Supported values are
|
// description: sort repos by attribute. Supported values are
|
||||||
// "alpha", "created", "updated", "size", and "id".
|
// "alpha", "created", "updated", "size", "git_size", "lfs_size", "stars", "forks" and "id".
|
||||||
// Default is "alpha"
|
// Default is "alpha"
|
||||||
// type: string
|
// type: string
|
||||||
// - name: order
|
// - name: order
|
||||||
@ -184,7 +184,7 @@ func Search(ctx *context.APIContext) {
|
|||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,9 +7,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
@ -287,3 +291,349 @@ func DeleteTag(ctx *context.APIContext) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTagProtection lists tag protections for a repo
|
||||||
|
func ListTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections repository repoListTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: List tag protections for a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtectionList"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
pts, err := git_model.GetProtectedTags(ctx, repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTags", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiPts := make([]*api.TagProtection, len(pts))
|
||||||
|
for i := range pts {
|
||||||
|
apiPts[i] = convert.ToTagProtection(ctx, pts[i], repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiPts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagProtection gets a tag protection
|
||||||
|
func GetTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections/{id} repository repoGetTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Get a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the tag protect to get
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || repo.ID != pt.RepoID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtection creates a tag protection for a repo
|
||||||
|
func CreateTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/tag_protections repository repoCreateTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Create a tag protections for a repository
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
form := web.GetForm(ctx).(*api.CreateTagProtectionOption)
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
namePattern := strings.TrimSpace(form.NamePattern)
|
||||||
|
if namePattern == "" {
|
||||||
|
ctx.Error(http.StatusBadRequest, "name_pattern are empty", "name_pattern are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(form.WhitelistUsernames) == 0 && len(form.WhitelistTeams) == 0 {
|
||||||
|
ctx.Error(http.StatusBadRequest, "both whitelist_usernames and whitelist_teams are empty", "both whitelist_usernames and whitelist_teams are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err := git_model.GetProtectedTagByNamePattern(ctx, repo.ID, namePattern)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectTagOfRepo", err)
|
||||||
|
return
|
||||||
|
} else if pt != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "Create tag protection", "Tag protection already exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protectTag := &git_model.ProtectedTag{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
NamePattern: strings.TrimSpace(namePattern),
|
||||||
|
AllowlistUserIDs: whitelistUsers,
|
||||||
|
AllowlistTeamIDs: whitelistTeams,
|
||||||
|
}
|
||||||
|
if err := git_model.InsertProtectedTag(ctx, protectTag); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "InsertProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, protectTag.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtection edits a tag protection for a repo
|
||||||
|
func EditTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /repos/{owner}/{repo}/tag_protections/{id} repository repoEditTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Edit a tag protections for a repository. Only fields that are set will be changed
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
form := web.GetForm(ctx).(*api.EditTagProtectionOption)
|
||||||
|
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.NamePattern != nil {
|
||||||
|
pt.NamePattern = *form.NamePattern
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
if form.WhitelistTeams != nil {
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pt.AllowlistTeamIDs = whitelistTeams
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.WhitelistUsernames != nil {
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pt.AllowlistUserIDs = whitelistUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.UpdateProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", "New tag protection not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTagProtection
|
||||||
|
func DeleteTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/tag_protections/{id} repository repoDeleteTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Delete a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.DeleteProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
@ -170,6 +170,12 @@ type swaggerParameterBodies struct {
|
|||||||
// in:body
|
// in:body
|
||||||
CreateTagOption api.CreateTagOption
|
CreateTagOption api.CreateTagOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
CreateTagProtectionOption api.CreateTagProtectionOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
EditTagProtectionOption api.EditTagProtectionOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateAccessTokenOption api.CreateAccessTokenOption
|
CreateAccessTokenOption api.CreateAccessTokenOption
|
||||||
|
|
||||||
|
@ -70,6 +70,20 @@ type swaggerResponseAnnotatedTag struct {
|
|||||||
Body api.AnnotatedTag `json:"body"`
|
Body api.AnnotatedTag `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtectionList
|
||||||
|
// swagger:response TagProtectionList
|
||||||
|
type swaggerResponseTagProtectionList struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagProtection
|
||||||
|
// swagger:response TagProtection
|
||||||
|
type swaggerResponseTagProtection struct {
|
||||||
|
// in:body
|
||||||
|
Body api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// Reference
|
// Reference
|
||||||
// swagger:response Reference
|
// swagger:response Reference
|
||||||
type swaggerResponseReference struct {
|
type swaggerResponseReference struct {
|
||||||
|
@ -7,63 +7,67 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
"mvdan.cc/xurls/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
||||||
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
|
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
|
||||||
var markupType string
|
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
|
||||||
relativePath := ""
|
// filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
|
||||||
|
// filePath will be used as RenderContext.RelativePath
|
||||||
|
|
||||||
if len(text) == 0 {
|
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||||
_, _ = ctx.Write([]byte(""))
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
return
|
|
||||||
|
var markupType, relativePath string
|
||||||
|
|
||||||
|
links := markup.Links{AbsolutePrefix: true}
|
||||||
|
if urlPathContext != "" {
|
||||||
|
links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case "markdown":
|
case "markdown":
|
||||||
// Raw markdown
|
// Raw markdown
|
||||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case "comment":
|
case "comment":
|
||||||
// Comment as markdown
|
// Issue & comment content
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "gfm":
|
case "gfm":
|
||||||
// Github Flavored Markdown as document
|
// GitHub Flavored Markdown
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "file":
|
case "file":
|
||||||
// File as document based on file extension
|
markupType = "" // render the repo file content by its extension
|
||||||
markupType = ""
|
|
||||||
relativePath = filePath
|
relativePath = filePath
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
|
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
|
||||||
// check if urlPrefix is already set to a URL
|
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
|
||||||
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
|
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
|
||||||
m := linkRegex.FindStringIndex(urlPrefix)
|
absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
|
||||||
if m == nil {
|
|
||||||
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
|
fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
|
||||||
}
|
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
||||||
|
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
|
||||||
|
|
||||||
|
links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]string{}
|
meta := map[string]string{}
|
||||||
@ -81,12 +85,9 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := markup.Render(&markup.RenderContext{
|
if err := markup.Render(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Repo: repoCtx,
|
Repo: repoCtx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
Metas: meta,
|
Metas: meta,
|
||||||
IsWiki: wiki,
|
IsWiki: wiki,
|
||||||
Type: markupType,
|
Type: markupType,
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
||||||
func ProtocolMiddlewares() (handlers []any) {
|
func ProtocolMiddlewares() (handlers []any) {
|
||||||
// first, normalize the URL path
|
// first, normalize the URL path
|
||||||
handlers = append(handlers, stripSlashesMiddleware)
|
handlers = append(handlers, normalizeRequestPathMiddleware)
|
||||||
|
|
||||||
// prepare the ContextData and panic recovery
|
// prepare the ContextData and panic recovery
|
||||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||||
@ -75,9 +75,9 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripSlashesMiddleware(next http.Handler) http.Handler {
|
func normalizeRequestPathMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
// escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
||||||
req.URL.RawPath = req.URL.EscapedPath()
|
req.URL.RawPath = req.URL.EscapedPath()
|
||||||
|
|
||||||
urlPath := req.URL.RawPath
|
urlPath := req.URL.RawPath
|
||||||
@ -86,19 +86,42 @@ func stripSlashesMiddleware(next http.Handler) http.Handler {
|
|||||||
urlPath = rctx.RoutePath
|
urlPath = rctx.RoutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedPath := &strings.Builder{}
|
normalizedPath := strings.TrimRight(urlPath, "/")
|
||||||
prevWasSlash := false
|
// the following code block is a slow-path for replacing all repeated slashes "//" to one single "/"
|
||||||
for _, chr := range strings.TrimRight(urlPath, "/") {
|
// if the path doesn't have repeated slashes, then no need to execute it
|
||||||
if chr != '/' || !prevWasSlash {
|
if strings.Contains(normalizedPath, "//") {
|
||||||
sanitizedPath.WriteRune(chr)
|
buf := &strings.Builder{}
|
||||||
|
prevWasSlash := false
|
||||||
|
for _, chr := range normalizedPath {
|
||||||
|
if chr != '/' || !prevWasSlash {
|
||||||
|
buf.WriteRune(chr)
|
||||||
|
}
|
||||||
|
prevWasSlash = chr == '/'
|
||||||
}
|
}
|
||||||
prevWasSlash = chr == '/'
|
normalizedPath = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.UseSubURLPath {
|
||||||
|
remainingPath, ok := strings.CutPrefix(normalizedPath, setting.AppSubURL+"/")
|
||||||
|
if ok {
|
||||||
|
normalizedPath = "/" + remainingPath
|
||||||
|
} else if normalizedPath == setting.AppSubURL {
|
||||||
|
normalizedPath = "/"
|
||||||
|
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
||||||
|
// do not respond to other requests, to simulate a real sub-path environment
|
||||||
|
http.Error(resp, "404 page not found, sub-path is: "+setting.AppSubURL, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: it's not quite clear about how req.URL and rctx.RoutePath work together.
|
||||||
|
// Fortunately, it is only used for debug purpose, we have enough time to figure it out in the future.
|
||||||
|
req.URL.RawPath = normalizedPath
|
||||||
|
req.URL.Path = normalizedPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if rctx == nil {
|
if rctx == nil {
|
||||||
req.URL.Path = sanitizedPath.String()
|
req.URL.Path = normalizedPath
|
||||||
} else {
|
} else {
|
||||||
rctx.RoutePath = sanitizedPath.String()
|
rctx.RoutePath = normalizedPath
|
||||||
}
|
}
|
||||||
next.ServeHTTP(resp, req)
|
next.ServeHTTP(resp, req)
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ func TestStripSlashesMiddleware(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// pass the test middleware to validate the changes
|
// pass the test middleware to validate the changes
|
||||||
handlerToTest := stripSlashesMiddleware(testMiddleware)
|
handlerToTest := normalizeRequestPathMiddleware(testMiddleware)
|
||||||
// create a mock request to use
|
// create a mock request to use
|
||||||
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
||||||
// call the handler using a mock response recorder
|
// call the handler using a mock response recorder
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
@ -222,6 +223,14 @@ func SelfCheck(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["DatabaseCheckHasProblems"] = hasProblem
|
ctx.Data["DatabaseCheckHasProblems"] = hasProblem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elapsed, err := cache.Test()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Data["CacheError"] = err
|
||||||
|
} else if elapsed > cache.SlowCacheThreshold {
|
||||||
|
ctx.Data["CacheSlow"] = fmt.Sprint(elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSelfCheck)
|
ctx.HTML(http.StatusOK, tplSelfCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
system_model "code.gitea.io/gitea/models/system"
|
system_model "code.gitea.io/gitea/models/system"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -42,6 +43,22 @@ func SendTestMail(ctx *context.Context) {
|
|||||||
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCache test the cache settings
|
||||||
|
func TestCache(ctx *context.Context) {
|
||||||
|
elapsed, err := cache.Test()
|
||||||
|
if err != nil {
|
||||||
|
ctx.Flash.Error(ctx.Tr("admin.config.cache_test_failed", err))
|
||||||
|
} else {
|
||||||
|
if elapsed > cache.SlowCacheThreshold {
|
||||||
|
ctx.Flash.Warning(ctx.Tr("admin.config.cache_test_slow", elapsed))
|
||||||
|
} else {
|
||||||
|
ctx.Flash.Info(ctx.Tr("admin.config.cache_test_succeeded", elapsed))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
||||||
|
}
|
||||||
|
|
||||||
func shadowPasswordKV(cfgItem, splitter string) string {
|
func shadowPasswordKV(cfgItem, splitter string) string {
|
||||||
fields := strings.Split(cfgItem, splitter)
|
fields := strings.Split(cfgItem, splitter)
|
||||||
for i := 0; i < len(fields); i++ {
|
for i := 0; i < len(fields); i++ {
|
||||||
@ -183,7 +200,7 @@ func ChangeConfig(ctx *context.Context) {
|
|||||||
value := ctx.FormString("value")
|
value := ctx.FormString("value")
|
||||||
cfg := setting.Config()
|
cfg := setting.Config()
|
||||||
|
|
||||||
marshalBool := func(v string) (string, error) {
|
marshalBool := func(v string) (string, error) { //nolint:unparam
|
||||||
if b, _ := strconv.ParseBool(v); b {
|
if b, _ := strconv.ParseBool(v); b {
|
||||||
return "true", nil
|
return "true", nil
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package explore
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
@ -57,47 +58,18 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
|||||||
orderBy db.SearchOrderBy
|
orderBy db.SearchOrderBy
|
||||||
)
|
)
|
||||||
|
|
||||||
sortOrder := ctx.FormString("sort")
|
sortOrder := strings.ToLower(ctx.FormString("sort"))
|
||||||
if sortOrder == "" {
|
if sortOrder == "" {
|
||||||
sortOrder = setting.UI.ExploreDefaultSort
|
sortOrder = setting.UI.ExploreDefaultSort
|
||||||
}
|
}
|
||||||
ctx.Data["SortType"] = sortOrder
|
|
||||||
|
|
||||||
switch sortOrder {
|
if order, ok := repo_model.OrderByFlatMap[sortOrder]; ok {
|
||||||
case "newest":
|
orderBy = order
|
||||||
orderBy = db.SearchOrderByNewest
|
} else {
|
||||||
case "oldest":
|
sortOrder = "recentupdate"
|
||||||
orderBy = db.SearchOrderByOldest
|
|
||||||
case "leastupdate":
|
|
||||||
orderBy = db.SearchOrderByLeastUpdated
|
|
||||||
case "reversealphabetically":
|
|
||||||
orderBy = db.SearchOrderByAlphabeticallyReverse
|
|
||||||
case "alphabetically":
|
|
||||||
orderBy = db.SearchOrderByAlphabetically
|
|
||||||
case "reversesize":
|
|
||||||
orderBy = db.SearchOrderBySizeReverse
|
|
||||||
case "size":
|
|
||||||
orderBy = db.SearchOrderBySize
|
|
||||||
case "reversegitsize":
|
|
||||||
orderBy = db.SearchOrderByGitSizeReverse
|
|
||||||
case "gitsize":
|
|
||||||
orderBy = db.SearchOrderByGitSize
|
|
||||||
case "reverselfssize":
|
|
||||||
orderBy = db.SearchOrderByLFSSizeReverse
|
|
||||||
case "lfssize":
|
|
||||||
orderBy = db.SearchOrderByLFSSize
|
|
||||||
case "moststars":
|
|
||||||
orderBy = db.SearchOrderByStarsReverse
|
|
||||||
case "feweststars":
|
|
||||||
orderBy = db.SearchOrderByStars
|
|
||||||
case "mostforks":
|
|
||||||
orderBy = db.SearchOrderByForksReverse
|
|
||||||
case "fewestforks":
|
|
||||||
orderBy = db.SearchOrderByForks
|
|
||||||
default:
|
|
||||||
ctx.Data["SortType"] = "recentupdate"
|
|
||||||
orderBy = db.SearchOrderByRecentUpdated
|
orderBy = db.SearchOrderByRecentUpdated
|
||||||
}
|
}
|
||||||
|
ctx.Data["SortType"] = sortOrder
|
||||||
|
|
||||||
keyword := ctx.FormTrim("q")
|
keyword := ctx.FormTrim("q")
|
||||||
|
|
||||||
|
@ -99,8 +99,6 @@ func RefBlame(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
|
ctx.Data["NumLines"], err = blob.GetBlobLineCount()
|
||||||
ctx.Data["NumLinesSet"] = true
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.NotFound("GetBlobLineCount", err)
|
ctx.NotFound("GetBlobLineCount", err)
|
||||||
return
|
return
|
||||||
|
@ -418,8 +418,9 @@ func RedirectDownload(ctx *context.Context) {
|
|||||||
tagNames := []string{vTag}
|
tagNames := []string{vTag}
|
||||||
curRepo := ctx.Repo.Repository
|
curRepo := ctx.Repo.Repository
|
||||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||||
RepoID: curRepo.ID,
|
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
|
||||||
TagNames: tagNames,
|
RepoID: curRepo.ID,
|
||||||
|
TagNames: tagNames,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("RedirectDownload", err)
|
ctx.ServerError("RedirectDownload", err)
|
||||||
@ -615,7 +616,7 @@ func SearchRepo(ctx *context.Context) {
|
|||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
@ -303,6 +303,7 @@ func LFSFileGet(ctx *context.Context) {
|
|||||||
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
|
||||||
|
|
||||||
// Building code view blocks with line number on server side.
|
// Building code view blocks with line number on server side.
|
||||||
|
// FIXME: the logic is not right here: it first calls EscapeControlReader then calls HTMLEscapeString: double-escaping
|
||||||
escapedContent := &bytes.Buffer{}
|
escapedContent := &bytes.Buffer{}
|
||||||
ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, escapedContent, ctx.Locale)
|
ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, escapedContent, ctx.Locale)
|
||||||
|
|
||||||
|
@ -286,6 +286,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
|
|||||||
|
|
||||||
ctx.Data["FileIsText"] = fInfo.isTextFile
|
ctx.Data["FileIsText"] = fInfo.isTextFile
|
||||||
ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
|
ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
|
||||||
|
ctx.Data["FileSize"] = fInfo.fileSize
|
||||||
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
ctx.Data["IsLFSFile"] = fInfo.isLFSFile
|
||||||
|
|
||||||
if fInfo.isLFSFile {
|
if fInfo.isLFSFile {
|
||||||
@ -301,7 +302,6 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
|
|||||||
// Pretend that this is a normal text file to display 'This file is too large to be shown'
|
// Pretend that this is a normal text file to display 'This file is too large to be shown'
|
||||||
ctx.Data["IsFileTooLarge"] = true
|
ctx.Data["IsFileTooLarge"] = true
|
||||||
ctx.Data["IsTextFile"] = true
|
ctx.Data["IsTextFile"] = true
|
||||||
ctx.Data["FileSize"] = fInfo.fileSize
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,7 +552,6 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
} else {
|
} else {
|
||||||
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
|
||||||
}
|
}
|
||||||
ctx.Data["NumLinesSet"] = true
|
|
||||||
|
|
||||||
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
|
language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -606,8 +605,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this logic seems strange, it duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
|
||||||
// maybe for this case, the file is a binary file, and shouldn't be rendered?
|
// It is used by "external renders", markupRender will execute external programs to get rendered content.
|
||||||
if markupType := markup.Type(blob.Name()); markupType != "" {
|
if markupType := markup.Type(blob.Name()); markupType != "" {
|
||||||
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
rd := io.MultiReader(bytes.NewReader(buf), dataRc)
|
||||||
ctx.Data["IsMarkup"] = true
|
ctx.Data["IsMarkup"] = true
|
||||||
|
@ -5,6 +5,7 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
org_model "code.gitea.io/gitea/models/organization"
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
|
alpine_module "code.gitea.io/gitea/modules/packages/alpine"
|
||||||
@ -178,7 +180,11 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||||||
|
|
||||||
switch pd.Package.Type {
|
switch pd.Package.Type {
|
||||||
case packages_model.TypeContainer:
|
case packages_model.TypeContainer:
|
||||||
ctx.Data["RegistryHost"] = setting.Packages.RegistryHost
|
registryAppURL, err := url.Parse(httplib.GuessCurrentAppURL(ctx))
|
||||||
|
if err != nil {
|
||||||
|
registryAppURL, _ = url.Parse(setting.AppURL)
|
||||||
|
}
|
||||||
|
ctx.Data["RegistryHost"] = registryAppURL.Host
|
||||||
case packages_model.TypeAlpine:
|
case packages_model.TypeAlpine:
|
||||||
branches := make(container.Set[string])
|
branches := make(container.Set[string])
|
||||||
repositories := make(container.Set[string])
|
repositories := make(container.Set[string])
|
||||||
|
@ -692,6 +692,7 @@ func registerRoutes(m *web.Route) {
|
|||||||
m.Get("", admin.Config)
|
m.Get("", admin.Config)
|
||||||
m.Post("", admin.ChangeConfig)
|
m.Post("", admin.ChangeConfig)
|
||||||
m.Post("/test_mail", admin.SendTestMail)
|
m.Post("/test_mail", admin.SendTestMail)
|
||||||
|
m.Post("/test_cache", admin.TestCache)
|
||||||
m.Get("/settings", admin.ConfigSettings)
|
m.Get("/settings", admin.ConfigSettings)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -408,6 +408,32 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
||||||
|
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
||||||
|
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetRepoReaders: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistUsernames := getWhitelistEntities(readers, pt.AllowlistUserIDs)
|
||||||
|
|
||||||
|
teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistTeams := getWhitelistEntities(teamReaders, pt.AllowlistTeamIDs)
|
||||||
|
|
||||||
|
return &api.TagProtection{
|
||||||
|
ID: pt.ID,
|
||||||
|
NamePattern: pt.NamePattern,
|
||||||
|
WhitelistUsernames: whitelistUsernames,
|
||||||
|
WhitelistTeams: whitelistTeams,
|
||||||
|
Created: pt.CreatedUnix.AsTime(),
|
||||||
|
Updated: pt.UpdatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
||||||
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
||||||
return &api.TopicResponse{
|
return &api.TopicResponse{
|
||||||
|
@ -477,7 +477,7 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
|
// This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662
|
||||||
verifyHeader["Accept"] = lfs_module.MediaType
|
verifyHeader["Accept"] = lfs_module.AcceptHeader
|
||||||
|
|
||||||
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
|
rep.Actions["verify"] = &lfs_module.Link{Href: rc.VerifyLink(pointer), Header: verifyHeader}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -56,7 +57,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
|
|||||||
issueReference = "!"
|
issueReference = "!"
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewedOn := fmt.Sprintf("Reviewed-on: %s/%s", setting.AppURL, pr.Issue.Link())
|
reviewedOn := fmt.Sprintf("Reviewed-on: %s", httplib.MakeAbsoluteURL(ctx, pr.Issue.Link()))
|
||||||
reviewedBy := pr.GetApprovers(ctx)
|
reviewedBy := pr.GetApprovers(ctx)
|
||||||
|
|
||||||
if mergeStyle != "" {
|
if mergeStyle != "" {
|
||||||
@ -246,7 +247,7 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
// doMergeAndPush performs the merge operation without changing any pull information in database and pushes it up to the base repository
|
||||||
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) {
|
func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, mergeStyle repo_model.MergeStyle, expectedHeadCommitID, message string, pushTrigger repo_module.PushTrigger) (string, error) { //nolint:unparam
|
||||||
// Clone base repo.
|
// Clone base repo.
|
||||||
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, expectedHeadCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user