Browse Source

Merge remote-tracking branch 'origin/master' into for-closed-social

for-closed-social
欧醚 4 years ago
parent
commit
f83516760e
50 changed files with 655 additions and 222 deletions
  1. +2
    -0
      custom/conf/app.example.ini
  2. +4
    -0
      docs/content/doc/advanced/config-cheat-sheet.en-us.md
  3. +64
    -25
      models/gpg_key.go
  4. +2
    -0
      models/migrations/migrations.go
  5. +14
    -0
      models/migrations/v152.go
  6. +2
    -0
      models/notification.go
  7. +25
    -25
      models/pull_sign.go
  8. +56
    -0
      models/repo.go
  9. +51
    -42
      models/repo_sign.go
  10. +4
    -0
      modules/auth/repo_form.go
  11. +1
    -1
      modules/context/repo.go
  12. +5
    -5
      modules/git/repo_tree.go
  13. +1
    -1
      modules/migrations/gitea_test.go
  14. +6
    -2
      modules/migrations/github.go
  15. +1
    -1
      modules/migrations/github_test.go
  16. +1
    -1
      modules/repofiles/delete.go
  17. +19
    -3
      modules/repofiles/temp_repo.go
  18. +1
    -1
      modules/repofiles/update.go
  19. +1
    -0
      modules/repository/create.go
  20. +1
    -0
      modules/repository/generate.go
  21. +14
    -3
      modules/repository/init.go
  22. +31
    -21
      modules/setting/repository.go
  23. +5
    -4
      modules/structs/notifications.go
  24. +3
    -0
      modules/structs/repo.go
  25. +17
    -0
      options/locale/locale_de-DE.ini
  26. +13
    -0
      options/locale/locale_en-US.ini
  27. +28
    -17
      options/locale/locale_tr-TR.ini
  28. +88
    -24
      options/locale/locale_zh-TW.ini
  29. +3
    -5
      routers/api/v1/misc/signing.go
  30. +1
    -1
      routers/api/v1/org/label.go
  31. +1
    -1
      routers/api/v1/org/org.go
  32. +9
    -9
      routers/api/v1/repo/commits.go
  33. +1
    -1
      routers/api/v1/repo/fork.go
  34. +1
    -1
      routers/api/v1/repo/label.go
  35. +1
    -1
      routers/api/v1/repo/milestone.go
  36. +4
    -4
      routers/api/v1/repo/pull.go
  37. +3
    -3
      routers/api/v1/repo/pull_review.go
  38. +2
    -2
      routers/api/v1/repo/release.go
  39. +2
    -1
      routers/api/v1/repo/repo.go
  40. +1
    -1
      routers/repo/issue.go
  41. +1
    -0
      routers/repo/repo.go
  42. +25
    -0
      routers/repo/setting.go
  43. +14
    -5
      services/pull/merge.go
  44. +8
    -3
      services/repository/push.go
  45. +15
    -4
      services/wiki/wiki.go
  46. +13
    -0
      templates/repo/create.tmpl
  47. +46
    -0
      templates/repo/settings/options.tmpl
  48. +14
    -0
      templates/swagger/v1_json.tmpl
  49. +10
    -3
      web_src/js/features/migration.js
  50. +20
    -1
      web_src/js/index.js

+ 2
- 0
custom/conf/app.example.ini View File

@ -124,6 +124,8 @@ SIGNING_KEY = default
; by setting the SIGNING_KEY ID to the correct ID.) ; by setting the SIGNING_KEY ID to the correct ID.)
SIGNING_NAME = SIGNING_NAME =
SIGNING_EMAIL = SIGNING_EMAIL =
; Sets the default trust model for repositories. Options are: collaborator, committer, collaboratorcommitter
DEFAULT_TRUST_MODEL=collaborator
; Determines when gitea should sign the initial commit when creating a repository ; Determines when gitea should sign the initial commit when creating a repository
; Either: ; Either:
; - never ; - never

+ 4
- 0
docs/content/doc/advanced/config-cheat-sheet.en-us.md View File

@ -101,6 +101,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
- `twofa`: Only sign if the user is logged in with twofa - `twofa`: Only sign if the user is logged in with twofa
- `always`: Always sign - `always`: Always sign
- Options other than `never` and `always` can be combined as a comma separated list. - Options other than `never` and `always` can be combined as a comma separated list.
- `DEFAULT_TRUST_MODEL`: **collaborator**: \[collaborator, committer, collaboratorcommitter\]: The default trust model used for verifying commits.
- `collaborator`: Trust signatures signed by keys of collaborators.
- `committer`: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the commmitter).
- `collaboratorcommitter`: Trust signatures signed by keys of collaborators which match the commiter.
- `WIKI`: **never**: \[never, pubkey, twofa, always, parentsigned\]: Sign commits to wiki. - `WIKI`: **never**: \[never, pubkey, twofa, always, parentsigned\]: Sign commits to wiki.
- `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions. - `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions.
- Options as above, with the addition of: - Options as above, with the addition of:

+ 64
- 25
models/gpg_key.go View File

@ -831,7 +831,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
newCommits = list.New() newCommits = list.New()
e = oldCommits.Front() e = oldCommits.Front()
) )
memberMap := map[int64]bool{}
keyMap := map[string]bool{}
for e != nil { for e != nil {
c := e.Value.(UserCommit) c := e.Value.(UserCommit)
@ -840,7 +840,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
Verification: ParseCommitWithSignature(c.Commit), Verification: ParseCommitWithSignature(c.Commit),
} }
_ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap)
_ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap)
newCommits.PushBack(signCommit) newCommits.PushBack(signCommit)
e = e.Next() e = e.Next()
@ -849,31 +849,70 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
} }
// CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository // CalculateTrustStatus will calculate the TrustStatus for a commit verification within a repository
func CalculateTrustStatus(verification *CommitVerification, repository *Repository, memberMap *map[int64]bool) (err error) {
if verification.Verified {
verification.TrustStatus = "trusted"
if verification.SigningUser.ID != 0 {
var isMember bool
if memberMap != nil {
var has bool
isMember, has = (*memberMap)[verification.SigningUser.ID]
if !has {
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
(*memberMap)[verification.SigningUser.ID] = isMember
}
} else {
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
}
func CalculateTrustStatus(verification *CommitVerification, repository *Repository, keyMap *map[string]bool) (err error) {
if !verification.Verified {
return
}
if !isMember {
verification.TrustStatus = "untrusted"
if verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same and are not the default key
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
verification.TrustStatus = "unmatched"
}
}
// There are several trust models in Gitea
trustModel := repository.GetTrustModel()
// In the Committer trust model a signature is trusted if it matches the committer
// - it doesn't matter if they're a collaborator, the owner, Gitea or Github
// NB: This model is commit verification only
if trustModel == CommitterTrustModel {
// default to "unmatched"
verification.TrustStatus = "unmatched"
// We can only verify against users in our database but the default key will match
// against by email if it is not in the db.
if (verification.SigningUser.ID != 0 &&
verification.CommittingUser.ID == verification.SigningUser.ID) ||
(verification.SigningUser.ID == 0 && verification.CommittingUser.ID == 0 &&
verification.SigningUser.Email == verification.CommittingUser.Email) {
verification.TrustStatus = "trusted"
} }
return
} }
// Now we drop to the more nuanced trust models...
verification.TrustStatus = "trusted"
if verification.SigningUser.ID == 0 {
// This commit is signed by the default key - but this key is not assigned to a user in the DB.
// However in the CollaboratorCommitterTrustModel we cannot mark this as trusted
// unless the default key matches the email of a non-user.
if trustModel == CollaboratorCommitterTrustModel && (verification.CommittingUser.ID != 0 ||
verification.SigningUser.Email != verification.CommittingUser.Email) {
verification.TrustStatus = "untrusted"
}
return
}
var isMember bool
if keyMap != nil {
var has bool
isMember, has = (*keyMap)[verification.SigningKey.KeyID]
if !has {
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
(*keyMap)[verification.SigningKey.KeyID] = isMember
}
} else {
isMember, err = repository.IsOwnerMemberCollaborator(verification.SigningUser.ID)
}
if !isMember {
verification.TrustStatus = "untrusted"
if verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same
// This should be marked as questionable unless the signing user is a collaborator/team member etc.
verification.TrustStatus = "unmatched"
}
} else if trustModel == CollaboratorCommitterTrustModel && verification.CommittingUser.ID != verification.SigningUser.ID {
// The committing user and the signing user are not the same and our trustmodel states that they must match
verification.TrustStatus = "unmatched"
}
return return
} }

+ 2
- 0
models/migrations/migrations.go View File

@ -237,6 +237,8 @@ var migrations = []Migration{
NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic), NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic),
// v151 -> v152 // v151 -> v152
NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2), NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2),
// v152 -> v153
NewMigration("add TrustModel field to Repository", addTrustModelToRepository),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

+ 14
- 0
models/migrations/v152.go View File

@ -0,0 +1,14 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import "xorm.io/xorm"
func addTrustModelToRepository(x *xorm.Engine) error {
type Repository struct {
TrustModel int
}
return x.Sync2(new(Repository))
}

+ 2
- 0
models/notification.go View File

@ -354,6 +354,7 @@ func (n *Notification) APIFormat() *api.NotificationThread {
if n.Issue != nil { if n.Issue != nil {
result.Subject.Title = n.Issue.Title result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL() result.Subject.URL = n.Issue.APIURL()
result.Subject.State = n.Issue.State()
comment, err := n.Issue.GetLastComment() comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil { if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL() result.Subject.LatestCommentURL = comment.APIURL()
@ -364,6 +365,7 @@ func (n *Notification) APIFormat() *api.NotificationThread {
if n.Issue != nil { if n.Issue != nil {
result.Subject.Title = n.Issue.Title result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL() result.Subject.URL = n.Issue.APIURL()
result.Subject.State = n.Issue.State()
comment, err := n.Issue.GetLastComment() comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil { if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL() result.Subject.LatestCommentURL = comment.APIURL()

+ 25
- 25
models/pull_sign.go View File

@ -11,16 +11,16 @@ import (
) )
// SignMerge determines if we should sign a PR merge commit to the base repository // SignMerge determines if we should sign a PR merge commit to the base repository
func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, error) {
func (pr *PullRequest) SignMerge(u *User, tmpBasePath, baseCommit, headCommit string) (bool, string, *git.Signature, error) {
if err := pr.LoadBaseRepo(); err != nil { if err := pr.LoadBaseRepo(); err != nil {
log.Error("Unable to get Base Repo for pull request") log.Error("Unable to get Base Repo for pull request")
return false, "", err
return false, "", nil, err
} }
repo := pr.BaseRepo repo := pr.BaseRepo
signingKey := signingKey(repo.RepoPath())
signingKey, signer := SigningKey(repo.RepoPath())
if signingKey == "" { if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
} }
rules := signingModeFromStrings(setting.Repository.Signing.Merges) rules := signingModeFromStrings(setting.Repository.Signing.Merges)
@ -31,101 +31,101 @@ Loop:
for _, rule := range rules { for _, rule := range rules {
switch rule { switch rule {
case never: case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{}) keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if len(keys) == 0 { if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
} }
case approved: case approved:
protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch) protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if protectedBranch == nil { if protectedBranch == nil {
return false, "", &ErrWontSign{approved}
return false, "", nil, &ErrWontSign{approved}
} }
if protectedBranch.GetGrantedApprovalsCount(pr) < 1 { if protectedBranch.GetGrantedApprovalsCount(pr) < 1 {
return false, "", &ErrWontSign{approved}
return false, "", nil, &ErrWontSign{approved}
} }
case baseSigned: case baseSigned:
if gitRepo == nil { if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath) gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
defer gitRepo.Close() defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(baseCommit) commit, err := gitRepo.GetCommit(baseCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{baseSigned}
return false, "", nil, &ErrWontSign{baseSigned}
} }
case headSigned: case headSigned:
if gitRepo == nil { if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath) gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
defer gitRepo.Close() defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(headCommit) commit, err := gitRepo.GetCommit(headCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{headSigned}
return false, "", nil, &ErrWontSign{headSigned}
} }
case commitsSigned: case commitsSigned:
if gitRepo == nil { if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath) gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
defer gitRepo.Close() defer gitRepo.Close()
} }
commit, err := gitRepo.GetCommit(headCommit) commit, err := gitRepo.GetCommit(headCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{commitsSigned}
return false, "", nil, &ErrWontSign{commitsSigned}
} }
// need to work out merge-base // need to work out merge-base
mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit) mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit) commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
for e := commitList.Front(); e != nil; e = e.Next() { for e := commitList.Front(); e != nil; e = e.Next() {
commit = e.Value.(*git.Commit) commit = e.Value.(*git.Commit)
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{commitsSigned}
return false, "", nil, &ErrWontSign{commitsSigned}
} }
} }
} }
} }
return true, signingKey, nil
return true, signingKey, signer, nil
} }

+ 56
- 0
models/repo.go View File

@ -143,6 +143,47 @@ const (
RepositoryBeingMigrated // repository is migrating RepositoryBeingMigrated // repository is migrating
) )
// TrustModelType defines the types of trust model for this repository
type TrustModelType int
// kinds of TrustModel
const (
DefaultTrustModel TrustModelType = iota // default trust model
CommitterTrustModel
CollaboratorTrustModel
CollaboratorCommitterTrustModel
)
// String converts a TrustModelType to a string
func (t TrustModelType) String() string {
switch t {
case DefaultTrustModel:
return "default"
case CommitterTrustModel:
return "committer"
case CollaboratorTrustModel:
return "collaborator"
case CollaboratorCommitterTrustModel:
return "collaboratorcommitter"
}
return "default"
}
// ToTrustModel converts a string to a TrustModelType
func ToTrustModel(model string) TrustModelType {
switch strings.ToLower(strings.TrimSpace(model)) {
case "default":
return DefaultTrustModel
case "collaborator":
return CollaboratorTrustModel
case "committer":
return CommitterTrustModel
case "collaboratorcommitter":
return CollaboratorCommitterTrustModel
}
return DefaultTrustModel
}
// Repository represents a git repository. // Repository represents a git repository.
type Repository struct { type Repository struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -198,6 +239,8 @@ type Repository struct {
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"` CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"` Topics []string `xorm:"TEXT JSON"`
TrustModel TrustModelType
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols // Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string `xorm:"VARCHAR(64)"` Avatar string `xorm:"VARCHAR(64)"`
@ -1038,6 +1081,7 @@ type CreateRepoOptions struct {
IsMirror bool IsMirror bool
AutoInit bool AutoInit bool
Status RepositoryStatus Status RepositoryStatus
TrustModel TrustModelType
} }
// GetRepoInitFile returns repository init files // GetRepoInitFile returns repository init files
@ -2383,6 +2427,18 @@ func UpdateRepositoryCols(repo *Repository, cols ...string) error {
return updateRepositoryCols(x, repo, cols...) return updateRepositoryCols(x, repo, cols...)
} }
// GetTrustModel will get the TrustModel for the repo or the default trust model
func (repo *Repository) GetTrustModel() TrustModelType {
trustModel := repo.TrustModel
if trustModel == DefaultTrustModel {
trustModel = ToTrustModel(setting.Repository.Signing.DefaultTrustModel)
if trustModel == DefaultTrustModel {
return CollaboratorTrustModel
}
}
return trustModel
}
// DoctorUserStarNum recalculate Stars number for all user // DoctorUserStarNum recalculate Stars number for all user
func DoctorUserStarNum() (err error) { func DoctorUserStarNum() (err error) {
const batchSize = 100 const batchSize = 100

+ 51
- 42
models/repo_sign.go View File

@ -31,7 +31,7 @@ const (
func signingModeFromStrings(modeStrings []string) []signingMode { func signingModeFromStrings(modeStrings []string) []signingMode {
returnable := make([]signingMode, 0, len(modeStrings)) returnable := make([]signingMode, 0, len(modeStrings))
for _, mode := range modeStrings { for _, mode := range modeStrings {
signMode := signingMode(strings.ToLower(mode))
signMode := signingMode(strings.ToLower(strings.TrimSpace(mode)))
switch signMode { switch signMode {
case never: case never:
return []signingMode{never} return []signingMode{never}
@ -59,9 +59,10 @@ func signingModeFromStrings(modeStrings []string) []signingMode {
return returnable return returnable
} }
func signingKey(repoPath string) string {
// SigningKey returns the KeyID and git Signature for the repo
func SigningKey(repoPath string) (string, *git.Signature) {
if setting.Repository.Signing.SigningKey == "none" { if setting.Repository.Signing.SigningKey == "none" {
return ""
return "", nil
} }
if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" { if setting.Repository.Signing.SigningKey == "default" || setting.Repository.Signing.SigningKey == "" {
@ -69,19 +70,27 @@ func signingKey(repoPath string) string {
value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath) value, _ := git.NewCommand("config", "--get", "commit.gpgsign").RunInDir(repoPath)
sign, valid := git.ParseBool(strings.TrimSpace(value)) sign, valid := git.ParseBool(strings.TrimSpace(value))
if !sign || !valid { if !sign || !valid {
return ""
return "", nil
} }
signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath) signingKey, _ := git.NewCommand("config", "--get", "user.signingkey").RunInDir(repoPath)
return strings.TrimSpace(signingKey)
signingName, _ := git.NewCommand("config", "--get", "user.name").RunInDir(repoPath)
signingEmail, _ := git.NewCommand("config", "--get", "user.email").RunInDir(repoPath)
return strings.TrimSpace(signingKey), &git.Signature{
Name: strings.TrimSpace(signingName),
Email: strings.TrimSpace(signingEmail),
}
} }
return setting.Repository.Signing.SigningKey
return setting.Repository.Signing.SigningKey, &git.Signature{
Name: setting.Repository.Signing.SigningName,
Email: setting.Repository.Signing.SigningEmail,
}
} }
// PublicSigningKey gets the public signing key within a provided repository directory // PublicSigningKey gets the public signing key within a provided repository directory
func PublicSigningKey(repoPath string) (string, error) { func PublicSigningKey(repoPath string) (string, error) {
signingKey := signingKey(repoPath)
signingKey, _ := SigningKey(repoPath)
if signingKey == "" { if signingKey == "" {
return "", nil return "", nil
} }
@ -96,143 +105,143 @@ func PublicSigningKey(repoPath string) (string, error) {
} }
// SignInitialCommit determines if we should sign the initial commit to this repository // SignInitialCommit determines if we should sign the initial commit to this repository
func SignInitialCommit(repoPath string, u *User) (bool, string, error) {
func SignInitialCommit(repoPath string, u *User) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit) rules := signingModeFromStrings(setting.Repository.Signing.InitialCommit)
signingKey := signingKey(repoPath)
signingKey, sig := SigningKey(repoPath)
if signingKey == "" { if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
} }
Loop: Loop:
for _, rule := range rules { for _, rule := range rules {
switch rule { switch rule {
case never: case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{}) keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if len(keys) == 0 { if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
} }
} }
} }
return true, signingKey, nil
return true, signingKey, sig, nil
} }
// SignWikiCommit determines if we should sign the commits to this repository wiki // SignWikiCommit determines if we should sign the commits to this repository wiki
func (repo *Repository) SignWikiCommit(u *User) (bool, string, error) {
func (repo *Repository) SignWikiCommit(u *User) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.Wiki) rules := signingModeFromStrings(setting.Repository.Signing.Wiki)
signingKey := signingKey(repo.WikiPath())
signingKey, sig := SigningKey(repo.WikiPath())
if signingKey == "" { if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
} }
Loop: Loop:
for _, rule := range rules { for _, rule := range rules {
switch rule { switch rule {
case never: case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{}) keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if len(keys) == 0 { if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
} }
case parentSigned: case parentSigned:
gitRepo, err := git.OpenRepository(repo.WikiPath()) gitRepo, err := git.OpenRepository(repo.WikiPath())
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
defer gitRepo.Close() defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD") commit, err := gitRepo.GetCommit("HEAD")
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if commit.Signature == nil { if commit.Signature == nil {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
} }
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
} }
} }
} }
return true, signingKey, nil
return true, signingKey, sig, nil
} }
// SignCRUDAction determines if we should sign a CRUD commit to this repository // SignCRUDAction determines if we should sign a CRUD commit to this repository
func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, error) {
func (repo *Repository) SignCRUDAction(u *User, tmpBasePath, parentCommit string) (bool, string, *git.Signature, error) {
rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions) rules := signingModeFromStrings(setting.Repository.Signing.CRUDActions)
signingKey := signingKey(repo.RepoPath())
signingKey, sig := SigningKey(repo.RepoPath())
if signingKey == "" { if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
} }
Loop: Loop:
for _, rule := range rules { for _, rule := range rules {
switch rule { switch rule {
case never: case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always: case always:
break Loop break Loop
case pubkey: case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{}) keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if len(keys) == 0 { if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
} }
case twofa: case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID) twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) { if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
} }
if twofaModel == nil { if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
} }
case parentSigned: case parentSigned:
gitRepo, err := git.OpenRepository(tmpBasePath) gitRepo, err := git.OpenRepository(tmpBasePath)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
defer gitRepo.Close() defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit) commit, err := gitRepo.GetCommit(parentCommit)
if err != nil { if err != nil {
return false, "", err
return false, "", nil, err
} }
if commit.Signature == nil { if commit.Signature == nil {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
} }
verification := ParseCommitWithSignature(commit) verification := ParseCommitWithSignature(commit)
if !verification.Verified { if !verification.Verified {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
} }
} }
} }
return true, signingKey, nil
return true, signingKey, sig, nil
} }

+ 4
- 0
modules/auth/repo_form.go View File

@ -45,6 +45,7 @@ type CreateRepoForm struct {
Webhooks bool Webhooks bool
Avatar bool Avatar bool
Labels bool Labels bool
TrustModel string
} }
// Validate validates the fields // Validate validates the fields
@ -142,6 +143,9 @@ type RepoSettingForm struct {
EnableIssueDependencies bool EnableIssueDependencies bool
IsArchived bool IsArchived bool
// Signing Settings
TrustModel string
// Admin settings // Admin settings
EnableHealthCheck bool EnableHealthCheck bool
EnableCloseIssuesViaCommitInAnyBranch bool EnableCloseIssuesViaCommitInAnyBranch bool

+ 1
- 1
modules/context/repo.go View File

@ -114,7 +114,7 @@ func (r *Repository) CanCommitToBranch(doer *models.User) (CanCommitToBranchResu
requireSigned = protectedBranch.RequireSignedCommits requireSigned = protectedBranch.RequireSignedCommits
} }
sign, keyID, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
sign, keyID, _, err := r.Repository.SignCRUDAction(doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName)
canCommit := r.CanEnableEditor() && userCanPush canCommit := r.CanEnableEditor() && userCanPush
if requireSigned { if requireSigned {

+ 5
- 5
modules/git/repo_tree.go View File

@ -62,7 +62,7 @@ type CommitTreeOpts struct {
} }
// CommitTree creates a commit from a given tree id for the user with provided message // CommitTree creates a commit from a given tree id for the user with provided message
func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
func (repo *Repository) CommitTree(authorspan> *Signature, committer *Signature, tree *Tree, opts CommitTreeOpts) (SHA1, error) {
err := LoadGitVersion() err := LoadGitVersion()
if err != nil { if err != nil {
return SHA1{}, err return SHA1{}, err
@ -72,11 +72,11 @@ func (repo *Repository) CommitTree(sig *Signature, tree *Tree, opts CommitTreeOp
// Because this may call hooks we should pass in the environment // Because this may call hooks we should pass in the environment
env := append(os.Environ(), env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_NAME="+author.Name,
"GIT_AUTHOR_EMAIL="+author.Email,
"GIT_AUTHOR_DATE="+commitTimeStr, "GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_NAME="+committer.Name,
"GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr, "GIT_COMMITTER_DATE="+commitTimeStr,
) )
cmd := NewCommand("commit-tree", tree.ID.String()) cmd := NewCommand("commit-tree", tree.ID.String())

+ 1
- 1
modules/migrations/gitea_test.go View File

@ -28,7 +28,7 @@ func TestGiteaUploadRepo(t *testing.T) {
user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
var ( var (
downloader = NewGithubDownloaderV3(context.Background(), "", "", "", "go-xorm", "builder")
downloader = NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", "", "go-xorm", "builder")
repoName = "builder-" + time.Now().Format("2006-01-02-15-04-05") repoName = "builder-" + time.Now().Format("2006-01-02-15-04-05")
uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName) uploader = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
) )

+ 6
- 2
modules/migrations/github.go View File

@ -47,13 +47,14 @@ func (f *GithubDownloaderV3Factory) New(ctx context.Context, opts base.MigrateOp
return nil, err return nil, err
} }
baseURL := u.Scheme + "://" + u.Host
fields := strings.Split(u.Path, "/") fields := strings.Split(u.Path, "/")
oldOwner := fields[1] oldOwner := fields[1]
oldName := strings.TrimSuffix(fields[2], ".git") oldName := strings.TrimSuffix(fields[2], ".git")
log.Trace("Create github downloader: %s/%s", oldOwner, oldName) log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
return NewGithubDownloaderV3(ctx, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
return NewGithubDownloaderV3(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
} }
// GitServiceType returns the type of git service // GitServiceType returns the type of git service
@ -74,7 +75,7 @@ type GithubDownloaderV3 struct {
} }
// NewGithubDownloaderV3 creates a github Downloader via github v3 API // NewGithubDownloaderV3 creates a github Downloader via github v3 API
func NewGithubDownloaderV3(ctx context.Context, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
func NewGithubDownloaderV3(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
var downloader = GithubDownloaderV3{ var downloader = GithubDownloaderV3{
userName: userName, userName: userName,
password: password, password: password,
@ -98,6 +99,9 @@ func NewGithubDownloaderV3(ctx context.Context, userName, password, token, repoO
client = oauth2.NewClient(downloader.ctx, ts) client = oauth2.NewClient(downloader.ctx, ts)
} }
downloader.client = github.NewClient(client) downloader.client = github.NewClient(client)
if baseURL != "https://github.com" {
downloader.client, _ = github.NewEnterpriseClient(baseURL, baseURL, client)
}
return &downloader return &downloader
} }

+ 1
- 1
modules/migrations/github_test.go View File

@ -65,7 +65,7 @@ func assertLabelEqual(t *testing.T, name, color, description string, label *base
func TestGitHubDownloadRepo(t *testing.T) { func TestGitHubDownloadRepo(t *testing.T) {
GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in // GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in //
downloader := NewGithubDownloaderV3(context.Background(), "", "", os.Getenv("GITHUB_READ_TOKEN"), "go-gitea", "test_repo")
downloader := NewGithubDownloaderV3(context.Background(), "https://github.com", "", "", os.Getenv("GITHUB_READ_TOKEN"), "go-gitea", "test_repo")
err := downloader.RefreshRate() err := downloader.RefreshRate()
assert.NoError(t, err) assert.NoError(t, err)

+ 1
- 1
modules/repofiles/delete.go View File

@ -67,7 +67,7 @@ func DeleteRepoFile(repo *models.Repository, doer *models.User, opts *DeleteRepo
} }
} }
if protectedBranch.RequireSignedCommits { if protectedBranch.RequireSignedCommits {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil { if err != nil {
if !models.IsErrWontSign(err) { if !models.IsErrWontSign(err) {
return nil, err return nil, err

+ 19
- 3
modules/repofiles/temp_repo.go View File

@ -204,8 +204,6 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
"GIT_AUTHOR_NAME="+authorSig.Name, "GIT_AUTHOR_NAME="+authorSig.Name,
"GIT_AUTHOR_EMAIL="+authorSig.Email, "GIT_AUTHOR_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339), "GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
"GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339), "GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
) )
@ -217,14 +215,32 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
// Determine if we should sign // Determine if we should sign
if git.CheckGitVersionConstraint(">= 1.7.9") == nil { if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
sign, keyID, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
sign, keyID, signer, _ := t.repo.SignCRUDAction(author, t.basePath, "HEAD")
if sign { if sign {
args = append(args, "-S"+keyID) args = append(args, "-S"+keyID)
if t.repo.GetTrustModel() == models.CommitterTrustModel || t.repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
if committerSig.Name != authorSig.Name || committerSig.Email != authorSig.Email {
// Add trailers
_, _ = messageBytes.WriteString("\n")
_, _ = messageBytes.WriteString("Co-Authored-By: ")
_, _ = messageBytes.WriteString(committerSig.String())
_, _ = messageBytes.WriteString("\n")
_, _ = messageBytes.WriteString("Co-Committed-By: ")
_, _ = messageBytes.WriteString(committerSig.String())
_, _ = messageBytes.WriteString("\n")
}
committerSig = signer
}
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil { } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
args = append(args, "--no-gpg-sign") args = append(args, "--no-gpg-sign")
} }
} }
env = append(env,
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
)
stdout := new(bytes.Buffer) stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil { if err := git.NewCommand(args...).RunInDirTimeoutEnvFullPipeline(env, -1, t.basePath, stdout, stderr, messageBytes); err != nil {

+ 1
- 1
modules/repofiles/update.go View File

@ -161,7 +161,7 @@ func CreateOrUpdateRepoFile(repo *models.Repository, doer *models.User, opts *Up
} }
} }
if protectedBranch.RequireSignedCommits { if protectedBranch.RequireSignedCommits {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil { if err != nil {
if !models.IsErrWontSign(err) { if !models.IsErrWontSign(err) {
return nil, err return nil, err

+ 1
- 0
modules/repository/create.go View File

@ -41,6 +41,7 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch,
Status: opts.Status, Status: opts.Status,
IsEmpty: !opts.AutoInit, IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel,
} }
err = models.WithTx(func(ctx models.DBContext) error { err = models.WithTx(func(ctx models.DBContext) error {

+ 1
- 0
modules/repository/generate.go View File

@ -243,6 +243,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template
IsEmpty: !opts.GitContent || templateRepo.IsEmpty, IsEmpty: !opts.GitContent || templateRepo.IsEmpty,
IsFsckEnabled: templateRepo.IsFsckEnabled, IsFsckEnabled: templateRepo.IsFsckEnabled,
TemplateID: templateRepo.ID, TemplateID: templateRepo.ID,
TrustModel: templateRepo.TrustModel,
} }
if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil { if err = models.CreateRepository(ctx, doer, owner, generateRepo); err != nil {

+ 14
- 3
modules/repository/init.go View File

@ -109,10 +109,10 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def
"GIT_AUTHOR_NAME="+sig.Name, "GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email, "GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr, "GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr, "GIT_COMMITTER_DATE="+commitTimeStr,
) )
committerName := sig.Name
committerEmail := sig.Email
if stdout, err := git.NewCommand("add", "--all"). if stdout, err := git.NewCommand("add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)). SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
@ -132,14 +132,25 @@ func initRepoCommit(tmpPath string, repo *models.Repository, u *models.User, def
} }
if git.CheckGitVersionConstraint(">= 1.7.9") == nil { if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
sign, keyID, _ := models.SignInitialCommit(tmpPath, u)
sign, keyID, signer, _ := models.SignInitialCommit(tmpPath, u)
if sign { if sign {
args = append(args, "-S"+keyID) args = append(args, "-S"+keyID)
if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
// need to set the committer to the KeyID owner
committerName = signer.Name
committerEmail = signer.Email
}
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil { } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
args = append(args, "--no-gpg-sign") args = append(args, "--no-gpg-sign")
} }
} }
env = append(env,
"GIT_COMMITTER_NAME="+committerName,
"GIT_COMMITTER_EMAIL="+committerEmail,
)
if stdout, err := git.NewCommand(args...). if stdout, err := git.NewCommand(args...).
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)). SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunInDirWithEnv(tmpPath, env); err != nil { RunInDirWithEnv(tmpPath, env); err != nil {

+ 31
- 21
modules/setting/repository.go View File

@ -83,13 +83,14 @@ var (
} `ini:"repository.issue"` } `ini:"repository.issue"`
Signing struct { Signing struct {
SigningKey string
SigningName string
SigningEmail string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
SigningKey string
SigningName string
SigningEmail string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
DefaultTrustModel string
} `ini:"repository.signing"` } `ini:"repository.signing"`
}{ }{
DetectedCharsetsOrder: []string{ DetectedCharsetsOrder: []string{
@ -209,21 +210,23 @@ var (
// Signing settings // Signing settings
Signing: struct { Signing: struct {
SigningKey string
SigningName string
SigningEmail string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
SigningKey string
SigningName string
SigningEmail string
InitialCommit []string
CRUDActions []string `ini:"CRUD_ACTIONS"`
Merges []string
Wiki []string
DefaultTrustModel string
}{ }{
SigningKey: "default",
SigningName: "",
SigningEmail: "",
InitialCommit: []string{"always"},
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
Wiki: []string{"never"},
SigningKey: "default",
SigningName: "",
SigningEmail: "",
InitialCommit: []string{"always"},
CRUDActions: []string{"pubkey", "twofa", "parentsigned"},
Merges: []string{"pubkey", "twofa", "basesigned", "commitssigned"},
Wiki: []string{"never"},
DefaultTrustModel: "collaborator",
}, },
} }
RepoRootPath string RepoRootPath string
@ -268,6 +271,13 @@ func newRepository() {
log.Fatal("Failed to map Repository.PullRequest settings: %v", err) log.Fatal("Failed to map Repository.PullRequest settings: %v", err)
} }
// Handle default trustmodel settings
Repository.Signing.DefaultTrustModel = strings.ToLower(strings.TrimSpace(Repository.Signing.DefaultTrustModel))
if Repository.Signing.DefaultTrustModel == "default" {
Repository.Signing.DefaultTrustModel = "collaborator"
}
// Handle preferred charset orders
preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder)) preferred := make([]string, 0, len(Repository.DetectedCharsetsOrder))
for _, charset := range Repository.DetectedCharsetsOrder { for _, charset := range Repository.DetectedCharsetsOrder {
canonicalCharset := strings.ToLower(strings.TrimSpace(charset)) canonicalCharset := strings.ToLower(strings.TrimSpace(charset))

+ 5
- 4
modules/structs/notifications.go View File

@ -21,10 +21,11 @@ type NotificationThread struct {
// NotificationSubject contains the notification subject (Issue/Pull/Commit) // NotificationSubject contains the notification subject (Issue/Pull/Commit)
type NotificationSubject struct { type NotificationSubject struct {
Title string `json:"title"`
URL string `json:"url"`
LatestCommentURL string `json:"latest_comment_url"`
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
Title string `json:"title"`
URL string `json:"url"`
LatestCommentURL string `json:"latest_comment_url"`
Type string `json:"type" binding:"In(Issue,Pull,Commit)"`
State StateType `json:"state"`
} }
// NotificationCount number of unread notifications // NotificationCount number of unread notifications

+ 3
- 0
modules/structs/repo.go View File

@ -117,6 +117,9 @@ type CreateRepoOption struct {
Readme string `json:"readme"` Readme string `json:"readme"`
// DefaultBranch of the repository (used when initializes and in template) // DefaultBranch of the repository (used when initializes and in template)
DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"` DefaultBranch string `json:"default_branch" binding:"GitRefName;MaxSize(100)"`
// TrustModel of the repository
// enum: default,collaborator,committer,collaboratorcommitter
TrustModel string `json:"trust_model"`
} }
// EditRepoOption options when editing a repository's properties // EditRepoOption options when editing a repository's properties

+ 17
- 0
options/locale/locale_de-DE.ini View File

@ -939,6 +939,8 @@ issues.new.no_assignees=Niemand zuständig
issues.new.no_reviewers=Keine Reviewer issues.new.no_reviewers=Keine Reviewer
issues.new.add_reviewer_title=Überprüfung anfordern issues.new.add_reviewer_title=Überprüfung anfordern
issues.choose.get_started=Los geht's issues.choose.get_started=Los geht's
issues.choose.blank=Standard
issues.choose.blank_about=Erstelle einen Issue aus dem Standardtemplate.
issues.no_ref=Keine Branch/Tag angegeben issues.no_ref=Keine Branch/Tag angegeben
issues.create=Issue erstellen issues.create=Issue erstellen
issues.new_label=Neues Label issues.new_label=Neues Label
@ -1461,6 +1463,19 @@ settings.transfer_desc=Übertrage dieses Repository auf einen anderen Benutzer o
settings.transfer_notices_1=– Du wirst keinen Zugriff mehr haben, wenn der neue Besitzer ein individueller Benutzer ist. settings.transfer_notices_1=– Du wirst keinen Zugriff mehr haben, wenn der neue Besitzer ein individueller Benutzer ist.
settings.transfer_notices_2=– Du wirst weiterhin Zugriff haben, wenn der neue Besitzer eine Organisation ist und du einer der Besitzer bist. settings.transfer_notices_2=– Du wirst weiterhin Zugriff haben, wenn der neue Besitzer eine Organisation ist und du einer der Besitzer bist.
settings.transfer_form_title=Gib den Repository-Namen zur Bestätigung ein: settings.transfer_form_title=Gib den Repository-Namen zur Bestätigung ein:
settings.signing_settings=Signaturüberprüfungseinstellungen
settings.trust_model=Signaturvertrauensmodell
settings.trust_model.default=Standardvertrauensmodell
settings.trust_model.default.desc=Verwende das Standardvertrauensmodell für diese Installation.
settings.trust_model.collaborator=Mitarbeiter
settings.trust_model.collaborator.long=Mitarbeiter: Vertraue Signaturen von Mitarbeitern
settings.trust_model.collaborator.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert - ( egal ob sie mit dem Committer übereinstimmen oder nicht). Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, unabhängig ob die Signatur mit dem Committer übereinstimmt oder nicht.
settings.trust_model.committer=Committer
settings.trust_model.committer.long=Committer: Vertraue Signaturen, die zu Committern passen (Dies stimmt mit GitHub überein und zwingt signierte Commits von Gitea dazu, Gitea als Committer zu haben)
settings.trust_model.committer.desc=Gültige Signaturen werden nur dann als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen, andernfalls werden sie als "nicht übereinstimmend" markiert. Dies zwingt Gitea dazu, der Committer bei signierten Commits zu sein, wobei der eigentliche Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit markiert ist. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen.
settings.trust_model.collaboratorcommitter=Mitarbeiter+Committer
settings.trust_model.collaboratorcommitter.long=Mitarbeiter+Committer: Signaturen der Mitarbeiter vertrauen die mit dem Committer übereinstimmen
settings.trust_model.collaboratorcommitter.desc=Gültige Signaturen von Mitarbeitern dieses Projekts werden als "vertrauenswürdig" markiert, wenn sie mit dem Committer übereinstimmen. Andernfalls werden gültige Signaturen als "nicht vertrauenswürdig" markiert, wenn die Signatur mit dem Committer übereinstimmt als "nicht übereinstimmend". Dies zwingt Gitea als Committer bei signierten Commits mit dem tatsächlichen Committer als Co-Authored-By: und Co-Committed-By: Trailer im Commit. Der Standard-Gitea-Schlüssel muss mit einem Benutzer in der Datenbank übereinstimmen
settings.wiki_delete=Wiki-Daten löschen settings.wiki_delete=Wiki-Daten löschen
settings.wiki_delete_desc=Das Löschen von Wiki-Daten kann nicht rückgängig gemacht werden. Bitte sei vorsichtig. settings.wiki_delete_desc=Das Löschen von Wiki-Daten kann nicht rückgängig gemacht werden. Bitte sei vorsichtig.
settings.wiki_delete_notices_1=– Dies löscht und deaktiviert das Wiki für %s. settings.wiki_delete_notices_1=– Dies löscht und deaktiviert das Wiki für %s.
@ -2103,7 +2118,9 @@ auths.admin_filter=Admin-Filter
auths.restricted_filter=Eingeschränkte Filter auths.restricted_filter=Eingeschränkte Filter
auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Stern ('*'), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen. auths.restricted_filter_helper=Leer lassen, um keine Benutzer als eingeschränkt festzulegen. Verwende einen Stern ('*'), um alle Benutzer, die nicht dem Admin-Filter entsprechen, als eingeschränkt zu setzen.
auths.verify_group_membership=Gruppenmitgliedschaft in LDAP überprüfen auths.verify_group_membership=Gruppenmitgliedschaft in LDAP überprüfen
auths.group_search_base=Gruppensuche Basisdomainname
auths.valid_groups_filter=Gültiger Gruppenfilter auths.valid_groups_filter=Gültiger Gruppenfilter
auths.group_attribute_list_users=Gruppenattribut, welches die die Benutzerliste enthält
auths.user_attribute_in_group=Benutzerattribut in der Gruppenliste auths.user_attribute_in_group=Benutzerattribut in der Gruppenliste
auths.ms_ad_sa=MS-AD-Suchattribute auths.ms_ad_sa=MS-AD-Suchattribute
auths.smtp_auth=SMTP-Authentifizierungstyp auths.smtp_auth=SMTP-Authentifizierungstyp

+ 13
- 0
options/locale/locale_en-US.ini View File

@ -1464,6 +1464,19 @@ settings.transfer_desc = Transfer this repository to a user or to an organizatio
settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user. settings.transfer_notices_1 = - You will lose access to the repository if you transfer it to an individual user.
settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own. settings.transfer_notices_2 = - You will keep access to the repository if you transfer it to an organization that you (co-)own.
settings.transfer_form_title = Enter the repository name as confirmation: settings.transfer_form_title = Enter the repository name as confirmation:
settings.signing_settings = Signing Verification Settings
settings.trust_model = Signature Trust Model
settings.trust_model.default = Default Trust Model
settings.trust_model.default.desc= Use the default repository trust model for this installation.
settings.trust_model.collaborator = Collaborator
settings.trust_model.collaborator.long = Collaborator: Trust signatures by collaborators
settings.trust_model.collaborator.desc = Valid signatures by collaborators of this repository will be marked "trusted" - (whether they match the committer or not). Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" if not.
settings.trust_model.committer = Committer
settings.trust_model.committer.long = Committer: Trust signatures that match committers (This matches GitHub and will force Gitea signed commits to have Gitea as the committer)
settings.trust_model.committer.desc = Valid signatures will only be marked "trusted" if they match the committer, otherwise they will be marked "unmatched". This will force Gitea to be the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database.
settings.trust_model.collaboratorcommitter = Collaborator+Committer
settings.trust_model.collaboratorcommitter.long = Collaborator+Committer: Trust signatures by collaborators which match the committer
settings.trust_model.collaboratorcommitter.desc = Valid signatures by collaborators of this repository will be marked "trusted" if they match the committer. Otherwise, valid signatures will be marked "untrusted" if the signature matches the committer and "unmatched" otherwise. This will force Gitea to be marked as the committer on signed commits with the actual committer marked as Co-Authored-By: and Co-Committed-By: trailer in the commit. The default Gitea key must match a User in the database,
settings.wiki_delete = Delete Wiki Data settings.wiki_delete = Delete Wiki Data
settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone. settings.wiki_delete_desc = Deleting repository wiki data is permanent and cannot be undone.
settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s. settings.wiki_delete_notices_1 = - This will permanently delete and disable the repository wiki for %s.

+ 28
- 17
options/locale/locale_tr-TR.ini View File

@ -10,7 +10,7 @@ link_account=Bağlantı hesabı
register=Üye Ol register=Üye Ol
website=Web sitesi website=Web sitesi
version=Sürüm version=Sürüm
powered_by=%s tarafından desteklenmektedir
powered_by=%s tarafından desteklenen
page=Sayfa page=Sayfa
template=Şablon template=Şablon
language=Dil language=Dil
@ -771,7 +771,7 @@ branch=Dal
tree=Ağaç tree=Ağaç
clear_ref='Geçerli referansı temizle' clear_ref='Geçerli referansı temizle'
filter_branch_and_tag=Dal veya biçim imini filtrele filter_branch_and_tag=Dal veya biçim imini filtrele
branches=Dallar
branches=Dal
tags=Biçim İmleri tags=Biçim İmleri
issues=Konular issues=Konular
pulls=Değişiklik İstekleri pulls=Değişiklik İstekleri
@ -858,7 +858,7 @@ editor.user_no_push_to_branch=Kullanıcı dala gönderemez
editor.require_signed_commit=Dal imzalı bir işleme gerektirir editor.require_signed_commit=Dal imzalı bir işleme gerektirir
commits.desc=Kaynak kodu değişiklik geçmişine göz atın. commits.desc=Kaynak kodu değişiklik geçmişine göz atın.
commits.commits=İşlemeler
commits.commits=İşleme
commits.no_commits=Ortak bir işleme yok. '%s' ve '%s' tamamen farklı geçmişlere sahip. commits.no_commits=Ortak bir işleme yok. '%s' ve '%s' tamamen farklı geçmişlere sahip.
commits.search=İşlemeleri ara… commits.search=İşlemeleri ara…
commits.search.tooltip=Anahtar kelimeleri "yazar:", "yorumcu:", "sonra:" veya "önce:", örneğin; "eski haline yazan: Alice önce: 2019-04-01" önekleyebilirsiniz. commits.search.tooltip=Anahtar kelimeleri "yazar:", "yorumcu:", "sonra:" veya "önce:", örneğin; "eski haline yazan: Alice önce: 2019-04-01" önekleyebilirsiniz.
@ -955,18 +955,18 @@ issues.label_templates.fail_to_load_file=Etiket şablon dosyası yüklemesi baş
issues.add_label_at=<div class="ui label" style="color: %s\; background-color: %s">%s</div> %s etiketini ekledi issues.add_label_at=<div class="ui label" style="color: %s\; background-color: %s">%s</div> %s etiketini ekledi
issues.remove_label_at=<div class="ui label" style="color: %s\; background-color: %s">%s</div> %s etiketini kaldırdı issues.remove_label_at=<div class="ui label" style="color: %s\; background-color: %s">%s</div> %s etiketini kaldırdı
issues.add_milestone_at=`%[2]s <b>%[1]s</b> kilometre taşına ekledi` issues.add_milestone_at=`%[2]s <b>%[1]s</b> kilometre taşına ekledi`
issues.add_project_at=`bunu %s <b>%s</b> projesine ekledi`
issues.add_project_at=`bunu %s projesine <b>%s</b> ekledi`
issues.change_milestone_at=`%s kilometre taşını <b>%s</b> iken <b>%s</b> olarak değiştirdi` issues.change_milestone_at=`%s kilometre taşını <b>%s</b> iken <b>%s</b> olarak değiştirdi`
issues.change_project_at=`%s <b>%s</b> olan projeyi <b>%s</b> olarak değiştirdi issues.change_project_at=`%s <b>%s</b> olan projeyi <b>%s</b> olarak değiştirdi
issues.remove_milestone_at=`%[2]s <b>%[1]s</b> kilometre taşından kaldırdı` issues.remove_milestone_at=`%[2]s <b>%[1]s</b> kilometre taşından kaldırdı`
issues.remove_project_at=`bunu %s <b>%s</b> projesinden kaldırdı`
issues.remove_project_at=`bunu %s projesinden <b>%s</b> kaldırdı`
issues.deleted_milestone=`(silindi)` issues.deleted_milestone=`(silindi)`
issues.deleted_project=`(silindi)` issues.deleted_project=`(silindi)`
issues.self_assign_at=`%s kendini atadı` issues.self_assign_at=`%s kendini atadı`
issues.add_assignee_at=`%[2]s <b>%[1]s</b> tarafından atandı` issues.add_assignee_at=`%[2]s <b>%[1]s</b> tarafından atandı`
issues.remove_assignee_at=`ataması %[2]s <b>%[1]s</b> tarafından kaldırıldı` issues.remove_assignee_at=`ataması %[2]s <b>%[1]s</b> tarafından kaldırıldı`
issues.remove_self_assignment=`atamalarını kaldırdı %s` issues.remove_self_assignment=`atamalarını kaldırdı %s`
issues.change_title_at=`%s başlığı <b><strike>%s</strike></b> iken <b>%s</b> olarak değiştirdi`
issues.change_title_at=`başlığı <b><strike>%s</strike></b> iken %s olarak <b>%s</b> değiştirdi`
issues.delete_branch_at=`<b>%s</b> dalı silindi %s` issues.delete_branch_at=`<b>%s</b> dalı silindi %s`
issues.open_tab=%d açık issues.open_tab=%d açık
issues.close_tab=%d kapanmış issues.close_tab=%d kapanmış
@ -1189,7 +1189,7 @@ pulls.title_desc=%[2]s içindeki %[1]d işlemeyi pulls.merged_title_desc=%[4]s <code>%[2]s</code> içindeki %[1]d işleme <code>%[3]s</code> ile birleştirdi pulls.merged_title_desc=%[4]s <code>%[2]s</code> içindeki %[1]d işleme <code>%[3]s</code> ile birleştirdi
pulls.change_target_branch_at='hedef dal <b>%s</b> adresinden <b>%s</b>%s adresine değiştirildi' pulls.change_target_branch_at='hedef dal <b>%s</b> adresinden <b>%s</b>%s adresine değiştirildi'
pulls.tab_conversation=Sohbet pulls.tab_conversation=Sohbet
pulls.tab_commits=İşlemeler
pulls.tab_commits=İşleme
pulls.tab_files=Değiştirilen Dosyalar pulls.tab_files=Değiştirilen Dosyalar
pulls.reopen_to_merge=Lütfen birleştirme gerçekleştirmek için bu değişiklik isteğini yeniden açın. pulls.reopen_to_merge=Lütfen birleştirme gerçekleştirmek için bu değişiklik isteğini yeniden açın.
pulls.cant_reopen_deleted_branch=Dal silindiğinden bu değişiklik isteği yeniden açılamaz. pulls.cant_reopen_deleted_branch=Dal silindiğinden bu değişiklik isteği yeniden açılamaz.
@ -1339,7 +1339,7 @@ activity.active_prs_count_n=%d Aktif Çekme İsteği
activity.merged_prs_count_1=Birleştirilmiş Değişiklik İsteği activity.merged_prs_count_1=Birleştirilmiş Değişiklik İsteği
activity.merged_prs_count_n=Birleştirilmiş Çekme İsteği activity.merged_prs_count_n=Birleştirilmiş Çekme İsteği
activity.opened_prs_count_1=Önerilen Değişiklik İsteği activity.opened_prs_count_1=Önerilen Değişiklik İsteği
activity.opened_prs_count_n=Önerilen Değişiklik İstekleri
activity.opened_prs_count_n=Önerilen Değişiklik İsteği
activity.title.user_1=%d kullanıcı activity.title.user_1=%d kullanıcı
activity.title.user_n=%d kullanıcı activity.title.user_n=%d kullanıcı
activity.title.prs_1=%d Değişiklik isteği activity.title.prs_1=%d Değişiklik isteği
@ -1372,23 +1372,23 @@ activity.no_git_activity=Bu dönemde herhangi bir işleme yapılmamıştır.
activity.git_stats_exclude_merges=Birleştirmeler hariç, activity.git_stats_exclude_merges=Birleştirmeler hariç,
activity.git_stats_author_1=%d yazar activity.git_stats_author_1=%d yazar
activity.git_stats_author_n=%d yazar activity.git_stats_author_n=%d yazar
activity.git_stats_pushed_1=itti
activity.git_stats_pushed_n=itti
activity.git_stats_commit_1=%d işleme
activity.git_stats_commit_n=%d işleme
activity.git_stats_push_to_branch=%s 'e ve
activity.git_stats_push_to_all_branches=tüm dallara.
activity.git_stats_pushed_1=
activity.git_stats_pushed_n=
activity.git_stats_commit_1=%d işlemeyi
activity.git_stats_commit_n=%d işlemeyi
activity.git_stats_push_to_branch=%s dalına ve
activity.git_stats_push_to_all_branches=tüm dallara gönderdi.
activity.git_stats_on_default_branch=%s üzerinde, activity.git_stats_on_default_branch=%s üzerinde,
activity.git_stats_file_1=%d dosya activity.git_stats_file_1=%d dosya
activity.git_stats_file_n=%d dosya activity.git_stats_file_n=%d dosya
activity.git_stats_files_changed_1=değişti activity.git_stats_files_changed_1=değişti
activity.git_stats_files_changed_n=değişti activity.git_stats_files_changed_n=değişti
activity.git_stats_additions=ve olmuş
activity.git_stats_additions=,
activity.git_stats_addition_1=%d ekleme oldu activity.git_stats_addition_1=%d ekleme oldu
activity.git_stats_addition_n=%d ekleme ve
activity.git_stats_addition_n=%d ekleme
activity.git_stats_and_deletions=ve activity.git_stats_and_deletions=ve
activity.git_stats_deletion_1=%d silme oldu activity.git_stats_deletion_1=%d silme oldu
activity.git_stats_deletion_n=%d silme
activity.git_stats_deletion_n=%d silme oldu
search=Ara search=Ara
search.search_repo=Depo ara search.search_repo=Depo ara
@ -1463,6 +1463,17 @@ settings.transfer_desc=Bu depoyu bir kullanıcıya veya yönetici haklarına sah
settings.transfer_notices_1=- Bireysel bir kullanıcıya aktarırsanız depoya erişiminizi kaybedersiniz. settings.transfer_notices_1=- Bireysel bir kullanıcıya aktarırsanız depoya erişiminizi kaybedersiniz.
settings.transfer_notices_2=- Sahip (-yardımcı) olduğunuz bir organizasyona devrederseniz, depoya erişmeye devam edersiniz. settings.transfer_notices_2=- Sahip (-yardımcı) olduğunuz bir organizasyona devrederseniz, depoya erişmeye devam edersiniz.
settings.transfer_form_title=Onaylamak için depo adını girin: settings.transfer_form_title=Onaylamak için depo adını girin:
settings.signing_settings=İmza Doğrulama Ayarları
settings.trust_model=İmza Güven Modeli
settings.trust_model.default=Varsayılan Güven Modeli
settings.trust_model.default.desc=Bu kurulum için varsayılan depo güven modelini kullanın.
settings.trust_model.collaborator=Katkıcı
settings.trust_model.collaborator.long=Katkıcı: Katkıcıların imzalarına güvenin
settings.trust_model.collaborator.desc=Bu deponun katkıcılarının geçerli imzaları "güvenilir" olarak işaretlenecektir - (işleyen ile eşleşse de eşleşmese de). Aksi takdirde, imzanın işleyenle eşleşmesi durumunda geçerli imzalar "güvenilmez", eşleşmiyorsa "eşleşmemiş" olarak işaretlenir.
settings.trust_model.committer=İşleyen
settings.trust_model.committer.long=İşleyen: İşleyenlerle eşleşen imzalara güvenin (Bu, GitHub ile eşleşir ve Gitea imzalı işlemeleri işleyen olarak Gitea'ya sahip olmaya zorlar)
settings.trust_model.collaboratorcommitter=Katkıcı+İşleyen
settings.trust_model.collaboratorcommitter.long=Katkıcı+İşleyen: İşleyenle eşleşen katkıcıların imzalarına güvenin
settings.wiki_delete=Wiki Verisini Sil settings.wiki_delete=Wiki Verisini Sil
settings.wiki_delete_desc=Depo wiki verilerini silmek kalıcıdır ve geri alınamaz. settings.wiki_delete_desc=Depo wiki verilerini silmek kalıcıdır ve geri alınamaz.
settings.wiki_delete_notices_1=- Bu işlem, %s için depo wiki'sini kalıcı olarak siler ve devre dışı bırakır. settings.wiki_delete_notices_1=- Bu işlem, %s için depo wiki'sini kalıcı olarak siler ve devre dışı bırakır.

+ 88
- 24
options/locale/locale_zh-TW.ini View File

@ -67,10 +67,10 @@ your_settings=設定
all=所有 all=所有
sources=來源 sources=來源
mirrors=鏡像 mirrors=鏡像
collaborative=同者
collaborative=
forks=Fork forks=Fork
activities=
activities=
pull_requests=合併請求 pull_requests=合併請求
issues=問題 issues=問題
milestones=里程碑 milestones=里程碑
@ -293,6 +293,8 @@ authorize_redirect_notice=如果您授權此應用程式,您將會被重新導
authorize_application_created_by=此應用程式是由 %s 建立的。 authorize_application_created_by=此應用程式是由 %s 建立的。
authorize_application_description=如果您允許,它將能夠讀取和修改您的所有帳戶資訊,包括私有儲存庫和組織。 authorize_application_description=如果您允許,它將能夠讀取和修改您的所有帳戶資訊,包括私有儲存庫和組織。
authorize_title=授權「%s」存取您的帳戶? authorize_title=授權「%s」存取您的帳戶?
sspi_auth_failed=SSPI 認證失敗
password_pwned=您選擇的密碼已被列於<a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">被盜密碼清單</a>中,該清單因公共資料外洩而暴露。請試試其它密碼。
password_pwned_err=無法完成對 HaveIBeenPwned 的請求。 password_pwned_err=無法完成對 HaveIBeenPwned 的請求。
[mail] [mail]
@ -379,7 +381,7 @@ target_branch_not_exist=目標分支不存在
change_avatar=更改大頭貼... change_avatar=更改大頭貼...
join_on=加入於 join_on=加入於
repositories=儲存庫列表 repositories=儲存庫列表
activity=公開
activity=公開動
followers=追蹤者 followers=追蹤者
starred=已加星號 starred=已加星號
projects=專案 projects=專案
@ -388,6 +390,7 @@ follow=追蹤
unfollow=取消追蹤 unfollow=取消追蹤
heatmap.loading=正在載入熱點圖... heatmap.loading=正在載入熱點圖...
user_bio=個人簡介 user_bio=個人簡介
disabled_public_activity=這個使用者已對外隱藏動態
form.name_reserved=帳號「%s」是被保留的。 form.name_reserved=帳號「%s」是被保留的。
form.name_pattern_not_allowed=帳號不可包含字元「%s」。 form.name_pattern_not_allowed=帳號不可包含字元「%s」。
@ -428,8 +431,8 @@ cancel=取消操作
language=語言 language=語言
ui=佈景主題 ui=佈景主題
privacy=隱私 privacy=隱私
keep_activity_private=在個人資料頁面隱藏最近活
keep_activity_private_popup=最近的活動只有你和管理員看得到
keep_activity_private=在個人資料頁面隱藏動
keep_activity_private_popup=讓動只有你和管理員看得到
lookup_avatar_by_mail=以電子信箱查詢大頭貼 lookup_avatar_by_mail=以電子信箱查詢大頭貼
federated_avatar_lookup=Federated Avatar 查詢 federated_avatar_lookup=Federated Avatar 查詢
@ -490,8 +493,11 @@ ssh_helper=需要協助嗎?建議可看看 GitHub 的文件
gpg_helper=<strong>需要協助嗎?</strong>建議可看看 GitHub 的 <a href="%s">about GPG</a> 文件。 gpg_helper=<strong>需要協助嗎?</strong>建議可看看 GitHub 的 <a href="%s">about GPG</a> 文件。
add_new_key=增加 SSH 金鑰 add_new_key=增加 SSH 金鑰
add_new_gpg_key=新增 GPG 金鑰 add_new_gpg_key=新增 GPG 金鑰
key_content_ssh_placeholder=以 'ssh-ed25519', 'ssh-rsa', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521' 開頭
key_content_gpg_placeholder=以 '-----BEGIN PGP PUBLIC KEY BLOCK-----' 開頭
ssh_key_been_used=此 SSH 金鑰已添加到伺服器。 ssh_key_been_used=此 SSH 金鑰已添加到伺服器。
ssh_key_name_used=早已有相同名稱的 SSH 金鑰存在於你的帳戶。 ssh_key_name_used=早已有相同名稱的 SSH 金鑰存在於你的帳戶。
gpg_key_id_used=已存在具有相同 ID 的 GPG 金鑰。
gpg_no_key_email_found=此 GPG 金鑰不適用於您的任何電子信箱。 gpg_no_key_email_found=此 GPG 金鑰不適用於您的任何電子信箱。
subkeys=次金鑰 subkeys=次金鑰
key_id=金鑰 ID key_id=金鑰 ID
@ -509,7 +515,7 @@ add_on=增加於
valid_until=有效期至 valid_until=有效期至
valid_forever=永遠有效 valid_forever=永遠有效
last_used=上次使用在 last_used=上次使用在
no_activity=沒有最近活動
no_activity=沒有近期動態
can_read_info=讀取 can_read_info=讀取
can_write_info=寫入 can_write_info=寫入
key_state_desc=該金鑰在 7 天內被使用過 key_state_desc=該金鑰在 7 天內被使用過
@ -638,6 +644,8 @@ generate_from=產生自
repo_desc=儲存庫描述 repo_desc=儲存庫描述
repo_lang=儲存庫語言 repo_lang=儲存庫語言
repo_gitignore_helper=選擇 .gitignore 範本 repo_gitignore_helper=選擇 .gitignore 範本
issue_labels=問題標籤
issue_labels_helper=選擇一個問題標籤集
license=授權條款 license=授權條款
license_helper=請選擇授權條款檔案 license_helper=請選擇授權條款檔案
readme=讀我 readme=讀我
@ -668,6 +676,7 @@ template.git_hooks=Git Hook
template.webhooks=Webhook template.webhooks=Webhook
template.topics=主題 template.topics=主題
template.avatar=大頭貼 template.avatar=大頭貼
template.issue_labels=問題標籤
archive.title=此存儲庫已封存。您可以查看檔案及 Clone 此存儲庫,但不能推送、建立問題及發出合併請求。 archive.title=此存儲庫已封存。您可以查看檔案及 Clone 此存儲庫,但不能推送、建立問題及發出合併請求。
@ -753,6 +762,8 @@ video_not_supported_in_browser=您的瀏覽器不支援使用 HTML5 播放影片
audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 'audio' 標籤 audio_not_supported_in_browser=您的瀏覽器不支援 HTML5 'audio' 標籤
stored_lfs=儲存到到 Git LFS stored_lfs=儲存到到 Git LFS
commit_graph=提交線圖 commit_graph=提交線圖
commit_graph.monochrome=單色
commit_graph.color=彩色
blame=Blame blame=Blame
normal_view=標準檢視 normal_view=標準檢視
line= line=
@ -791,6 +802,7 @@ editor.add_subdir=加入目錄
editor.unable_to_upload_files=上傳檔案失敗到 '%s', 錯誤訊息: %v editor.unable_to_upload_files=上傳檔案失敗到 '%s', 錯誤訊息: %v
editor.upload_file_is_locked=檔案「%s」已被 %s 鎖定 editor.upload_file_is_locked=檔案「%s」已被 %s 鎖定
editor.upload_files_to_dir=上傳檔案到 '%s' editor.upload_files_to_dir=上傳檔案到 '%s'
editor.cannot_commit_to_protected_branch=無法提交到受保護的分支「%s」。
commits.commits=次程式碼提交 commits.commits=次程式碼提交
commits.search=搜尋提交歷史... commits.search=搜尋提交歷史...
@ -805,6 +817,7 @@ commits.signed_by=簽署人
commits.gpg_key_id=GPG 金鑰 ID commits.gpg_key_id=GPG 金鑰 ID
ext_issues=外部問題 ext_issues=外部問題
ext_issues.desc=連結到外部問題追蹤器。
projects=專案 projects=專案
projects.desc=在專案看板中管理問題和 pull。 projects.desc=在專案看板中管理問題和 pull。
@ -982,6 +995,7 @@ issues.tracker=時間追蹤
issues.start_tracking_short=開始 issues.start_tracking_short=開始
issues.start_tracking=開始時間追蹤 issues.start_tracking=開始時間追蹤
issues.start_tracking_history=`開始工作 %s` issues.start_tracking_history=`開始工作 %s`
issues.tracker_auto_close=當這個問題被關閉時,自動停止計時器
issues.tracking_already_started=`您已經開始時間追蹤這個 <a href="%s">問題</a>!` issues.tracking_already_started=`您已經開始時間追蹤這個 <a href="%s">問題</a>!`
issues.stop_tracking=停止 issues.stop_tracking=停止
issues.stop_tracking_history=`結束工作 %s` issues.stop_tracking_history=`結束工作 %s`
@ -1086,12 +1100,13 @@ wiki.file_revision=頁面修訂記錄
wiki.wiki_page_revisions=Wiki 頁面修訂記錄 wiki.wiki_page_revisions=Wiki 頁面修訂記錄
wiki.back_to_wiki=回到 Wiki 頁面 wiki.back_to_wiki=回到 Wiki 頁面
wiki.delete_page_button=刪除頁面 wiki.delete_page_button=刪除頁面
wiki.delete_page_notice_1=刪除 Wiki 頁面「%s」將不可還原。是否繼續?
wiki.page_already_exists=相同名稱的 Wiki 頁面已經存在。 wiki.page_already_exists=相同名稱的 Wiki 頁面已經存在。
wiki.reserved_page=Wiki 頁面名稱 "%s" 是被保留的。 wiki.reserved_page=Wiki 頁面名稱 "%s" 是被保留的。
wiki.pages=所有頁面 wiki.pages=所有頁面
wiki.last_updated=最後更新於 %s wiki.last_updated=最後更新於 %s
activity=
activity=
activity.period.filter_label=期間: activity.period.filter_label=期間:
activity.period.daily=1 天 activity.period.daily=1 天
activity.period.halfweekly=3 天 activity.period.halfweekly=3 天
@ -1130,6 +1145,7 @@ activity.title.releases_1=%d 版本發佈
activity.title.releases_n=%d 版本發佈 activity.title.releases_n=%d 版本發佈
activity.title.releases_published_by=%s 由 %s 發佈 activity.title.releases_published_by=%s 由 %s 發佈
activity.published_release_label=已發佈 activity.published_release_label=已發佈
activity.no_git_activity=期間內沒有任何提交動態
activity.git_stats_and_deletions= activity.git_stats_and_deletions=
search=搜尋 search=搜尋
@ -1145,7 +1161,7 @@ settings.collaboration.write=可寫權限
settings.collaboration.read=可讀權限 settings.collaboration.read=可讀權限
settings.collaboration.owner=擁有者 settings.collaboration.owner=擁有者
settings.collaboration.undefined=未定義 settings.collaboration.undefined=未定義
settings.hooks=管理 Webhooks
settings.hooks=Webhook
settings.githooks=管理 Git Hooks settings.githooks=管理 Git Hooks
settings.basic_settings=基本設定 settings.basic_settings=基本設定
settings.mirror_settings=鏡像設定 settings.mirror_settings=鏡像設定
@ -1164,31 +1180,44 @@ settings.use_external_wiki=使用外部 Wiki
settings.external_wiki_url=外部 Wiki 連結 settings.external_wiki_url=外部 Wiki 連結
settings.external_wiki_url_error=外部 Wiki 網址不是有效的網址。 settings.external_wiki_url_error=外部 Wiki 網址不是有效的網址。
settings.external_wiki_url_desc=點擊問題標籤時,使用者會被導向到外部 Wiki URL。 settings.external_wiki_url_desc=點擊問題標籤時,使用者會被導向到外部 Wiki URL。
settings.external_tracker_url=外部 Issue 追蹤網址
settings.tracker_url_format=外部問題管理系統的 URL 格式
settings.issues_desc=啟用儲存庫問題追蹤器
settings.use_internal_issue_tracker=使用內建問題追蹤器
settings.use_external_issue_tracker=使用外部問題追蹤器
settings.external_tracker_url=外部問題追蹤器 URL
settings.external_tracker_url_error=該外部問題追蹤器 URL 無效。
settings.external_tracker_url_desc=點擊問題頁籤時,使用者會被導向至外部問題追蹤器 URL。
settings.tracker_url_format=外部問題追蹤器的 URL 格式
settings.tracker_url_format_error=該外部問題追蹤器 URL 格式無效。
settings.tracker_issue_style=外部問題追蹤器的編號格式
settings.tracker_issue_style.numeric=數字 settings.tracker_issue_style.numeric=數字
settings.tracker_issue_style.alphanumeric=字母及數字 settings.tracker_issue_style.alphanumeric=字母及數字
settings.tracker_url_format_desc=使用占位符 <code>{user}</code>, <code>{repo}</code> 和 <code>{index}</code> 代表帳號、儲存庫名稱和問題編號。
settings.enable_timetracker=啟用時間追蹤 settings.enable_timetracker=啟用時間追蹤
settings.allow_only_contributors_to_track_time=只讓貢獻者追蹤時間
settings.projects_desc=啟用儲存庫專案 settings.projects_desc=啟用儲存庫專案
settings.admin_settings=管理員設定 settings.admin_settings=管理員設定
settings.danger_zone=危險操作區 settings.danger_zone=危險操作區
settings.new_owner_has_same_repo=新的儲存庫擁有者已經存在同名儲存庫! settings.new_owner_has_same_repo=新的儲存庫擁有者已經存在同名儲存庫!
settings.convert=轉換為普通儲存庫 settings.convert=轉換為普通儲存庫
settings.convert_desc=您可以將此鏡像轉成普通儲存庫。此動作不可恢復。
settings.convert_desc=您可以將此鏡像轉成普通儲存庫。此動作不可還原。
settings.convert_notices_1=此操作會將此鏡像轉換成普通儲存庫且不可還原。
settings.convert_confirm=轉換儲存庫 settings.convert_confirm=轉換儲存庫
settings.convert_succeed=鏡像儲存庫已成功轉換為一般儲存庫。 settings.convert_succeed=鏡像儲存庫已成功轉換為一般儲存庫。
settings.convert_fork=轉換成普通儲存庫 settings.convert_fork=轉換成普通儲存庫
settings.convert_fork_desc=您可以將此 fork 轉換成普通儲存庫。此動作不可還原。 settings.convert_fork_desc=您可以將此 fork 轉換成普通儲存庫。此動作不可還原。
settings.convert_fork_notices_1=此操作會將此 fork 轉換成普通儲存庫。此動作不可還原。
settings.convert_fork_notices_1=此操作會將此 fork 轉換成普通儲存庫不可還原。
settings.convert_fork_confirm=轉換儲存庫 settings.convert_fork_confirm=轉換儲存庫
settings.convert_fork_succeed=此 fork 已轉換成普通儲存庫。 settings.convert_fork_succeed=此 fork 已轉換成普通儲存庫。
settings.transfer=轉移儲存庫所有權 settings.transfer=轉移儲存庫所有權
settings.transfer_desc=將此儲存庫轉移給其它使用者或受您管理的組織。
settings.transfer_form_title=輸入儲存庫名稱以確認: settings.transfer_form_title=輸入儲存庫名稱以確認:
settings.wiki_delete=刪除 Wiki 資料 settings.wiki_delete=刪除 Wiki 資料
settings.wiki_delete_desc=刪除儲存庫 Wiki 資料是永久的且不可還原。
settings.confirm_wiki_delete=刪除 Wiki 資料 settings.confirm_wiki_delete=刪除 Wiki 資料
settings.wiki_deletion_success=已刪除儲存庫的 Wiki 資料。 settings.wiki_deletion_success=已刪除儲存庫的 Wiki 資料。
settings.delete=刪除本儲存庫 settings.delete=刪除本儲存庫
settings.delete_notices_1=- 此操作 <strong>不可以</strong> 被回滾。
settings.delete_desc=刪除儲存庫是永久的且不可還原。
settings.delete_notices_1=- 此操作<strong>不可</strong>還原。
settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。 settings.delete_notices_fork_1=- 在此儲存庫刪除後,它的 fork 將會變成獨立儲存庫。
settings.deletion_success=這個儲存庫已被刪除。 settings.deletion_success=這個儲存庫已被刪除。
settings.update_settings_success=已更新儲存庫的設定。 settings.update_settings_success=已更新儲存庫的設定。
@ -1206,6 +1235,7 @@ settings.teams=團隊
settings.add_team=增加團隊 settings.add_team=增加團隊
settings.search_team=搜尋團隊... settings.search_team=搜尋團隊...
settings.add_webhook=建立 Webhook settings.add_webhook=建立 Webhook
settings.hooks_desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。在<a target="_blank" rel="noopener noreferrer" href="%s">Webhook 指南</a>閱讀更多內容。
settings.webhook_deletion=刪除 Webhook settings.webhook_deletion=刪除 Webhook
settings.webhook_deletion_success=Webhook 已刪除。 settings.webhook_deletion_success=Webhook 已刪除。
settings.webhook.test_delivery=測試推送 settings.webhook.test_delivery=測試推送
@ -1255,8 +1285,9 @@ settings.slack_token=Token
settings.slack_domain=域名 settings.slack_domain=域名
settings.slack_channel=頻道 settings.slack_channel=頻道
settings.add_msteams_hook_desc=將 <a href="%s">Microsoft Teams</a> 整合到你的儲存庫。 settings.add_msteams_hook_desc=將 <a href="%s">Microsoft Teams</a> 整合到你的儲存庫。
settings.deploy_keys=管理部署金鑰
settings.deploy_keys=部署金鑰
settings.add_deploy_key=新增部署金鑰 settings.add_deploy_key=新增部署金鑰
settings.no_deploy_keys=沒有任何部屬金鑰。
settings.title=標題 settings.title=標題
settings.deploy_key_content=金鑰文本 settings.deploy_key_content=金鑰文本
settings.deploy_key_deletion=刪除部署金鑰 settings.deploy_key_deletion=刪除部署金鑰
@ -1265,14 +1296,29 @@ settings.protected_branch=分支保護
settings.protected_branch_can_push=允許推送? settings.protected_branch_can_push=允許推送?
settings.protected_branch_can_push_yes=你可以推送 settings.protected_branch_can_push_yes=你可以推送
settings.protected_branch_can_push_no=你不能推送 settings.protected_branch_can_push_no=你不能推送
settings.branch_protection=<b>%s</b> 的分支保護
settings.protect_this_branch=啟用分支保護 settings.protect_this_branch=啟用分支保護
settings.protect_this_branch_desc=防止刪除分支,並限制 Git 推送與合併到分支。
settings.protect_disable_push=停用推送
settings.protect_disable_push_desc=不允許推送到此分支。
settings.protect_enable_push=啟用推送
settings.protect_enable_push_desc=任何擁有寫入權限的使用者將可推送至該分支(但不可使用force push)。
settings.protect_whitelist_committers=使用白名單限制推送
settings.protect_whitelist_committers_desc=僅允許白名單內的使用者或團隊推送至該分支(但不可使用force push)。
settings.protect_whitelist_users=允許推送的使用者:
settings.protect_whitelist_search_users=搜尋使用者... settings.protect_whitelist_search_users=搜尋使用者...
settings.protect_whitelist_teams=允許推送的團隊:
settings.protect_whitelist_search_teams=搜尋團隊... settings.protect_whitelist_search_teams=搜尋團隊...
settings.protect_merge_whitelist_committers=啟動合併白清單
settings.protect_merge_whitelist_users=允許合併的使用者:
settings.protect_merge_whitelist_teams=允許合併的團隊:
settings.add_protected_branch=啟用保護 settings.add_protected_branch=啟用保護
settings.delete_protected_branch=停用保護 settings.delete_protected_branch=停用保護
settings.update_protect_branch_success='%s' 的分支保護已被更新 settings.update_protect_branch_success='%s' 的分支保護已被更新
settings.remove_protected_branch_success='%s' 的分支保護已被停用 settings.remove_protected_branch_success='%s' 的分支保護已被停用
settings.protected_branch_deletion=停用分支保護 settings.protected_branch_deletion=停用分支保護
settings.protected_branch_deletion_desc=停用分支保護將允許有寫入權限的使用者推送至該分支,是否繼續?
settings.default_branch_desc=請選擇一個用來提交程式碼和合併請求的預設分支。
settings.choose_branch=選擇一個分支... settings.choose_branch=選擇一個分支...
settings.no_protected_branch=沒有受保護的分支。 settings.no_protected_branch=沒有受保護的分支。
settings.edit_protected_branch=編輯 settings.edit_protected_branch=編輯
@ -1281,6 +1327,7 @@ settings.matrix.access_token=Access Token
settings.matrix.message_type=訊息類型 settings.matrix.message_type=訊息類型
settings.archive.button=封存儲存庫 settings.archive.button=封存儲存庫
settings.archive.header=封存本儲存庫 settings.archive.header=封存本儲存庫
settings.archive.text=封存此儲存庫會將它完全進入唯讀狀態。它將自資訊主頁隱藏、無法進行推送且不能建立問題及合併請求。
settings.archive.success=此儲存庫已被封存 settings.archive.success=此儲存庫已被封存
settings.unarchive.button=解除封存儲存庫 settings.unarchive.button=解除封存儲存庫
settings.unarchive.header=解除封存本儲存庫 settings.unarchive.header=解除封存本儲存庫
@ -1298,8 +1345,12 @@ diff.browse_source=瀏覽代碼
diff.parent=父節點 diff.parent=父節點
diff.commit=當前提交 diff.commit=當前提交
diff.data_not_available=沒有內容比較可以使用 diff.data_not_available=沒有內容比較可以使用
diff.options_button=Diff 選項
diff.show_diff_stats=顯示統計資料
diff.download_patch=下載 Patch 檔
diff.download_diff=下載 Diff 檔
diff.show_split_view=分割檢視 diff.show_split_view=分割檢視
diff.show_unified_view=統一視圖
diff.show_unified_view=統一
diff.stats_desc=共有 <strong> %d 個檔案被更改</strong>,包括 <strong>%d 行新增</strong> 和 <strong>%d 行删除</strong> diff.stats_desc=共有 <strong> %d 個檔案被更改</strong>,包括 <strong>%d 行新增</strong> 和 <strong>%d 行删除</strong>
diff.bin=二進制 diff.bin=二進制
diff.view_file=查看文件 diff.view_file=查看文件
@ -1339,7 +1390,7 @@ branch.already_exists=分支名稱 ”%s“ 已經存在
branch.delete_head=刪除 branch.delete_head=刪除
branch.delete=刪除分支 '%s' branch.delete=刪除分支 '%s'
branch.delete_html=刪除分支 branch.delete_html=刪除分支
branch.delete_desc=刪除分支是永久的。 此動作<strong>無法</strong>復原,繼續?
branch.delete_desc=刪除分支是永久的。 此動作<strong>不可</strong>還原,是否繼續?
branch.deletion_success=分支 '%s' 已被刪除。 branch.deletion_success=分支 '%s' 已被刪除。
branch.deletion_failed=刪除分支 '%s' 失敗。 branch.deletion_failed=刪除分支 '%s' 失敗。
branch.create_branch=建立分支 <strong>%s</strong> branch.create_branch=建立分支 <strong>%s</strong>
@ -1350,6 +1401,8 @@ branch.deleted_by=刪除人: %s
branch.restore_failed=還原分支 %s 失敗 branch.restore_failed=還原分支 %s 失敗
branch.protected_deletion_failed=分支 '%s' 已被保護,不能刪除。 branch.protected_deletion_failed=分支 '%s' 已被保護,不能刪除。
branch.download=下載分支 '%s' branch.download=下載分支 '%s'
branch.included_desc=此分支是預設分支的一部分
branch.included=包含
topic.manage_topics=管理主題 topic.manage_topics=管理主題
topic.done=完成 topic.done=完成
@ -1400,6 +1453,7 @@ settings.change_orgname_prompt=注意:修改組織名稱將會同時修改對
settings.update_avatar_success=已更新組織的大頭貼。 settings.update_avatar_success=已更新組織的大頭貼。
settings.delete=刪除組織 settings.delete=刪除組織
settings.delete_account=刪除這個組織 settings.delete_account=刪除這個組織
settings.delete_prompt=該組織將被永久刪除。此動作<strong>不可</strong>還原!
settings.confirm_delete_account=確認刪除組織 settings.confirm_delete_account=確認刪除組織
settings.delete_org_title=刪除組織 settings.delete_org_title=刪除組織
settings.hooks_desc=新增 webhooks 將觸發在這個組織下 <strong>全部的儲存庫</strong> 。 settings.hooks_desc=新增 webhooks 將觸發在這個組織下 <strong>全部的儲存庫</strong> 。
@ -1480,7 +1534,7 @@ total=總計:%d
dashboard.statistic=摘要 dashboard.statistic=摘要
dashboard.operations=維護操作 dashboard.operations=維護操作
dashboard.system_status=系統狀態 dashboard.system_status=系統狀態
dashboard.statistic_info=Gitea 資料庫統計:<b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 次評論,<b>%d</b> 個社群帳戶,<b>%d</b> 個用戶關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個登錄源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。
dashboard.statistic_info=Gitea 資料庫統計:<b>%d</b> 位使用者,<b>%d</b> 個組織,<b>%d</b> 個公鑰,<b>%d</b> 個儲存庫,<b>%d</b> 個儲存庫關注,<b>%d</b> 個星號,<b>%d</b> 次行為,<b>%d</b> 條權限記錄,<b>%d</b> 個問題,<b>%d</b> 次評論,<b>%d</b> 個社群帳戶,<b>%d</b> 個用戶關注,<b>%d</b> 個鏡像,<b>%d</b> 個版本發佈,<b>%d</b> 個認證來源,<b>%d</b> 個 Webhook ,<b>%d</b> 個里程碑,<b>%d</b> 個標籤,<b>%d</b> 個 Hook 任務,<b>%d</b> 個團隊,<b>%d</b> 個更新任務,<b>%d</b> 個附件。
dashboard.operation_name=操作名稱 dashboard.operation_name=操作名稱
dashboard.operation_switch=開關 dashboard.operation_switch=開關
dashboard.operation_run=執行 dashboard.operation_run=執行
@ -1539,8 +1593,9 @@ users.never_login=從未登入
users.send_register_notify=寄送使用者註冊通知 users.send_register_notify=寄送使用者註冊通知
users.new_success=已建立新帳戶 '%s'。 users.new_success=已建立新帳戶 '%s'。
users.edit=編輯 users.edit=編輯
users.auth_source=認證源
users.auth_source=認證
users.local=本地 users.local=本地
users.auth_login_name=認證登入名稱
users.password_helper=密碼留空則不修改。 users.password_helper=密碼留空則不修改。
users.update_profile_success=已更新使用者帳戶。 users.update_profile_success=已更新使用者帳戶。
users.edit_account=編輯使用者帳戶 users.edit_account=編輯使用者帳戶
@ -1585,11 +1640,14 @@ repos.forks=Fork 數
repos.issues=問題數 repos.issues=問題數
repos.size=大小 repos.size=大小
hooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。這裡所定義的 Webhook 是預設的,並且會複製到所有新儲存庫。在<a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">Webhook 指南</a>閱讀更多內容。
hooks.add_webhook=新增預設 Webhook hooks.add_webhook=新增預設 Webhook
hooks.update_webhook=更新預設 Webhook hooks.update_webhook=更新預設 Webhook
systemhooks.desc=當觸發某些 Gitea 事件時,Webhook 會自動發出 HTTP POST 請求到指定的伺服器。由於這裡所定義的 Webhook 會影響此系統上的所有儲存庫,因此請評估這會對效能造成多少影響。在<a target="_blank" rel="noopener" href="https://docs.gitea.io/en-us/webhooks/">Webhook 指南</a>閱讀更多內容。
systemhooks.add_webhook=新增系統 Webhook systemhooks.add_webhook=新增系統 Webhook
auths.auth_manage_panel=認證來源管理
auths.new=新增認證來源 auths.new=新增認證來源
auths.name=認證名稱 auths.name=認證名稱
auths.type=認證類型 auths.type=認證類型
@ -1601,7 +1659,7 @@ auths.auth_name=認證名稱
auths.security_protocol=安全協定 auths.security_protocol=安全協定
auths.domain=域名 auths.domain=域名
auths.host=主機地址 auths.host=主機地址
auths.port=主機端口
auths.port=連接埠
auths.bind_dn=Bind DN auths.bind_dn=Bind DN
auths.bind_password=Bind 密碼 auths.bind_password=Bind 密碼
auths.bind_password_helper=警告:此密碼以明文存儲。 請儘可能使用唯讀帳戶。 auths.bind_password_helper=警告:此密碼以明文存儲。 請儘可能使用唯讀帳戶。
@ -1620,7 +1678,7 @@ auths.restricted_filter_helper=留白則不限制任何使用者。使用米字
auths.ms_ad_sa=MS AD 搜尋屬性 auths.ms_ad_sa=MS AD 搜尋屬性
auths.smtp_auth=SMTP 驗證類型 auths.smtp_auth=SMTP 驗證類型
auths.smtphost=SMTP 主機地址 auths.smtphost=SMTP 主機地址
auths.smtpport=SMTP 主機端口
auths.smtpport=SMTP 連接埠
auths.allowed_domains=域名白名單 auths.allowed_domains=域名白名單
auths.enable_tls=啟用 TLS 加密 auths.enable_tls=啟用 TLS 加密
auths.skip_tls_verify=忽略 TLS 驗證 auths.skip_tls_verify=忽略 TLS 驗證
@ -1647,12 +1705,17 @@ auths.tip.github=在 https://github.com/settings/applications/new 註冊一個
auths.tip.gitlab=在 https://gitlab.com/profile/applications 註冊一個新的應用程式 auths.tip.gitlab=在 https://gitlab.com/profile/applications 註冊一個新的應用程式
auths.tip.openid_connect=使用 OpenID 連接探索 URL (<server>/.well-known/openid-configuration) 來指定節點 auths.tip.openid_connect=使用 OpenID 連接探索 URL (<server>/.well-known/openid-configuration) 來指定節點
auths.edit=修改認證來源 auths.edit=修改認證來源
auths.activated=授權來源已啟用
auths.activated=認證來源已啟用
auths.new_success=已增加認證'%s'。 auths.new_success=已增加認證'%s'。
auths.update_success=認證來源已更新
auths.update=更新證來源
auths.delete=刪除證來源
auths.update_success=已更新認證來源。
auths.update=更新證來源
auths.delete=刪除證來源
auths.delete_auth_title=刪除認證來源 auths.delete_auth_title=刪除認證來源
auths.delete_auth_desc=刪除認證來源將會拒絕使用它登入的使用者。是否繼續?
auths.still_in_used=此認證來源正在使用中。請先轉換或刪除使用此授權來源的使用者。
auths.deletion_success=已刪除認證來源。
auths.login_source_exist=認證來源「%s」已經存在。
auths.login_source_of_type_exist=已經有相同類型的認證來源。
config.server_config=伺服器組態 config.server_config=伺服器組態
config.app_name=網站標題 config.app_name=網站標題
@ -1711,6 +1774,7 @@ config.default_keep_email_private=預設隱藏電子郵件地址
config.default_allow_create_organization=預設允許新增組織 config.default_allow_create_organization=預設允許新增組織
config.enable_timetracking=啟用時間追蹤 config.enable_timetracking=啟用時間追蹤
config.default_enable_timetracking=預設啟用時間追蹤 config.default_enable_timetracking=預設啟用時間追蹤
config.default_allow_only_contributors_to_track_time=只讓貢獻者追蹤時間
config.no_reply_address=隱藏電子郵件域名 config.no_reply_address=隱藏電子郵件域名
config.default_visibility_organization=新組織的預設瀏覽權限 config.default_visibility_organization=新組織的預設瀏覽權限
config.default_enable_dependencies=預設啟用問題相依 config.default_enable_dependencies=預設啟用問題相依

+ 3
- 5
routers/api/v1/misc/signing.go View File

@ -10,11 +10,10 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
) )
// SigningKey returns the public key of the default signing key if it exists // SigningKey returns the public key of the default signing key if it exists
func SigningKey(ctx *context.Context) {
func SigningKey(ctx *context.APIContext) {
// swagger:operation GET /signing-key.gpg miscellaneous getSigningKey // swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
// --- // ---
// summary: Get default signing-key.gpg // summary: Get default signing-key.gpg
@ -55,12 +54,11 @@ func SigningKey(ctx *context.Context) {
content, err := models.PublicSigningKey(path) content, err := models.PublicSigningKey(path)
if err != nil { if err != nil {
ctx.ServerError("gpg export", err)
ctx.Error(http.StatusInternalServerError, "gpg export", err)
return return
} }
_, err = ctx.Write([]byte(content)) _, err = ctx.Write([]byte(content))
if err != nil { if err != nil {
log.Error("Error writing key content %v", err)
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("%v", err))
ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %v", err))
} }
} }

+ 1
- 1
routers/api/v1/org/label.go View File

@ -201,7 +201,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
label.Description = *form.Description label.Description = *form.Description
} }
if err := models.UpdateLabel(label); err != nil { if err := models.UpdateLabel(label); err != nil {
ctx.ServerError("UpdateLabel", err)
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToLabel(label)) ctx.JSON(http.StatusOK, convert.ToLabel(label))

+ 1
- 1
routers/api/v1/org/org.go View File

@ -85,7 +85,7 @@ func ListUserOrgs(ctx *context.APIContext) {
if ctx.Written() { if ctx.Written() {
return return
} }
listUserOrgs(ctx, u, ctx.User.IsAdmin)
listUserOrgs(ctx, u, ctx.User != nil && (ctx.User.IsAdmin || ctx.User.ID == u.ID))
} }
// GetAll return list of all public organizations // GetAll return list of all public organizations

+ 9
- 9
routers/api/v1/repo/commits.go View File

@ -63,7 +63,7 @@ func GetSingleCommit(ctx *context.APIContext) {
func getCommit(ctx *context.APIContext, identifier string) { func getCommit(ctx *context.APIContext, identifier string) {
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil { if err != nil {
ctx.ServerError("OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return return
} }
defer gitRepo.Close() defer gitRepo.Close()
@ -75,7 +75,7 @@ func getCommit(ctx *context.APIContext, identifier string) {
json, err := convert.ToCommit(ctx.Repo.Repository, commit, nil) json, err := convert.ToCommit(ctx.Repo.Repository, commit, nil)
if err != nil { if err != nil {
ctx.ServerError("toCommit", err)
ctx.Error(http.StatusInternalServerError, "toCommit", err)
return return
} }
ctx.JSON(http.StatusOK, json) ctx.JSON(http.StatusOK, json)
@ -129,7 +129,7 @@ func GetAllCommits(ctx *context.APIContext) {
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath()) gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil { if err != nil {
ctx.ServerError("OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return return
} }
defer gitRepo.Close() defer gitRepo.Close()
@ -150,20 +150,20 @@ func GetAllCommits(ctx *context.APIContext) {
// no sha supplied - use default branch // no sha supplied - use default branch
head, err := gitRepo.GetHEADBranch() head, err := gitRepo.GetHEADBranch()
if err != nil { if err != nil {
ctx.ServerError("GetHEADBranch", err)
ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
return return
} }
baseCommit, err = gitRepo.GetBranchCommit(head.Name) baseCommit, err = gitRepo.GetBranchCommit(head.Name)
if err != nil { if err != nil {
ctx.ServerError("GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return return
} }
} else { } else {
// get commit specified by sha // get commit specified by sha
baseCommit, err = gitRepo.GetCommit(sha) baseCommit, err = gitRepo.GetCommit(sha)
if err != nil { if err != nil {
ctx.ServerError("GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return return
} }
} }
@ -171,7 +171,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Total commit count // Total commit count
commitsCountTotal, err := baseCommit.CommitsCount() commitsCountTotal, err := baseCommit.CommitsCount()
if err != nil { if err != nil {
ctx.ServerError("GetCommitsCount", err)
ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
return return
} }
@ -180,7 +180,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Query commits // Query commits
commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize) commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize)
if err != nil { if err != nil {
ctx.ServerError("CommitsByRange", err)
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
return return
} }
@ -195,7 +195,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Create json struct // Create json struct
apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, commit, userCache) apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, commit, userCache)
if err != nil { if err != nil {
ctx.ServerError("toCommit", err)
ctx.Error(http.StatusInternalServerError, "toCommit", err)
return return
} }

+ 1
- 1
routers/api/v1/repo/fork.go View File

@ -109,7 +109,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) {
} }
isMember, err := org.IsOrgMember(ctx.User.ID) isMember, err := org.IsOrgMember(ctx.User.ID)
if err != nil { if err != nil {
ctx.ServerError("IsOrgMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
return return
} else if !isMember { } else if !isMember {
ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name)) ctx.Error(http.StatusForbidden, "isMemberNot", fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))

+ 1
- 1
routers/api/v1/repo/label.go View File

@ -222,7 +222,7 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
label.Description = *form.Description label.Description = *form.Description
} }
if err := models.UpdateLabel(label); err != nil { if err := models.UpdateLabel(label); err != nil {
ctx.ServerError("UpdateLabel", err)
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToLabel(label)) ctx.JSON(http.StatusOK, convert.ToLabel(label))

+ 1
- 1
routers/api/v1/repo/milestone.go View File

@ -215,7 +215,7 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
} }
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil { if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
return return
} }
ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone)) ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))

+ 4
- 4
routers/api/v1/repo/pull.go View File

@ -725,7 +725,7 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) {
} }
if err = pr.LoadHeadRepo(); err != nil { if err = pr.LoadHeadRepo(); err != nil {
ctx.ServerError("LoadHeadRepo", err)
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
return return
} }
@ -885,7 +885,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName") ctx.NotFound("GetUserByName")
} else { } else {
ctx.ServerError("GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
} }
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
@ -929,7 +929,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User) permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil { if err != nil {
headGitRepo.Close() headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) { if !permBase.CanReadIssuesOrPulls(true) || !permBase.CanRead(models.UnitTypeCode) {
@ -948,7 +948,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
permHead, err := models.GetUserRepoPermission(headRepo, ctx.User) permHead, err := models.GetUserRepoPermission(headRepo, ctx.User)
if err != nil { if err != nil {
headGitRepo.Close() headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, nil, "", "" return nil, nil, nil, nil, "", ""
} }
if !permHead.CanRead(models.UnitTypeCode) { if !permHead.CanRead(models.UnitTypeCode) {

+ 3
- 3
routers/api/v1/repo/pull_review.go View File

@ -318,14 +318,14 @@ func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions)
if opts.CommitID == "" { if opts.CommitID == "" {
gitRepo, err := git.OpenRepository(pr.Issue.Repo.RepoPath()) gitRepo, err := git.OpenRepository(pr.Issue.Repo.RepoPath())
if err != nil { if err != nil {
ctx.ServerError("git.OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err)
return return
} }
defer gitRepo.Close() defer gitRepo.Close()
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName()) headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil { if err != nil {
ctx.ServerError("GetRefCommitID", err)
ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err)
return return
} }
@ -350,7 +350,7 @@ func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions)
0, // no reply 0, // no reply
opts.CommitID, opts.CommitID,
); err != nil { ); err != nil {
ctx.ServerError("CreateCodeComment", err)
ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
return return
} }
} }

+ 2
- 2
routers/api/v1/repo/release.go View File

@ -151,7 +151,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName) rel, err := models.GetRelease(ctx.Repo.Repository.ID, form.TagName)
if err != nil { if err != nil {
if !models.IsErrReleaseNotExist(err) { if !models.IsErrReleaseNotExist(err) {
ctx.ServerError("GetRelease", err)
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
return return
} }
// If target is not provided use default branch // If target is not provided use default branch
@ -195,7 +195,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
rel.Publisher = ctx.User rel.Publisher = ctx.User
if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, nil, true); err != nil { if err = releaseservice.UpdateReleaseOrCreatReleaseFromTag(ctx.User, ctx.Repo.GitRepo, rel, nil, true); err != nil {
ctx.ServerError("UpdateReleaseOrCreatReleaseFromTag", err)
ctx.Error(http.StatusInternalServerError, "UpdateReleaseOrCreatReleaseFromTag", err)
return return
} }
} }

+ 2
- 1
routers/api/v1/repo/repo.go View File

@ -244,6 +244,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR
IsPrivate: opt.Private, IsPrivate: opt.Private,
AutoInit: opt.AutoInit, AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch, DefaultBranch: opt.DefaultBranch,
TrustModel: models.ToTrustModel(opt.TrustModel),
}) })
if err != nil { if err != nil {
if models.IsErrRepoAlreadyExist(err) { if models.IsErrRepoAlreadyExist(err) {
@ -372,7 +373,7 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
if !ctx.User.IsAdmin { if !ctx.User.IsAdmin {
canCreate, err := org.CanCreateOrgRepo(ctx.User.ID) canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
if err != nil { if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
return return
} else if !canCreate { } else if !canCreate {
ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.") ctx.Error(http.StatusForbidden, "", "Given user is not allowed to create repository in organization.")

+ 1
- 1
routers/repo/issue.go View File

@ -1259,7 +1259,7 @@ func ViewIssue(ctx *context.Context) {
} }
ctx.Data["WillSign"] = false ctx.Data["WillSign"] = false
if ctx.User != nil { if ctx.User != nil {
sign, key, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
sign, key, _, err := pull.SignMerge(ctx.User, pull.BaseRepo.RepoPath(), pull.BaseBranch, pull.GetGitRefName())
ctx.Data["WillSign"] = sign ctx.Data["WillSign"] = sign
ctx.Data["SigningKey"] = key ctx.Data["SigningKey"] = key
if err != nil { if err != nil {

+ 1
- 0
routers/repo/repo.go View File

@ -238,6 +238,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) {
IsPrivate: form.Private || setting.Repository.ForcePrivate, IsPrivate: form.Private || setting.Repository.ForcePrivate,
DefaultBranch: form.DefaultBranch, DefaultBranch: form.DefaultBranch,
AutoInit: form.AutoInit, AutoInit: form.AutoInit,
TrustModel: models.ToTrustModel(form.TrustModel),
}) })
if err == nil { if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)

+ 25
- 0
routers/repo/setting.go View File

@ -51,6 +51,11 @@ func Settings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsOptions"] = true ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
signing, _ := models.SigningKey(ctx.Repo.Repository.RepoPath())
ctx.Data["SigningKeyAvailable"] = len(signing) > 0
ctx.Data["SigningSettings"] = setting.Repository.Signing
ctx.HTML(200, tplSettingsOptions) ctx.HTML(200, tplSettingsOptions)
} }
@ -318,6 +323,26 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings") ctx.Redirect(ctx.Repo.RepoLink + "/settings")
case "signing":
changed := false
trustModel := models.ToTrustModel(form.TrustModel)
if trustModel != repo.TrustModel {
repo.TrustModel = trustModel
changed = true
}
if changed {
if err := models.UpdateRepository(repo, false); err != nil {
ctx.ServerError("UpdateRepository", err)
return
}
}
log.Trace("Repository signing settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(ctx.Repo.RepoLink + "/settings")
case "admin": case "admin":
if !ctx.User.IsAdmin { if !ctx.User.IsAdmin {
ctx.Error(403) ctx.Error(403)

+ 14
- 5
services/pull/merge.go View File

@ -209,18 +209,23 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
outbuf.Reset() outbuf.Reset()
errbuf.Reset() errbuf.Reset()
sig := doer.NewGitSig()
committer := sig
// Determine if we should sign // Determine if we should sign
signArg := "" signArg := ""
if git.CheckGitVersionConstraint(">= 1.7.9") == nil { if git.CheckGitVersionConstraint(">= 1.7.9") == nil {
sign, keyID, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
sign, keyID, signer, _ := pr.SignMerge(doer, tmpBasePath, "HEAD", trackingBranch)
if sign { if sign {
signArg = "-S" + keyID signArg = "-S" + keyID
if pr.BaseRepo.GetTrustModel() == models.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
committer = signer
}
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil { } else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
signArg = "--no-gpg-sign" signArg = "--no-gpg-sign"
} }
} }
sig := doer.NewGitSig()
commitTimeStr := time.Now().Format(time.RFC3339) commitTimeStr := time.Now().Format(time.RFC3339)
// Because this may call hooks we should pass in the environment // Because this may call hooks we should pass in the environment
@ -228,8 +233,8 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
"GIT_AUTHOR_NAME="+sig.Name, "GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email, "GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr, "GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_NAME="+committer.Name,
"GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr, "GIT_COMMITTER_DATE="+commitTimeStr,
) )
@ -346,6 +351,10 @@ func rawMerge(pr *models.PullRequest, doer *models.User, mergeStyle models.Merge
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
} }
} else { } else {
if committer != sig {
// add trailer
message += fmt.Sprintf("\nCo-Authored-By: %s\nCo-Committed-By: %s\n", sig.String(), sig.String())
}
if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil { if err := git.NewCommand("commit", signArg, fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email), "-m", message).RunInDirTimeoutEnvPipeline(env, -1, tmpBasePath, &outbuf, &errbuf); err != nil {
log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) log.Error("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String()) return "", fmt.Errorf("git commit [%s:%s -> %s:%s]: %v\n%s\n%s", pr.HeadRepo.FullName(), pr.HeadBranch, pr.BaseRepo.FullName(), pr.BaseBranch, err, outbuf.String(), errbuf.String())
@ -526,7 +535,7 @@ func IsSignedIfRequired(pr *models.PullRequest, doer *models.User) (bool, error)
return true, nil return true, nil
} }
sign, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
sign, _, _, err := pr.SignMerge(doer, pr.BaseRepo.RepoPath(), pr.BaseBranch, pr.GetGitRefName())
return sign, err return sign, err
} }

+ 8
- 3
services/repository/push.go View File

@ -9,6 +9,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
@ -291,6 +292,10 @@ func commitRepoAction(repo *models.Repository, gitRepo *git.Repository, optsList
} }
} }
} }
// Update the is empty and default_branch columns
if err := models.UpdateRepositoryCols(repo, "default_branch", "is_empty"); err != nil {
return fmt.Errorf("UpdateRepositoryCols: %v", err)
}
} }
opType := models.ActionCommitRepo opType := models.ActionCommitRepo
@ -359,9 +364,9 @@ func commitRepoAction(repo *models.Repository, gitRepo *git.Repository, optsList
} }
} }
// Change repository empty status and update last updated time.
if err := models.UpdateRepository(repo, false); err != nil {
return fmt.Errorf("UpdateRepository: %v", err)
// Change repository last updated time.
if err := models.UpdateRepositoryUpdatedTime(repo.ID, time.Now()); err != nil {
return fmt.Errorf("UpdateRepositoryUpdatedTime: %v", err)
} }
if err := models.NotifyWatchers(actions...); err != nil { if err := models.NotifyWatchers(actions...); err != nil {

+ 15
- 4
services/wiki/wiki.go View File

@ -185,16 +185,22 @@ func updateWikiPage(doer *models.User, repo *models.Repository, oldWikiName, new
Message: message, Message: message,
} }
sign, signingKey, _ := repo.SignWikiCommit(doer)
committer := doer.NewGitSig()
sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
if sign { if sign {
commitTreeOpts.KeyID = signingKey commitTreeOpts.KeyID = signingKey
if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
committer = signer
}
} else { } else {
commitTreeOpts.NoGPGSign = true commitTreeOpts.NoGPGSign = true
} }
if hasMasterBranch { if hasMasterBranch {
commitTreeOpts.Parents = []string{"HEAD"} commitTreeOpts.Parents = []string{"HEAD"}
} }
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil { if err != nil {
log.Error("%v", err) log.Error("%v", err)
return err return err
@ -302,14 +308,19 @@ func DeleteWikiPage(doer *models.User, repo *models.Repository, wikiName string)
Parents: []string{"HEAD"}, Parents: []string{"HEAD"},
} }
sign, signingKey, _ := repo.SignWikiCommit(doer)
committer := doer.NewGitSig()
sign, signingKey, signer, _ := repo.SignWikiCommit(doer)
if sign { if sign {
commitTreeOpts.KeyID = signingKey commitTreeOpts.KeyID = signingKey
if repo.GetTrustModel() == models.CommitterTrustModel || repo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
committer = signer
}
} else { } else {
commitTreeOpts.NoGPGSign = true commitTreeOpts.NoGPGSign = true
} }
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), tree, commitTreeOpts)
commitHash, err := gitRepo.CommitTree(doer.NewGitSig(), committer, tree, commitTreeOpts)
if err != nil { if err != nil {
return err return err
} }

+ 13
- 0
templates/repo/create.tmpl View File

@ -167,6 +167,19 @@
<label for="default_branch">{{.i18n.Tr "repo.default_branch"}}</label> <label for="default_branch">{{.i18n.Tr "repo.default_branch"}}</label>
<input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}"> <input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}">
</div> </div>
<div class="inline field">
<label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
<div class="ui selection owner dropdown">
<input type="hidden" id="trust_model" name="trust_model" value="default" required>
<div class="default text">{{.i18n.Tr "repo.settings.trust_model"}}</div>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item" data-value="default">{{.i18n.Tr "repo.settings.trust_model.default"}}</div>
<div class="item" data-value="collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator"}}</div>
<div class="item" data-value="committer">{{.i18n.Tr "repo.settings.trust_model.committer"}}</div>
<div class="item" data-value="collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter"}}</div>
</div>
</div>
</div> </div>
<br/> <br/>

+ 46
- 0
templates/repo/settings/options.tmpl View File

@ -340,6 +340,52 @@
</form> </form>
</div> </div>
<h4 class="ui top attached header">
{{.i18n.Tr "repo.settings.signing_settings"}}
</h4>
<div class="ui attached segment">
<form class="ui form" method="post">
{{.CsrfTokenHtml}}
<input type="hidden" name="action" value="signing">
<div class="field">
<label>{{.i18n.Tr "repo.settings.trust_model"}}</label>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" id="trust_model_default" name="trust_model" {{if eq .Repository.TrustModel.String "default"}}checked="checked"{{end}} value="default">
<label for="trust_model_default">{{.i18n.Tr "repo.settings.trust_model.default"}}</label>
<p class="help">{{.i18n.Tr "repo.settings.trust_model.default.desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" id="trust_model_collaborator" name="trust_model" {{if eq .Repository.TrustModel.String "collaborator"}}checked="checked"{{end}} value="collaborator">
<label for="trust_model_collaborator">{{.i18n.Tr "repo.settings.trust_model.collaborator.long"}}</label>
<p class="help">{{.i18n.Tr "repo.settings.trust_model.collaborator.desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="trust_model" id="trust_model_committer" {{if eq .Repository.TrustModel.String "committer"}}checked="checked"{{end}} value="committer">
<label for="trust_model_committer">{{.i18n.Tr "repo.settings.trust_model.committer.long"}}</label>
<p class="help">{{.i18n.Tr "repo.settings.trust_model.committer.desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="trust_model" id="trust_model_collaboratorcommitter" {{if eq .Repository.TrustModel.String "collaboratorcommitter"}}checked="checked"{{end}} value="collaboratorcommitter">
<label for="trust_model_collaboratorcommitter">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.long"}}</label>
<p class="help">{{.i18n.Tr "repo.settings.trust_model.collaboratorcommitter.desc"}}</p>
</div>
</div>
</div>
<div class="ui divider"></div>
<div class="field">
<button class="ui green button">{{$.i18n.Tr "repo.settings.update_settings"}}</button>
</div>
</form>
</div>
{{if .IsAdmin}} {{if .IsAdmin}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "repo.settings.admin_settings"}} {{.i18n.Tr "repo.settings.admin_settings"}}

+ 14
- 0
templates/swagger/v1_json.tmpl View File

@ -11937,6 +11937,17 @@
"description": "Readme of the repository to create", "description": "Readme of the repository to create",
"type": "string", "type": "string",
"x-go-name": "Readme" "x-go-name": "Readme"
},
"trust_model": {
"description": "TrustModel of the repository",
"type": "string",
"enum": [
"default",
"collaborator",
"committer",
"collaboratorcommitter"
],
"x-go-name": "TrustModel"
} }
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "x-go-package": "code.gitea.io/gitea/modules/structs"
@ -13824,6 +13835,9 @@
"type": "string", "type": "string",
"x-go-name": "LatestCommentURL" "x-go-name": "LatestCommentURL"
}, },
"state": {
"$ref": "#/definitions/StateType"
},
"title": { "title": {
"type": "string", "type": "string",
"x-go-name": "Title" "x-go-name": "Title"

+ 10
- 3
web_src/js/features/migration.js View File

@ -2,7 +2,8 @@ const $service = $('#service_type');
const $user = $('#auth_username'); const $user = $('#auth_username');
const $pass = $('#auth_password'); const $pass = $('#auth_password');
const $token = $('#auth_token'); const $token = $('#auth_token');
const $items = $('#migrate_items').find('.field');
const $mirror = $('#mirror');
const $items = $('#migrate_items').find('input[type=checkbox]');
export default function initMigration() { export default function initMigration() {
checkAuth(); checkAuth();
@ -10,6 +11,7 @@ export default function initMigration() {
$user.on('keyup', () => {checkItems(false)}); $user.on('keyup', () => {checkItems(false)});
$pass.on('keyup', () => {checkItems(false)}); $pass.on('keyup', () => {checkItems(false)});
$token.on('keyup', () => {checkItems(true)}); $token.on('keyup', () => {checkItems(true)});
$mirror.on('change', () => {checkItems(true)});
const $cloneAddr = $('#clone_addr'); const $cloneAddr = $('#clone_addr');
$cloneAddr.on('change', () => { $cloneAddr.on('change', () => {
@ -34,8 +36,13 @@ function checkItems(tokenAuth) {
enableItems = $user.val() !== '' || $pass.val() !== ''; enableItems = $user.val() !== '' || $pass.val() !== '';
} }
if (enableItems && $service.val() > 1) { if (enableItems && $service.val() > 1) {
$items.removeClass('disabled');
if ($mirror.is(':checked')) {
$items.not('[name="wiki"]').attr('disabled', true);
$items.filter('[name="wiki"]').attr('disabled', false);
return;
}
$items.attr('disabled', false);
} else { } else {
$items.addClass('disabled');
$items.attr('disabled', true);
} }
} }

+ 20
- 1
web_src/js/index.js View File

@ -1484,7 +1484,26 @@ function setCommentSimpleMDE($editArea) {
spellChecker: false, spellChecker: false,
toolbar: ['bold', 'italic', 'strikethrough', '|', toolbar: ['bold', 'italic', 'strikethrough', '|',
'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|', 'heading-1', 'heading-2', 'heading-3', 'heading-bigger', 'heading-smaller', '|',
'code', 'quote', '|',
'code', 'quote', '|', {
name: 'checkbox-empty',
action(e) {
const cm = e.codemirror;
cm.replaceSelection(`\n- [ ] ${cm.getSelection()}`);
cm.focus();
},
className: 'fa fa-square-o',
title: 'Add Checkbox (empty)',
},
{
name: 'checkbox-checked',
action(e) {
const cm = e.codemirror;
cm.replaceSelection(`\n- [x] ${cm.getSelection()}`);
cm.focus();
},
className: 'fa fa-check-square-o',
title: 'Add Checkbox (checked)',
}, '|',
'unordered-list', 'ordered-list', '|', 'unordered-list', 'ordered-list', '|',
'link', 'image', 'table', 'horizontal-rule', '|', 'link', 'image', 'table', 'horizontal-rule', '|',
'clean-block', '|', 'clean-block', '|',

Loading…
Cancel
Save