Browse Source

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

for-closed-social
欧醚 3 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.)
SIGNING_NAME =
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
; Either:
; - 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
- `always`: Always sign
- 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.
- `CRUD_ACTIONS`: **pubkey, twofa, parentsigned**: \[never, pubkey, twofa, parentsigned, always\]: Sign CRUD actions.
- 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()
e = oldCommits.Front()
)
memberMap := map[int64]bool{}
keyMap := map[string]bool{}
for e != nil {
c := e.Value.(UserCommit)
@ -840,7 +840,7 @@ func ParseCommitsWithSignature(oldCommits *list.List, repository *Repository) *l
Verification: ParseCommitWithSignature(c.Commit),
}
_ = CalculateTrustStatus(signCommit.Verification, repository, &memberMap)
_ = CalculateTrustStatus(signCommit.Verification, repository, &keyMap)
newCommits.PushBack(signCommit)
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
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
}

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

@ -237,6 +237,8 @@ var migrations = []Migration{
NewMigration("add primary key to repo_topic", addPrimaryKeyToRepoTopic),
// v151 -> v152
NewMigration("set default password algorithm to Argon2", setDefaultPasswordToArgon2),
// v152 -> v153
NewMigration("add TrustModel field to Repository", addTrustModelToRepository),
}
// 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 {
result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL()
result.Subject.State = n.Issue.State()
comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil {
result.Subject.LatestCommentURL = comment.APIURL()
@ -364,6 +365,7 @@ func (n *Notification) APIFormat() *api.NotificationThread {
if n.Issue != nil {
result.Subject.Title = n.Issue.Title
result.Subject.URL = n.Issue.APIURL()
result.Subject.State = n.Issue.State()
comment, err := n.Issue.GetLastComment()
if err == nil && comment != nil {
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
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 {
log.Error("Unable to get Base Repo for pull request")
return false, "", err
return false, "", nil, err
}
repo := pr.BaseRepo
signingKey := signingKey(repo.RepoPath())
signingKey, signer := SigningKey(repo.RepoPath())
if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
}
rules := signingModeFromStrings(setting.Repository.Signing.Merges)
@ -31,101 +31,101 @@ Loop:
for _, rule := range rules {
switch rule {
case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
return false, "", err
return false, "", nil, err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
}
case approved:
protectedBranch, err := GetProtectedBranchBy(repo.ID, pr.BaseBranch)
if err != nil {
return false, "", err
return false, "", nil, err
}
if protectedBranch == nil {
return false, "", &ErrWontSign{approved}
return false, "", nil, &ErrWontSign{approved}
}
if protectedBranch.GetGrantedApprovalsCount(pr) < 1 {
return false, "", &ErrWontSign{approved}
return false, "", nil, &ErrWontSign{approved}
}
case baseSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, "", err
return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(baseCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, "", &ErrWontSign{baseSigned}
return false, "", nil, &ErrWontSign{baseSigned}
}
case headSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, "", err
return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, "", &ErrWontSign{headSigned}
return false, "", nil, &ErrWontSign{headSigned}
}
case commitsSigned:
if gitRepo == nil {
gitRepo, err = git.OpenRepository(tmpBasePath)
if err != nil {
return false, "", err
return false, "", nil, err
}
defer gitRepo.Close()
}
commit, err := gitRepo.GetCommit(headCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
verification := ParseCommitWithSignature(commit)
if !verification.Verified {
return false, "", &ErrWontSign{commitsSigned}
return false, "", nil, &ErrWontSign{commitsSigned}
}
// need to work out merge-base
mergeBaseCommit, _, err := gitRepo.GetMergeBase("", baseCommit, headCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
commitList, err := commit.CommitsBeforeUntil(mergeBaseCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
for e := commitList.Front(); e != nil; e = e.Next() {
commit = e.Value.(*git.Commit)
verification := ParseCommitWithSignature(commit)
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
)
// 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.
type Repository struct {
ID int64 `xorm:"pk autoincr"`
@ -198,6 +239,8 @@ type Repository struct {
CloseIssuesViaCommitInAnyBranch bool `xorm:"NOT NULL DEFAULT false"`
Topics []string `xorm:"TEXT JSON"`
TrustModel TrustModelType
// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
Avatar string `xorm:"VARCHAR(64)"`
@ -1038,6 +1081,7 @@ type CreateRepoOptions struct {
IsMirror bool
AutoInit bool
Status RepositoryStatus
TrustModel TrustModelType
}
// GetRepoInitFile returns repository init files
@ -2383,6 +2427,18 @@ func UpdateRepositoryCols(repo *Repository, cols ...string) error {
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
func DoctorUserStarNum() (err error) {
const batchSize = 100

+ 51
- 42
models/repo_sign.go View File

@ -31,7 +31,7 @@ const (
func signingModeFromStrings(modeStrings []string) []signingMode {
returnable := make([]signingMode, 0, len(modeStrings))
for _, mode := range modeStrings {
signMode := signingMode(strings.ToLower(mode))
signMode := signingMode(strings.ToLower(strings.TrimSpace(mode)))
switch signMode {
case never:
return []signingMode{never}
@ -59,9 +59,10 @@ func signingModeFromStrings(modeStrings []string) []signingMode {
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" {
return ""
return "", nil
}
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)
sign, valid := git.ParseBool(strings.TrimSpace(value))
if !sign || !valid {
return ""
return "", nil
}
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
func PublicSigningKey(repoPath string) (string, error) {
signingKey := signingKey(repoPath)
signingKey, _ := SigningKey(repoPath)
if signingKey == "" {
return "", nil
}
@ -96,143 +105,143 @@ func PublicSigningKey(repoPath string) (string, error) {
}
// 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)
signingKey := signingKey(repoPath)
signingKey, sig := SigningKey(repoPath)
if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
return false, "", err
return false, "", nil, err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
}
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
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)
signingKey := signingKey(repo.WikiPath())
signingKey, sig := SigningKey(repo.WikiPath())
if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
return false, "", err
return false, "", nil, err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(repo.WikiPath())
if err != nil {
return false, "", err
return false, "", nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit("HEAD")
if err != nil {
return false, "", err
return false, "", nil, err
}
if commit.Signature == nil {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
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
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)
signingKey := signingKey(repo.RepoPath())
signingKey, sig := SigningKey(repo.RepoPath())
if signingKey == "" {
return false, "", &ErrWontSign{noKey}
return false, "", nil, &ErrWontSign{noKey}
}
Loop:
for _, rule := range rules {
switch rule {
case never:
return false, "", &ErrWontSign{never}
return false, "", nil, &ErrWontSign{never}
case always:
break Loop
case pubkey:
keys, err := ListGPGKeys(u.ID, ListOptions{})
if err != nil {
return false, "", err
return false, "", nil, err
}
if len(keys) == 0 {
return false, "", &ErrWontSign{pubkey}
return false, "", nil, &ErrWontSign{pubkey}
}
case twofa:
twofaModel, err := GetTwoFactorByUID(u.ID)
if err != nil && !IsErrTwoFactorNotEnrolled(err) {
return false, "", err
return false, "", nil, err
}
if twofaModel == nil {
return false, "", &ErrWontSign{twofa}
return false, "", nil, &ErrWontSign{twofa}
}
case parentSigned:
gitRepo, err := git.OpenRepository(tmpBasePath)
if err != nil {
return false, "", err
return false, "", nil, err
}
defer gitRepo.Close()
commit, err := gitRepo.GetCommit(parentCommit)
if err != nil {
return false, "", err
return false, "", nil, err
}
if commit.Signature == nil {
return false, "", &ErrWontSign{parentSigned}
return false, "", nil, &ErrWontSign{parentSigned}
}
verification := ParseCommitWithSignature(commit)
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
Avatar bool
Labels bool
TrustModel string
}
// Validate validates the fields
@ -142,6 +143,9 @@ type RepoSettingForm struct {
EnableIssueDependencies bool
IsArchived bool
// Signing Settings
TrustModel string
// Admin settings
EnableHealthCheck 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
}
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
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
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()
if err != nil {
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
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_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_NAME="+committer.Name,
"GIT_COMMITTER_EMAIL="+committer.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
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)
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")
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
}
baseURL := u.Scheme + "://" + u.Host
fields := strings.Split(u.Path, "/")
oldOwner := fields[1]
oldName := strings.TrimSuffix(fields[2], ".git")
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
@ -74,7 +75,7 @@ type GithubDownloaderV3 struct {
}
// 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{
userName: userName,
password: password,
@ -98,6 +99,9 @@ func NewGithubDownloaderV3(ctx context.Context, userName, password, token, repoO
client = oauth2.NewClient(downloader.ctx, ts)
}
downloader.client = github.NewClient(client)
if baseURL != "https://github.com" {
downloader.client, _ = github.NewEnterpriseClient(baseURL, baseURL, client)
}
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) {
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()
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 {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(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_EMAIL="+authorSig.Email,
"GIT_AUTHOR_DATE="+authorDate.Format(time.RFC3339),
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
"GIT_COMMITTER_DATE="+committerDate.Format(time.RFC3339),
)
@ -217,14 +215,32 @@ func (t *TemporaryUploadRepository) CommitTreeWithDate(author, committer *models
// Determine if we should sign
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 {
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 {
args = append(args, "--no-gpg-sign")
}
}
env = append(env,
"GIT_COMMITTER_NAME="+committerSig.Name,
"GIT_COMMITTER_EMAIL="+committerSig.Email,
)
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
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 {
_, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
_, _, _, err := repo.SignCRUDAction(doer, repo.RepoPath(), opts.OldBranch)
if err != nil {
if !models.IsErrWontSign(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,
Status: opts.Status,
IsEmpty: !opts.AutoInit,
TrustModel: opts.TrustModel,
}
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,
IsFsckEnabled: templateRepo.IsFsckEnabled,
TemplateID: templateRepo.ID,
TrustModel: templateRepo.TrustModel,
}
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_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
committerName := sig.Name
committerEmail := sig.Email
if stdout, err := git.NewCommand("add", "--all").
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 {
sign, keyID, _ := models.SignInitialCommit(tmpPath, u)
sign, keyID, signer, _ := models.SignInitialCommit(tmpPath, u)
if sign {
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 {
args = append(args, "--no-gpg-sign")
}
}
env = append(env,
"GIT_COMMITTER_NAME="+committerName,
"GIT_COMMITTER_EMAIL="+committerEmail,
)
if stdout, err := git.NewCommand(args...).
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunInDirWithEnv(tmpPath, env); err != nil {

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

@ -83,13 +83,14 @@ var (
} `ini:"repository.issue"`
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"`
}{
DetectedCharsetsOrder: []string{
@ -209,21 +210,23 @@ var (
// Signing settings
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
@ -268,6 +271,13 @@ func newRepository() {
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))
for _, charset := range Repository.DetectedCharsetsOrder {
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)
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

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

@ -117,6 +117,9 @@ type CreateRepoOption struct {
Readme string `json:"readme"`
// DefaultBranch of the repository (used when initializes and in template)
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

+ 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.add_reviewer_title=Überprüfung anfordern
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.create=Issue erstellen
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_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.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_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.
@ -2103,7 +2118,9 @@ auths.admin_filter=Admin-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.verify_group_membership=Gruppenmitgliedschaft in LDAP überprüfen
auths.group_search_base=Gruppensuche Basisdomainname
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.ms_ad_sa=MS-AD-Suchattribute
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_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.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_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.

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

@ -10,7 +10,7 @@ link_account=Bağlantı hesabı
register=Üye Ol
website=Web sitesi
version=Sürüm
powered_by=%s tarafından desteklenmektedir
powered_by=%s tarafından desteklenen
page=Sayfa
template=Şablon
language=Dil
@ -771,7 +771,7 @@ branch=Dal
tree=Ağaç
clear_ref='Geçerli referansı temizle'
filter_branch_and_tag=Dal veya biçim imini filtrele
branches=Dallar
branches=Dal
tags=Biçim İmleri
issues=Konular
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
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.search=İşlemeleri ara…
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.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_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_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_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_project=`(silindi)`
issues.self_assign_at=`%s kendini atadı`
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_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.open_tab=%d açık
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.change_target_branch_at='hedef dal <b>%s</b> adresinden <b>%s</b>%s adresine değiştirildi'
pulls.tab_conversation=Sohbet
pulls.tab_commits=İşlemeler
pulls.tab_commits=İşleme
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.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_n=Birleştirilmiş Çekme İ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_n=%d kullanıcı
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_author_1=%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_file_1=%d dosya
activity.git_stats_file_n=%d dosya
activity.git_stats_files_changed_1=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_n=%d ekleme ve
activity.git_stats_addition_n=%d ekleme
activity.git_stats_and_deletions=ve
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.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_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.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_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.

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

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

+ 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) {
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
defer gitRepo.Close()
@ -75,7 +75,7 @@ func getCommit(ctx *context.APIContext, identifier string) {
json, err := convert.ToCommit(ctx.Repo.Repository, commit, nil)
if err != nil {
ctx.ServerError("toCommit", err)
ctx.Error(http.StatusInternalServerError, "toCommit", err)
return
}
ctx.JSON(http.StatusOK, json)
@ -129,7 +129,7 @@ func GetAllCommits(ctx *context.APIContext) {
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
if err != nil {
ctx.ServerError("OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return
}
defer gitRepo.Close()
@ -150,20 +150,20 @@ func GetAllCommits(ctx *context.APIContext) {
// no sha supplied - use default branch
head, err := gitRepo.GetHEADBranch()
if err != nil {
ctx.ServerError("GetHEADBranch", err)
ctx.Error(http.StatusInternalServerError, "GetHEADBranch", err)
return
}
baseCommit, err = gitRepo.GetBranchCommit(head.Name)
if err != nil {
ctx.ServerError("GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
} else {
// get commit specified by sha
baseCommit, err = gitRepo.GetCommit(sha)
if err != nil {
ctx.ServerError("GetCommit", err)
ctx.Error(http.StatusInternalServerError, "GetCommit", err)
return
}
}
@ -171,7 +171,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Total commit count
commitsCountTotal, err := baseCommit.CommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
ctx.Error(http.StatusInternalServerError, "GetCommitsCount", err)
return
}
@ -180,7 +180,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Query commits
commits, err := baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize)
if err != nil {
ctx.ServerError("CommitsByRange", err)
ctx.Error(http.StatusInternalServerError, "CommitsByRange", err)
return
}
@ -195,7 +195,7 @@ func GetAllCommits(ctx *context.APIContext) {
// Create json struct
apiCommits[i], err = convert.ToCommit(ctx.Repo.Repository, commit, userCache)
if err != nil {
ctx.ServerError("toCommit", err)
ctx.Error(http.StatusInternalServerError, "toCommit", err)
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)
if err != nil {
ctx.ServerError("IsOrgMember", err)
ctx.Error(http.StatusInternalServerError, "IsOrgMember", err)
return
} else if !isMember {
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
}
if err := models.UpdateLabel(label); err != nil {
ctx.ServerError("UpdateLabel", err)
ctx.Error(http.StatusInternalServerError, "UpdateLabel", err)
return
}
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 {
ctx.ServerError("UpdateMilestone", err)
ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
return
}
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 {
ctx.ServerError("LoadHeadRepo", err)
ctx.Error(http.StatusInternalServerError, "LoadHeadRepo", err)
return
}
@ -885,7 +885,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
if models.IsErrUserNotExist(err) {
ctx.NotFound("GetUserByName")
} else {
ctx.ServerError("GetUserByName", err)
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
}
return nil, nil, nil, nil, "", ""
}
@ -929,7 +929,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
permBase, err := models.GetUserRepoPermission(baseRepo, ctx.User)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
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)
if err != nil {
headGitRepo.Close()
ctx.ServerError("GetUserRepoPermission", err)
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return nil, nil, nil, nil, "", ""
}
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 == "" {
gitRepo, err := git.OpenRepository(pr.Issue.Repo.RepoPath())
if err != nil {
ctx.ServerError("git.OpenRepository", err)
ctx.Error(http.StatusInternalServerError, "git.OpenRepository", err)
return
}
defer gitRepo.Close()
headCommitID, err := gitRepo.GetRefCommitID(pr.GetGitRefName())
if err != nil {
ctx.ServerError("GetRefCommitID", err)
ctx.Error(http.StatusInternalServerError, "GetRefCommitID", err)
return
}
@ -350,7 +350,7 @@ func CreatePullReview(ctx *context.APIContext, opts api.CreatePullReviewOptions)
0, // no reply
opts.CommitID,
); err != nil {
ctx.ServerError("CreateCodeComment", err)
ctx.Error(http.StatusInternalServerError, "CreateCodeComment", err)
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)
if err != nil {
if !models.IsErrReleaseNotExist(err) {
ctx.ServerError("GetRelease", err)
ctx.Error(http.StatusInternalServerError, "GetRelease", err)
return
}
// If target is not provided use default branch
@ -195,7 +195,7 @@ func CreateRelease(ctx *context.APIContext, form api.CreateReleaseOption) {
rel.Publisher = ctx.User
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
}
}

+ 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,
AutoInit: opt.AutoInit,
DefaultBranch: opt.DefaultBranch,
TrustModel: models.ToTrustModel(opt.TrustModel),
})
if err != nil {
if models.IsErrRepoAlreadyExist(err) {
@ -372,7 +373,7 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
if !ctx.User.IsAdmin {
canCreate, err := org.CanCreateOrgRepo(ctx.User.ID)
if err != nil {
ctx.ServerError("CanCreateOrgRepo", err)
ctx.Error(http.StatusInternalServerError, "CanCreateOrgRepo", err)
return
} else if !canCreate {
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
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["SigningKey"] = key
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,
DefaultBranch: form.DefaultBranch,
AutoInit: form.AutoInit,
TrustModel: models.ToTrustModel(form.TrustModel),
})
if err == nil {
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["PageIsSettingsOptions"] = true
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)
}
@ -318,6 +323,26 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
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":
if !ctx.User.IsAdmin {
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()
errbuf.Reset()
sig := doer.NewGitSig()
committer := sig
// Determine if we should sign
signArg := ""
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 {
signArg = "-S" + keyID
if pr.BaseRepo.GetTrustModel() == models.CommitterTrustModel || pr.BaseRepo.GetTrustModel() == models.CollaboratorCommitterTrustModel {
committer = signer
}
} else if git.CheckGitVersionConstraint(">= 2.0.0") == nil {
signArg = "--no-gpg-sign"
}
}
sig := doer.NewGitSig()
commitTimeStr := time.Now().Format(time.RFC3339)
// 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_EMAIL="+sig.Email,
"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,
)
@ -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())
}
} 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 {
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())
@ -526,7 +535,7 @@ func IsSignedIfRequired(pr *models.PullRequest, doer *models.User) (bool, error)
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
}

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

@ -9,6 +9,7 @@ import (
"encoding/json"
"fmt"
"strings"
"time"
"code.gitea.io/gitea/models"
"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
@ -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 {

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

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

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

@ -167,6 +167,19 @@
<label for="default_branch">{{.i18n.Tr "repo.default_branch"}}</label>
<input id="default_branch" name="default_branch" value="{{.default_branch}}" placeholder="{{.default_branch}}">
</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>
<br/>

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

@ -340,6 +340,52 @@
</form>
</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}}
<h4 class="ui top attached header">
{{.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",
"type": "string",
"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"
@ -13824,6 +13835,9 @@
"type": "string",
"x-go-name": "LatestCommentURL"
},
"state": {
"$ref": "#/definitions/StateType"
},
"title": {
"type": "string",
"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 $pass = $('#auth_password');
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() {
checkAuth();
@ -10,6 +11,7 @@ export default function initMigration() {
$user.on('keyup', () => {checkItems(false)});
$pass.on('keyup', () => {checkItems(false)});
$token.on('keyup', () => {checkItems(true)});
$mirror.on('change', () => {checkItems(true)});
const $cloneAddr = $('#clone_addr');
$cloneAddr.on('change', () => {
@ -34,8 +36,13 @@ function checkItems(tokenAuth) {
enableItems = $user.val() !== '' || $pass.val() !== '';
}
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 {
$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,
toolbar: ['bold', 'italic', 'strikethrough', '|',
'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', '|',
'link', 'image', 'table', 'horizontal-rule', '|',
'clean-block', '|',

Loading…
Cancel
Save