Merge pull request '[gitea] week 2024-19 cherry pick (gitea-github/main -> forgejo)' (#3639) from earl-warren/wcp/2024-19 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3639 Reviewed-by: Gergely Nagy <algernon@noreply.codeberg.org>
This commit is contained in:
		
				commit
				
					
						a2c8fe0370
					
				
			
		
					 89 changed files with 911 additions and 404 deletions
				
			
		| 
						 | 
				
			
			@ -4,6 +4,7 @@ reportUnusedDisableDirectives: true
 | 
			
		|||
ignorePatterns:
 | 
			
		||||
  - /web_src/js/vendor
 | 
			
		||||
  - /web_src/fomantic
 | 
			
		||||
  - /public/assets/js
 | 
			
		||||
 | 
			
		||||
parserOptions:
 | 
			
		||||
  sourceType: module
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -767,7 +767,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
 | 
			
		|||
.PHONY: generate-go
 | 
			
		||||
generate-go: $(TAGS_PREREQ)
 | 
			
		||||
	@echo "Running go generate..."
 | 
			
		||||
	@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./...
 | 
			
		||||
	@CC= GOOS= GOARCH= CGO_ENABLED=0 $(GO) generate -tags '$(TAGS)' ./...
 | 
			
		||||
 | 
			
		||||
.PHONY: merge-locales
 | 
			
		||||
merge-locales:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1471,7 +1471,7 @@ LEVEL = Info
 | 
			
		|||
;; Batch size to send for batched queues
 | 
			
		||||
;BATCH_LENGTH = 20
 | 
			
		||||
;;
 | 
			
		||||
;; Connection string for redis queues this will store the redis or redis-cluster connection string.
 | 
			
		||||
;; Connection string for redis queues this will store the redis (or Redis cluster) connection string.
 | 
			
		||||
;; When `TYPE` is `persistable-channel`, this provides a directory for the underlying leveldb
 | 
			
		||||
;; or additional options of the form `leveldb://path/to/db?option=value&....`, and will override `DATADIR`.
 | 
			
		||||
;CONN_STR = "redis://127.0.0.1:6379/0"
 | 
			
		||||
| 
						 | 
				
			
			@ -1756,9 +1756,8 @@ LEVEL = Info
 | 
			
		|||
;; For "memory" only, GC interval in seconds, default is 60
 | 
			
		||||
;INTERVAL = 60
 | 
			
		||||
;;
 | 
			
		||||
;; For "redis", "redis-cluster" and "memcache", connection host address
 | 
			
		||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
 | 
			
		||||
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
 | 
			
		||||
;; For "redis" and "memcache", connection host address
 | 
			
		||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
 | 
			
		||||
;; memcache: `127.0.0.1:11211`
 | 
			
		||||
;; twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
 | 
			
		||||
;HOST =
 | 
			
		||||
| 
						 | 
				
			
			@ -1788,15 +1787,14 @@ LEVEL = Info
 | 
			
		|||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 | 
			
		||||
;;
 | 
			
		||||
;; Either "memory", "file", "redis", "redis-cluster", "db", "mysql", "couchbase", "memcache" or "postgres"
 | 
			
		||||
;; Either "memory", "file", "redis", "db", "mysql", "couchbase", "memcache" or "postgres"
 | 
			
		||||
;; Default is "memory". "db" will reuse the configuration in [database]
 | 
			
		||||
;PROVIDER = memory
 | 
			
		||||
;;
 | 
			
		||||
;; Provider config options
 | 
			
		||||
;; memory: doesn't have any config yet
 | 
			
		||||
;; file: session file path, e.g. `data/sessions`
 | 
			
		||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
 | 
			
		||||
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
 | 
			
		||||
;; redis: `redis://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` (or `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s` for a Redis cluster)
 | 
			
		||||
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table`
 | 
			
		||||
;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
 | 
			
		||||
;;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -57,6 +57,7 @@ require (
 | 
			
		|||
	github.com/google/uuid v1.6.0
 | 
			
		||||
	github.com/gorilla/feeds v1.1.2
 | 
			
		||||
	github.com/gorilla/sessions v1.2.2
 | 
			
		||||
	github.com/h2non/gock v1.2.0
 | 
			
		||||
	github.com/hashicorp/go-version v1.6.0
 | 
			
		||||
	github.com/hashicorp/golang-lru/v2 v2.0.7
 | 
			
		||||
	github.com/huandu/xstrings v1.4.0
 | 
			
		||||
| 
						 | 
				
			
			@ -203,6 +204,7 @@ require (
 | 
			
		|||
	github.com/gorilla/handlers v1.5.2 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.8.1 // indirect
 | 
			
		||||
	github.com/gorilla/securecookie v1.1.2 // indirect
 | 
			
		||||
	github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
 | 
			
		||||
	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
 | 
			
		||||
	github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
 | 
			
		||||
	github.com/hashicorp/hcl v1.0.0 // indirect
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										6
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -474,6 +474,10 @@ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE
 | 
			
		|||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 | 
			
		||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
 | 
			
		||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
 | 
			
		||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
 | 
			
		||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
 | 
			
		||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
 | 
			
		||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
 | 
			
		||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
 | 
			
		||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 | 
			
		||||
| 
						 | 
				
			
			@ -638,6 +642,8 @@ github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
 | 
			
		|||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
 | 
			
		||||
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
 | 
			
		||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
 | 
			
		||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
 | 
			
		||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 | 
			
		||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
 | 
			
		||||
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
 | 
			
		||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -362,36 +362,16 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
 | 
			
		|||
 | 
			
		||||
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
 | 
			
		||||
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) {
 | 
			
		||||
	type result struct {
 | 
			
		||||
		Index int64
 | 
			
		||||
		SHA   string
 | 
			
		||||
	}
 | 
			
		||||
	getBase := func() *xorm.Session {
 | 
			
		||||
		return db.GetEngine(ctx).Table(&CommitStatus{}).Where("repo_id = ?", repoID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := timeutil.TimeStampNow().AddDuration(-before)
 | 
			
		||||
	results := make([]result, 0, 10)
 | 
			
		||||
 | 
			
		||||
	sess := getBase().And("updated_unix >= ?", start).
 | 
			
		||||
		Select("max( `index` ) as `index`, sha").
 | 
			
		||||
		GroupBy("context_hash, sha").OrderBy("max( `index` ) desc")
 | 
			
		||||
 | 
			
		||||
	err := sess.Find(&results)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	var contexts []string
 | 
			
		||||
	if err := db.GetEngine(ctx).Table("commit_status").
 | 
			
		||||
		Where("repo_id = ?", repoID).And("updated_unix >= ?", start).
 | 
			
		||||
		Cols("context").Distinct().Find(&contexts); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contexts := make([]string, 0, len(results))
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
	return contexts, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	conds := make([]builder.Cond, 0, len(results))
 | 
			
		||||
	for _, result := range results {
 | 
			
		||||
		conds = append(conds, builder.Eq{"`index`": result.Index, "sha": result.SHA})
 | 
			
		||||
	}
 | 
			
		||||
	return contexts, getBase().And(builder.Or(conds...)).Select("context").Find(&contexts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCommitStatusOptions holds options for creating a CommitStatus
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,11 +5,15 @@ package git_test
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	git_model "code.gitea.io/gitea/models/git"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/gitrepo"
 | 
			
		||||
	"code.gitea.io/gitea/modules/structs"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
| 
						 | 
				
			
			@ -183,3 +187,55 @@ func Test_CalcCommitStatus(t *testing.T) {
 | 
			
		|||
		assert.Equal(t, kase.expected, git_model.CalcCommitStatus(kase.statuses))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFindRepoRecentCommitStatusContexts(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
 | 
			
		||||
	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 | 
			
		||||
	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
	gitRepo, err := gitrepo.OpenRepository(git.DefaultContext, repo2)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	defer gitRepo.Close()
 | 
			
		||||
 | 
			
		||||
	commit, err := gitRepo.GetBranchCommit(repo2.DefaultBranch)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_, err := db.DeleteByBean(db.DefaultContext, &git_model.CommitStatus{
 | 
			
		||||
			RepoID:    repo2.ID,
 | 
			
		||||
			CreatorID: user2.ID,
 | 
			
		||||
			SHA:       commit.ID.String(),
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
 | 
			
		||||
		Repo:    repo2,
 | 
			
		||||
		Creator: user2,
 | 
			
		||||
		SHA:     commit.ID,
 | 
			
		||||
		CommitStatus: &git_model.CommitStatus{
 | 
			
		||||
			State:     structs.CommitStatusFailure,
 | 
			
		||||
			TargetURL: "https://example.com/tests/",
 | 
			
		||||
			Context:   "compliance/lint-backend",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	err = git_model.NewCommitStatus(db.DefaultContext, git_model.NewCommitStatusOptions{
 | 
			
		||||
		Repo:    repo2,
 | 
			
		||||
		Creator: user2,
 | 
			
		||||
		SHA:     commit.ID,
 | 
			
		||||
		CommitStatus: &git_model.CommitStatus{
 | 
			
		||||
			State:     structs.CommitStatusSuccess,
 | 
			
		||||
			TargetURL: "https://example.com/tests/",
 | 
			
		||||
			Context:   "compliance/lint-backend",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	contexts, err := git_model.FindRepoRecentCommitStatusContexts(db.DefaultContext, repo2.ID, time.Hour)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	if assert.Len(t, contexts, 1) {
 | 
			
		||||
		assert.Equal(t, "compliance/lint-backend", contexts[0])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -450,65 +450,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateIssueByAPI updates all allowed fields of given issue.
 | 
			
		||||
// If the issue status is changed a statusChangeComment is returned
 | 
			
		||||
// similarly if the title is changed the titleChanged bool is set to true
 | 
			
		||||
func UpdateIssueByAPI(ctx context.Context, issue *Issue, doer *user_model.User) (statusChangeComment *Comment, titleChanged bool, err error) {
 | 
			
		||||
	ctx, committer, err := db.TxContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	defer committer.Close()
 | 
			
		||||
 | 
			
		||||
	if err := issue.LoadRepo(ctx); err != nil {
 | 
			
		||||
		return nil, false, fmt.Errorf("loadRepo: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Reload the issue
 | 
			
		||||
	currentIssue, err := GetIssueByID(ctx, issue.ID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sess := db.GetEngine(ctx).ID(issue.ID)
 | 
			
		||||
	cols := []string{"name", "content", "milestone_id", "priority", "deadline_unix", "is_locked"}
 | 
			
		||||
	if issue.NoAutoTime {
 | 
			
		||||
		cols = append(cols, "updated_unix")
 | 
			
		||||
		sess.NoAutoTime()
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := sess.Cols(cols...).Update(issue); err != nil {
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	titleChanged = currentIssue.Title != issue.Title
 | 
			
		||||
	if titleChanged {
 | 
			
		||||
		opts := &CreateCommentOptions{
 | 
			
		||||
			Type:     CommentTypeChangeTitle,
 | 
			
		||||
			Doer:     doer,
 | 
			
		||||
			Repo:     issue.Repo,
 | 
			
		||||
			Issue:    issue,
 | 
			
		||||
			OldTitle: currentIssue.Title,
 | 
			
		||||
			NewTitle: issue.Title,
 | 
			
		||||
		}
 | 
			
		||||
		_, err := CreateComment(ctx, opts)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, false, fmt.Errorf("createComment: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if currentIssue.IsClosed != issue.IsClosed {
 | 
			
		||||
		statusChangeComment, err = doChangeIssueStatus(ctx, issue, doer, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, false, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issue.AddCrossReferences(ctx, doer, true); err != nil {
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return statusChangeComment, titleChanged, committer.Commit()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
 | 
			
		||||
func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
 | 
			
		||||
	// if the deadline hasn't changed do nothing
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	// Comment on PR to reopen issue #1
 | 
			
		||||
	content = fmt.Sprintf("content2, reopens #%d", itarget.Index)
 | 
			
		||||
	c := testCreateComment(t, 1, 2, pr.ID, content)
 | 
			
		||||
	c := testCreateComment(t, 2, pr.ID, content)
 | 
			
		||||
	ref = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: itarget.ID, RefIssueID: pr.ID, RefCommentID: c.ID})
 | 
			
		||||
	assert.Equal(t, issues_model.CommentTypeCommentRef, ref.Type)
 | 
			
		||||
	assert.Equal(t, pr.RepoID, ref.RefRepoID)
 | 
			
		||||
| 
						 | 
				
			
			@ -104,18 +104,18 @@ func TestXRef_ResolveCrossReferences(t *testing.T) {
 | 
			
		|||
	pr := testCreatePR(t, 1, 2, "titlepr", fmt.Sprintf("closes #%d", i1.Index))
 | 
			
		||||
	rp := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i1.ID, RefIssueID: pr.Issue.ID, RefCommentID: 0})
 | 
			
		||||
 | 
			
		||||
	c1 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i2.Index))
 | 
			
		||||
	c1 := testCreateComment(t, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i2.Index))
 | 
			
		||||
	r1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID})
 | 
			
		||||
 | 
			
		||||
	// Must be ignored
 | 
			
		||||
	c2 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("mentions #%d", i2.Index))
 | 
			
		||||
	c2 := testCreateComment(t, 2, pr.Issue.ID, fmt.Sprintf("mentions #%d", i2.Index))
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c2.ID})
 | 
			
		||||
 | 
			
		||||
	// Must be superseded by c4/r4
 | 
			
		||||
	c3 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("reopens #%d", i3.Index))
 | 
			
		||||
	c3 := testCreateComment(t, 2, pr.Issue.ID, fmt.Sprintf("reopens #%d", i3.Index))
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c3.ID})
 | 
			
		||||
 | 
			
		||||
	c4 := testCreateComment(t, 1, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i3.Index))
 | 
			
		||||
	c4 := testCreateComment(t, 2, pr.Issue.ID, fmt.Sprintf("closes #%d", i3.Index))
 | 
			
		||||
	r4 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID})
 | 
			
		||||
 | 
			
		||||
	refs, err := pr.ResolveCrossReferences(db.DefaultContext)
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +168,7 @@ func testCreatePR(t *testing.T, repo, doer int64, title, content string) *issues
 | 
			
		|||
	return pr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testCreateComment(t *testing.T, repo, doer, issue int64, content string) *issues_model.Comment {
 | 
			
		||||
func testCreateComment(t *testing.T, doer, issue int64, content string) *issues_model.Comment {
 | 
			
		||||
	d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
 | 
			
		||||
	i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue})
 | 
			
		||||
	c := &issues_model.Comment{Type: issues_model.CommentTypeComment, PosterID: doer, Poster: d, IssueID: issue, Issue: i, Content: content}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -291,15 +291,15 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
 | 
			
		|||
func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
			
		||||
	org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
 | 
			
		||||
	testSuccess := func(userID, _, pageSize int64, expectedRepoIDs []int64) {
 | 
			
		||||
	testSuccess := func(userID int64, expectedRepoIDs []int64) {
 | 
			
		||||
		env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		repoIDs, err := env.RepoIDs(1, 100)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Equal(t, expectedRepoIDs, repoIDs)
 | 
			
		||||
	}
 | 
			
		||||
	testSuccess(2, 1, 100, []int64{3, 5, 32})
 | 
			
		||||
	testSuccess(4, 0, 100, []int64{3, 32})
 | 
			
		||||
	testSuccess(2, []int64{3, 5, 32})
 | 
			
		||||
	testSuccess(4, []int64{3, 32})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,10 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
 | 
			
		|||
	// and just waste 1 unit is cheaper than re-allocate memory once.
 | 
			
		||||
	users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
 | 
			
		||||
	if len(userIDs) > 0 {
 | 
			
		||||
		if err = e.In("id", uniqueUserIDs.Values()).OrderBy(user_model.GetOrderByName()).Find(&users); err != nil {
 | 
			
		||||
		if err = e.In("id", uniqueUserIDs.Values()).
 | 
			
		||||
			Where(builder.Eq{"`user`.is_active": true}).
 | 
			
		||||
			OrderBy(user_model.GetOrderByName()).
 | 
			
		||||
			Find(&users); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +120,8 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cond := builder.And(builder.Neq{"`user`.id": posterID})
 | 
			
		||||
	cond := builder.And(builder.Neq{"`user`.id": posterID}).
 | 
			
		||||
		And(builder.Eq{"`user`.is_active": true})
 | 
			
		||||
 | 
			
		||||
	if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
 | 
			
		||||
		// This a private repository:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,10 +26,17 @@ func TestRepoAssignees(t *testing.T) {
 | 
			
		|||
	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
 | 
			
		||||
	users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, users, 3)
 | 
			
		||||
	assert.Equal(t, users[0].ID, int64(15))
 | 
			
		||||
	assert.Equal(t, users[1].ID, int64(18))
 | 
			
		||||
	assert.Equal(t, users[2].ID, int64(16))
 | 
			
		||||
	if assert.Len(t, users, 3) {
 | 
			
		||||
		assert.ElementsMatch(t, []int64{15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// do not return deactivated users
 | 
			
		||||
	assert.NoError(t, user_model.UpdateUserCols(db.DefaultContext, &user_model.User{ID: 15, IsActive: false}, "is_active"))
 | 
			
		||||
	users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	if assert.Len(t, users, 2) {
 | 
			
		||||
		assert.NotContains(t, []int64{users[0].ID, users[1].ID}, 15)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRepoGetReviewers(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,17 +48,19 @@ func TestRepoGetReviewers(t *testing.T) {
 | 
			
		|||
	ctx := db.DefaultContext
 | 
			
		||||
	reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, reviewers, 4)
 | 
			
		||||
	if assert.Len(t, reviewers, 3) {
 | 
			
		||||
		assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// should include doer if doer is not PR poster.
 | 
			
		||||
	reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, reviewers, 4)
 | 
			
		||||
	assert.Len(t, reviewers, 3)
 | 
			
		||||
 | 
			
		||||
	// should not include PR poster, if PR poster would be otherwise eligible
 | 
			
		||||
	reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, reviewers, 3)
 | 
			
		||||
	assert.Len(t, reviewers, 2)
 | 
			
		||||
 | 
			
		||||
	// test private user repo
 | 
			
		||||
	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -211,14 +211,14 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
 | 
			
		|||
		webhook_module.HookEventIssueAssign,
 | 
			
		||||
		webhook_module.HookEventIssueLabel,
 | 
			
		||||
		webhook_module.HookEventIssueMilestone:
 | 
			
		||||
		return matchIssuesEvent(commit, payload.(*api.IssuePayload), evt)
 | 
			
		||||
		return matchIssuesEvent(payload.(*api.IssuePayload), evt)
 | 
			
		||||
 | 
			
		||||
	case // issue_comment
 | 
			
		||||
		webhook_module.HookEventIssueComment,
 | 
			
		||||
		// `pull_request_comment` is same as `issue_comment`
 | 
			
		||||
		// See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
 | 
			
		||||
		webhook_module.HookEventPullRequestComment:
 | 
			
		||||
		return matchIssueCommentEvent(commit, payload.(*api.IssueCommentPayload), evt)
 | 
			
		||||
		return matchIssueCommentEvent(payload.(*api.IssueCommentPayload), evt)
 | 
			
		||||
 | 
			
		||||
	case // pull_request
 | 
			
		||||
		webhook_module.HookEventPullRequest,
 | 
			
		||||
| 
						 | 
				
			
			@ -232,19 +232,19 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
 | 
			
		|||
	case // pull_request_review
 | 
			
		||||
		webhook_module.HookEventPullRequestReviewApproved,
 | 
			
		||||
		webhook_module.HookEventPullRequestReviewRejected:
 | 
			
		||||
		return matchPullRequestReviewEvent(commit, payload.(*api.PullRequestPayload), evt)
 | 
			
		||||
		return matchPullRequestReviewEvent(payload.(*api.PullRequestPayload), evt)
 | 
			
		||||
 | 
			
		||||
	case // pull_request_review_comment
 | 
			
		||||
		webhook_module.HookEventPullRequestReviewComment:
 | 
			
		||||
		return matchPullRequestReviewCommentEvent(commit, payload.(*api.PullRequestPayload), evt)
 | 
			
		||||
		return matchPullRequestReviewCommentEvent(payload.(*api.PullRequestPayload), evt)
 | 
			
		||||
 | 
			
		||||
	case // release
 | 
			
		||||
		webhook_module.HookEventRelease:
 | 
			
		||||
		return matchReleaseEvent(commit, payload.(*api.ReleasePayload), evt)
 | 
			
		||||
		return matchReleaseEvent(payload.(*api.ReleasePayload), evt)
 | 
			
		||||
 | 
			
		||||
	case // registry_package
 | 
			
		||||
		webhook_module.HookEventPackage:
 | 
			
		||||
		return matchPackageEvent(commit, payload.(*api.PackagePayload), evt)
 | 
			
		||||
		return matchPackageEvent(payload.(*api.PackagePayload), evt)
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		log.Warn("unsupported event %q", triggedEvent)
 | 
			
		||||
| 
						 | 
				
			
			@ -350,7 +350,7 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa
 | 
			
		|||
	return matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchIssuesEvent(commit *git.Commit, issuePayload *api.IssuePayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchIssuesEvent(issuePayload *api.IssuePayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			@ -498,7 +498,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
 | 
			
		|||
	return activityTypeMatched && matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchIssueCommentEvent(issueCommentPayload *api.IssueCommentPayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			@ -530,7 +530,7 @@ func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCo
 | 
			
		|||
	return matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchPullRequestReviewEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			@ -579,7 +579,7 @@ func matchPullRequestReviewEvent(commit *git.Commit, prPayload *api.PullRequestP
 | 
			
		|||
	return matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchPullRequestReviewCommentEvent(commit *git.Commit, prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			@ -628,7 +628,7 @@ func matchPullRequestReviewCommentEvent(commit *git.Commit, prPayload *api.PullR
 | 
			
		|||
	return matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchReleaseEvent(commit *git.Commit, payload *api.ReleasePayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchReleaseEvent(payload *api.ReleasePayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			@ -665,7 +665,7 @@ func matchReleaseEvent(commit *git.Commit, payload *api.ReleasePayload, evt *job
 | 
			
		|||
	return matchTimes == len(evt.Acts())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchPackageEvent(commit *git.Commit, payload *api.PackagePayload, evt *jobparser.Event) bool {
 | 
			
		||||
func matchPackageEvent(payload *api.PackagePayload, evt *jobparser.Event) bool {
 | 
			
		||||
	// with no special filter parameters
 | 
			
		||||
	if len(evt.Acts()) == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,11 @@
 | 
			
		|||
package pwn
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/rand/v2"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/h2non/gock"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,86 +17,34 @@ var client = New(WithHTTP(&http.Client{
 | 
			
		|||
}))
 | 
			
		||||
 | 
			
		||||
func TestPassword(t *testing.T) {
 | 
			
		||||
	// Check input error
 | 
			
		||||
	_, err := client.CheckPassword("", false)
 | 
			
		||||
	defer gock.Off()
 | 
			
		||||
 | 
			
		||||
	count, err := client.CheckPassword("", false)
 | 
			
		||||
	assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
 | 
			
		||||
	assert.Equal(t, -1, count)
 | 
			
		||||
 | 
			
		||||
	// Should fail
 | 
			
		||||
	fail := "password1234"
 | 
			
		||||
	count, err := client.CheckPassword(fail, false)
 | 
			
		||||
	assert.NotEmpty(t, count, "%s should fail as a password", fail)
 | 
			
		||||
	gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
 | 
			
		||||
	count, err = client.CheckPassword("pwned", false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 1, count)
 | 
			
		||||
 | 
			
		||||
	// Should fail (with padding)
 | 
			
		||||
	failPad := "administrator"
 | 
			
		||||
	count, err = client.CheckPassword(failPad, true)
 | 
			
		||||
	assert.NotEmpty(t, count, "%s should fail as a password", failPad)
 | 
			
		||||
	gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
 | 
			
		||||
	count, err = client.CheckPassword("notpwned", false)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 0, count)
 | 
			
		||||
 | 
			
		||||
	// Checking for a "good" password isn't going to be perfect, but we can give it a good try
 | 
			
		||||
	// with hopefully minimal error. Try five times?
 | 
			
		||||
	assert.Condition(t, func() bool {
 | 
			
		||||
		for i := 0; i <= 5; i++ {
 | 
			
		||||
			count, err = client.CheckPassword(testPassword(), false)
 | 
			
		||||
	gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
 | 
			
		||||
	count, err = client.CheckPassword("paddedpwned", true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
			if count == 0 {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}, "no generated passwords passed. there is a chance this is a fluke")
 | 
			
		||||
	assert.Equal(t, 1, count)
 | 
			
		||||
 | 
			
		||||
	// Again, but with padded responses
 | 
			
		||||
	assert.Condition(t, func() bool {
 | 
			
		||||
		for i := 0; i <= 5; i++ {
 | 
			
		||||
			count, err = client.CheckPassword(testPassword(), true)
 | 
			
		||||
	gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
 | 
			
		||||
	count, err = client.CheckPassword("paddednotpwned", true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
			if count == 0 {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	}, "no generated passwords passed. there is a chance this is a fluke")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Credit to https://golangbyexample.com/generate-random-password-golang/
 | 
			
		||||
// DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR
 | 
			
		||||
var (
 | 
			
		||||
	lowerCharSet   = "abcdedfghijklmnopqrst"
 | 
			
		||||
	upperCharSet   = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 | 
			
		||||
	specialCharSet = "!@#$%&*"
 | 
			
		||||
	numberSet      = "0123456789"
 | 
			
		||||
	allCharSet     = lowerCharSet + upperCharSet + specialCharSet + numberSet
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testPassword() string {
 | 
			
		||||
	var password strings.Builder
 | 
			
		||||
 | 
			
		||||
	// Set special character
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.IntN(len(specialCharSet))
 | 
			
		||||
		password.WriteString(string(specialCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set numeric
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.IntN(len(numberSet))
 | 
			
		||||
		password.WriteString(string(numberSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set uppercase
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.IntN(len(upperCharSet))
 | 
			
		||||
		password.WriteString(string(upperCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < 5; i++ {
 | 
			
		||||
		random := rand.IntN(len(allCharSet))
 | 
			
		||||
		password.WriteString(string(allCharSet[random]))
 | 
			
		||||
	}
 | 
			
		||||
	inRune := []rune(password.String())
 | 
			
		||||
	rand.Shuffle(len(inRune), func(i, j int) {
 | 
			
		||||
		inRune[i], inRune[j] = inRune[j], inRune[i]
 | 
			
		||||
	})
 | 
			
		||||
	return string(inRune)
 | 
			
		||||
	assert.Equal(t, 0, count)
 | 
			
		||||
 | 
			
		||||
	gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
 | 
			
		||||
	count, err = client.CheckPassword("paddednotpwnedzero", true)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, 0, count)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 | 
			
		|||
	var revs map[string]*Commit
 | 
			
		||||
	if commit.repo.LastCommitCache != nil {
 | 
			
		||||
		var unHitPaths []string
 | 
			
		||||
		revs, unHitPaths, err = getLastCommitForPathsByCache(ctx, commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
 | 
			
		||||
		revs, unHitPaths, err = getLastCommitForPathsByCache(commit.ID.String(), treePath, entryPaths, commit.repo.LastCommitCache)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, nil, err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +97,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
 | 
			
		|||
	return commitsInfo, treeCommit, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getLastCommitForPathsByCache(ctx context.Context, commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
 | 
			
		||||
func getLastCommitForPathsByCache(commitID, treePath string, paths []string, cache *LastCommitCache) (map[string]*Commit, []string, error) {
 | 
			
		||||
	var unHitEntryPaths []string
 | 
			
		||||
	results := make(map[string]*Commit)
 | 
			
		||||
	for _, p := range paths {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
 | 
			
		||||
func ParseTreeEntries(h ObjectFormat, data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
	return parseTreeEntries(data, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ func TestParseTreeEntries(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	for _, testCase := range testCases {
 | 
			
		||||
		entries, err := ParseTreeEntries(Sha1ObjectFormat, []byte(testCase.Input))
 | 
			
		||||
		entries, err := ParseTreeEntries([]byte(testCase.Input))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		if len(entries) > 1 {
 | 
			
		||||
			fmt.Println(testCase.Expected[0].ID)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,13 +17,13 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// ParseTreeEntries parses the output of a `git ls-tree -l` command.
 | 
			
		||||
func ParseTreeEntries(objectFormat ObjectFormat, data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
	return parseTreeEntries(objectFormat, data, nil)
 | 
			
		||||
func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
 | 
			
		||||
	return parseTreeEntries(data, nil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var sepSpace = []byte{' '}
 | 
			
		||||
 | 
			
		||||
func parseTreeEntries(objectFormat ObjectFormat, data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
			
		||||
func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
 | 
			
		||||
	for pos := 0; pos < len(data); {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,8 +12,6 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func TestParseTreeEntriesLong(t *testing.T) {
 | 
			
		||||
	objectFormat := Sha1ObjectFormat
 | 
			
		||||
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		Input    string
 | 
			
		||||
		Expected []*TreeEntry
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +54,7 @@ func TestParseTreeEntriesLong(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, testCase := range testCases {
 | 
			
		||||
		entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
 | 
			
		||||
		entries, err := ParseTreeEntries([]byte(testCase.Input))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Len(t, entries, len(testCase.Expected))
 | 
			
		||||
		for i, entry := range entries {
 | 
			
		||||
| 
						 | 
				
			
			@ -66,8 +64,6 @@ func TestParseTreeEntriesLong(t *testing.T) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func TestParseTreeEntriesShort(t *testing.T) {
 | 
			
		||||
	objectFormat := Sha1ObjectFormat
 | 
			
		||||
 | 
			
		||||
	testCases := []struct {
 | 
			
		||||
		Input    string
 | 
			
		||||
		Expected []*TreeEntry
 | 
			
		||||
| 
						 | 
				
			
			@ -91,7 +87,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
 | 
			
		|||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, testCase := range testCases {
 | 
			
		||||
		entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input))
 | 
			
		||||
		entries, err := ParseTreeEntries([]byte(testCase.Input))
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		assert.Len(t, entries, len(testCase.Expected))
 | 
			
		||||
		for i, entry := range entries {
 | 
			
		||||
| 
						 | 
				
			
			@ -102,7 +98,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestParseTreeEntriesInvalid(t *testing.T) {
 | 
			
		||||
	// there was a panic: "runtime error: slice bounds out of range" when the input was invalid: #20315
 | 
			
		||||
	entries, err := ParseTreeEntries(Sha1ObjectFormat, []byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
 | 
			
		||||
	entries, err := ParseTreeEntries([]byte("100644 blob ea0d83c9081af9500ac9f804101b3fd0a5c293af"))
 | 
			
		||||
	assert.Error(t, err)
 | 
			
		||||
	assert.Len(t, entries, 0)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,11 +77,8 @@ func (t *Tree) ListEntries() (Entries, error) {
 | 
			
		|||
		return nil, runErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	objectFormat, err := t.repo.GetObjectFormat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	t.entries, err = parseTreeEntries(objectFormat, stdout, t)
 | 
			
		||||
	var err error
 | 
			
		||||
	t.entries, err = parseTreeEntries(stdout, t)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.entriesParsed = true
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -104,11 +101,8 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
 | 
			
		|||
		return nil, runErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	objectFormat, err := t.repo.GetObjectFormat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	t.entriesRecursive, err = parseTreeEntries(objectFormat, stdout, t)
 | 
			
		||||
	var err error
 | 
			
		||||
	t.entriesRecursive, err = parseTreeEntries(stdout, t)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		t.entriesRecursiveParsed = true
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,11 +17,14 @@ import (
 | 
			
		|||
	"time"
 | 
			
		||||
 | 
			
		||||
	charsetModule "code.gitea.io/gitea/modules/charset"
 | 
			
		||||
	"code.gitea.io/gitea/modules/container"
 | 
			
		||||
	"code.gitea.io/gitea/modules/httpcache"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/typesniffer"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/klauspost/compress/gzhttp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ServeHeaderOptions struct {
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +41,11 @@ type ServeHeaderOptions struct {
 | 
			
		|||
func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
 | 
			
		||||
	header := w.Header()
 | 
			
		||||
 | 
			
		||||
	skipCompressionExts := container.SetOf(".gz", ".bz2", ".zip", ".xz", ".zst", ".deb", ".apk", ".jar", ".png", ".jpg", ".webp")
 | 
			
		||||
	if skipCompressionExts.Contains(strings.ToLower(path.Ext(opts.Filename))) {
 | 
			
		||||
		w.Header().Add(gzhttp.HeaderNoCompression, "1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contentType := typesniffer.ApplicationOctetStream
 | 
			
		||||
	if opts.ContentType != "" {
 | 
			
		||||
		if opts.ContentTypeCharset != "" {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,8 +62,8 @@ func isIndexable(entry *git.TreeEntry) bool {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
 | 
			
		||||
func parseGitLsTreeOutput(objectFormat git.ObjectFormat, stdout []byte) ([]internal.FileUpdate, error) {
 | 
			
		||||
	entries, err := git.ParseTreeEntries(objectFormat, stdout)
 | 
			
		||||
func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) {
 | 
			
		||||
	entries, err := git.ParseTreeEntries(stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -91,10 +91,8 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
 | 
			
		|||
		return nil, runErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout)
 | 
			
		||||
	changes.Updates, err = parseGitLsTreeOutput(stdout)
 | 
			
		||||
	return &changes, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,8 +170,6 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
 | 
			
		||||
 | 
			
		||||
	changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout)
 | 
			
		||||
	changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout)
 | 
			
		||||
	return &changes, err
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -58,11 +58,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
 | 
			
		|||
		case *ast.Paragraph:
 | 
			
		||||
			g.applyElementDir(v)
 | 
			
		||||
		case *ast.Image:
 | 
			
		||||
			g.transformImage(ctx, v, reader)
 | 
			
		||||
			g.transformImage(ctx, v)
 | 
			
		||||
		case *ast.Link:
 | 
			
		||||
			g.transformLink(ctx, v, reader)
 | 
			
		||||
			g.transformLink(ctx, v)
 | 
			
		||||
		case *ast.List:
 | 
			
		||||
			g.transformList(ctx, v, reader, rc)
 | 
			
		||||
			g.transformList(ctx, v, rc)
 | 
			
		||||
		case *ast.Text:
 | 
			
		||||
			if v.SoftLineBreak() && !v.HardLineBreak() {
 | 
			
		||||
				if ctx.Metas["mode"] != "document" {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ import (
 | 
			
		|||
	"github.com/yuin/goldmark/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (g *ASTTransformer) transformHeading(ctx *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
 | 
			
		||||
func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) {
 | 
			
		||||
	for _, attr := range v.Attributes() {
 | 
			
		||||
		if _, ok := attr.Value.([]byte); !ok {
 | 
			
		||||
			v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,10 +10,9 @@ import (
 | 
			
		|||
	giteautil "code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/yuin/goldmark/ast"
 | 
			
		||||
	"github.com/yuin/goldmark/text"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image, reader text.Reader) {
 | 
			
		||||
func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image) {
 | 
			
		||||
	// Images need two things:
 | 
			
		||||
	//
 | 
			
		||||
	// 1. Their src needs to munged to be a real value
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,9 @@ import (
 | 
			
		|||
	giteautil "code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/yuin/goldmark/ast"
 | 
			
		||||
	"github.com/yuin/goldmark/text"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link, reader text.Reader) {
 | 
			
		||||
func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) {
 | 
			
		||||
	// Links need their href to munged to be a real value
 | 
			
		||||
	link := v.Destination
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ import (
 | 
			
		|||
	"github.com/yuin/goldmark/ast"
 | 
			
		||||
	east "github.com/yuin/goldmark/extension/ast"
 | 
			
		||||
	"github.com/yuin/goldmark/renderer/html"
 | 
			
		||||
	"github.com/yuin/goldmark/text"
 | 
			
		||||
	"github.com/yuin/goldmark/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +49,7 @@ func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node
 | 
			
		|||
	return ast.WalkContinue, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *ASTTransformer) transformList(ctx *markup.RenderContext, v *ast.List, reader text.Reader, rc *RenderConfig) {
 | 
			
		||||
func (g *ASTTransformer) transformList(_ *markup.RenderContext, v *ast.List, rc *RenderConfig) {
 | 
			
		||||
	if v.HasChildren() {
 | 
			
		||||
		children := make([]ast.Node, 0, v.ChildCount())
 | 
			
		||||
		child := v.FirstChild()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,7 @@ func (r *stripRenderer) Render(w io.Writer, source []byte, doc ast.Node) error {
 | 
			
		|||
			}
 | 
			
		||||
			return ast.WalkContinue, nil
 | 
			
		||||
		case *ast.Link:
 | 
			
		||||
			r.processLink(w, v.Destination)
 | 
			
		||||
			r.processLink(v.Destination)
 | 
			
		||||
			return ast.WalkSkipChildren, nil
 | 
			
		||||
		case *ast.AutoLink:
 | 
			
		||||
			// This could be a reference to an issue or pull - if so convert it
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +124,7 @@ func (r *stripRenderer) processAutoLink(w io.Writer, link []byte) {
 | 
			
		|||
	_, _ = w.Write([]byte(parts[4]))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *stripRenderer) processLink(w io.Writer, link []byte) {
 | 
			
		||||
func (r *stripRenderer) processLink(link []byte) {
 | 
			
		||||
	// Links are processed out of band
 | 
			
		||||
	r.links = append(r.links, string(link))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ func TestOption(t *testing.T) {
 | 
			
		|||
	assert.Equal(t, int(0), none.Value())
 | 
			
		||||
	assert.Equal(t, int(1), none.ValueOrDefault(1))
 | 
			
		||||
 | 
			
		||||
	some := optional.Some[int](1)
 | 
			
		||||
	some := optional.Some(1)
 | 
			
		||||
	assert.True(t, some.Has())
 | 
			
		||||
	assert.Equal(t, int(1), some.Value())
 | 
			
		||||
	assert.Equal(t, int(1), some.ValueOrDefault(2))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,6 +78,7 @@ type PackageMetadataVersion struct {
 | 
			
		|||
	Repository           Repository          `json:"repository,omitempty"`
 | 
			
		||||
	Keywords             []string            `json:"keywords,omitempty"`
 | 
			
		||||
	Dependencies         map[string]string   `json:"dependencies,omitempty"`
 | 
			
		||||
	BundleDependencies   []string            `json:"bundleDependencies,omitempty"`
 | 
			
		||||
	DevDependencies      map[string]string   `json:"devDependencies,omitempty"`
 | 
			
		||||
	PeerDependencies     map[string]string   `json:"peerDependencies,omitempty"`
 | 
			
		||||
	Bin                  map[string]string   `json:"bin,omitempty"`
 | 
			
		||||
| 
						 | 
				
			
			@ -218,6 +219,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
 | 
			
		|||
				ProjectURL:              meta.Homepage,
 | 
			
		||||
				Keywords:                meta.Keywords,
 | 
			
		||||
				Dependencies:            meta.Dependencies,
 | 
			
		||||
				BundleDependencies:      meta.BundleDependencies,
 | 
			
		||||
				DevelopmentDependencies: meta.DevDependencies,
 | 
			
		||||
				PeerDependencies:        meta.PeerDependencies,
 | 
			
		||||
				OptionalDependencies:    meta.OptionalDependencies,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ type Metadata struct {
 | 
			
		|||
	ProjectURL              string            `json:"project_url,omitempty"`
 | 
			
		||||
	Keywords                []string          `json:"keywords,omitempty"`
 | 
			
		||||
	Dependencies            map[string]string `json:"dependencies,omitempty"`
 | 
			
		||||
	BundleDependencies      []string          `json:"bundleDependencies,omitempty"`
 | 
			
		||||
	DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
 | 
			
		||||
	PeerDependencies        map[string]string `json:"peer_dependencies,omitempty"`
 | 
			
		||||
	OptionalDependencies    map[string]string `json:"optional_dependencies,omitempty"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,12 +56,12 @@ func loadIncomingEmailFrom(rootCfg ConfigProvider) {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := checkReplyToAddress(IncomingEmail.ReplyToAddress); err != nil {
 | 
			
		||||
	if err := checkReplyToAddress(); err != nil {
 | 
			
		||||
		log.Fatal("Invalid incoming_mail.REPLY_TO_ADDRESS (%s): %v", IncomingEmail.ReplyToAddress, err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkReplyToAddress(address string) error {
 | 
			
		||||
func checkReplyToAddress() error {
 | 
			
		||||
	parsed, err := mail.ParseAddress(IncomingEmail.ReplyToAddress)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -99,7 +99,7 @@ func getStorage(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (*S
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	overrideSec := getStorageOverrideSection(rootCfg, targetSec, sec, tp, name)
 | 
			
		||||
	overrideSec := getStorageOverrideSection(rootCfg, sec, tp, name)
 | 
			
		||||
 | 
			
		||||
	targetType := targetSec.Key("STORAGE_TYPE").String()
 | 
			
		||||
	switch targetType {
 | 
			
		||||
| 
						 | 
				
			
			@ -191,7 +191,7 @@ func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec Confi
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// getStorageOverrideSection override section will be read SERVE_DIRECT, PATH, MINIO_BASE_PATH, MINIO_BUCKET to override the targetsec when possible
 | 
			
		||||
func getStorageOverrideSection(rootConfig ConfigProvider, targetSec, sec ConfigSection, targetSecType targetSecType, name string) ConfigSection {
 | 
			
		||||
func getStorageOverrideSection(rootConfig ConfigProvider, sec ConfigSection, targetSecType targetSecType, name string) ConfigSection {
 | 
			
		||||
	if targetSecType == targetSecIsSec {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
 | 
			
		|||
// EditPullRequestOption options when modify pull request
 | 
			
		||||
type EditPullRequestOption struct {
 | 
			
		||||
	Title     string   `json:"title"`
 | 
			
		||||
	Body      string   `json:"body"`
 | 
			
		||||
	Body      *string  `json:"body"`
 | 
			
		||||
	Base      string   `json:"base"`
 | 
			
		||||
	Assignee  string   `json:"assignee"`
 | 
			
		||||
	Assignees []string `json:"assignees"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								modules/structs/repo_actions.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								modules/structs/repo_actions.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package structs
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ActionTask represents a ActionTask
 | 
			
		||||
type ActionTask struct {
 | 
			
		||||
	ID           int64  `json:"id"`
 | 
			
		||||
	Name         string `json:"name"`
 | 
			
		||||
	HeadBranch   string `json:"head_branch"`
 | 
			
		||||
	HeadSHA      string `json:"head_sha"`
 | 
			
		||||
	RunNumber    int64  `json:"run_number"`
 | 
			
		||||
	Event        string `json:"event"`
 | 
			
		||||
	DisplayTitle string `json:"display_title"`
 | 
			
		||||
	Status       string `json:"status"`
 | 
			
		||||
	WorkflowID   string `json:"workflow_id"`
 | 
			
		||||
	URL          string `json:"url"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
	CreatedAt time.Time `json:"created_at"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
	UpdatedAt time.Time `json:"updated_at"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
	RunStartedAt time.Time `json:"run_started_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ActionTaskResponse returns a ActionTask
 | 
			
		||||
type ActionTaskResponse struct {
 | 
			
		||||
	Entries    []*ActionTask `json:"workflow_runs"`
 | 
			
		||||
	TotalCount int64         `json:"total_count"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										121
									
								
								options/license/Catharon
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								options/license/Catharon
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,121 @@
 | 
			
		|||
                  The Catharon Open Source LICENSE
 | 
			
		||||
                    ----------------------------
 | 
			
		||||
 | 
			
		||||
                            2000-Jul-04
 | 
			
		||||
 | 
			
		||||
          Copyright (C) 2000 by Catharon Productions, Inc.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Introduction
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
  This  license  applies to  source  files  distributed by  Catharon
 | 
			
		||||
  Productions,  Inc.  in  several  archive packages.   This  license
 | 
			
		||||
  applies  to all files  found in  such packages  which do  not fall
 | 
			
		||||
  under their own explicit license.
 | 
			
		||||
 | 
			
		||||
  This  license   was  inspired  by  the  BSD,   Artistic,  and  IJG
 | 
			
		||||
  (Independent JPEG  Group) licenses, which  all encourage inclusion
 | 
			
		||||
  and  use of  free  software in  commercial  and freeware  products
 | 
			
		||||
  alike.  As a consequence, its main points are that:
 | 
			
		||||
 | 
			
		||||
    o We  don't promise that  this software works.  However,  we are
 | 
			
		||||
      interested in any kind of bug reports. (`as is' distribution)
 | 
			
		||||
 | 
			
		||||
    o You can  use this software for whatever you  want, in parts or
 | 
			
		||||
      full form, without having to pay us. (`royalty-free' usage)
 | 
			
		||||
 | 
			
		||||
    o You may not pretend that  you wrote this software.  If you use
 | 
			
		||||
      it, or  only parts of it,  in a program,  you must acknowledge
 | 
			
		||||
      somewhere  in  your  documentation  that  you  have  used  the
 | 
			
		||||
      Catharon Code. (`credits')
 | 
			
		||||
 | 
			
		||||
  We  specifically  permit  and  encourage  the  inclusion  of  this
 | 
			
		||||
  software, with  or without modifications,  in commercial products.
 | 
			
		||||
  We disclaim  all warranties  covering the packages  distributed by
 | 
			
		||||
  Catharon  Productions, Inc.  and  assume no  liability related  to
 | 
			
		||||
  their use.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Legal Terms
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
0. Definitions
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
  Throughout this license,  the terms `Catharon Package', `package',
 | 
			
		||||
  and  `Catharon  Code'  refer   to  the  set  of  files  originally
 | 
			
		||||
  distributed by Catharon Productions, Inc.
 | 
			
		||||
 | 
			
		||||
  `You' refers to  the licensee, or person using  the project, where
 | 
			
		||||
  `using' is a generic term including compiling the project's source
 | 
			
		||||
  code as  well as linking it  to form a  `program' or `executable'.
 | 
			
		||||
  This  program  is referred  to  as `a  program  using  one of  the
 | 
			
		||||
  Catharon Packages'.
 | 
			
		||||
 | 
			
		||||
  This  license applies  to all  files distributed  in  the original
 | 
			
		||||
  Catharon  Package(s),  including  all  source code,  binaries  and
 | 
			
		||||
  documentation,  unless  otherwise  stated   in  the  file  in  its
 | 
			
		||||
  original, unmodified form as  distributed in the original archive.
 | 
			
		||||
  If you are  unsure whether or not a particular  file is covered by
 | 
			
		||||
  this license, you must contact us to verify this.
 | 
			
		||||
 | 
			
		||||
  The  Catharon   Packages  are  copyright  (C)   2000  by  Catharon
 | 
			
		||||
  Productions, Inc.  All rights reserved except as specified below.
 | 
			
		||||
 | 
			
		||||
1. No Warranty
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
  THE CATHARON PACKAGES ARE PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
 | 
			
		||||
  KIND, EITHER  EXPRESS OR IMPLIED,  INCLUDING, BUT NOT  LIMITED TO,
 | 
			
		||||
  WARRANTIES  OF  MERCHANTABILITY   AND  FITNESS  FOR  A  PARTICULAR
 | 
			
		||||
  PURPOSE.  IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
 | 
			
		||||
  BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OF OR THE INABILITY TO
 | 
			
		||||
  USE THE CATHARON PACKAGE.
 | 
			
		||||
 | 
			
		||||
2. Redistribution
 | 
			
		||||
-----------------
 | 
			
		||||
 | 
			
		||||
  This  license  grants  a  worldwide, royalty-free,  perpetual  and
 | 
			
		||||
  irrevocable right  and license to use,  execute, perform, compile,
 | 
			
		||||
  display,  copy,   create  derivative  works   of,  distribute  and
 | 
			
		||||
  sublicense the  Catharon Packages (in both source  and object code
 | 
			
		||||
  forms)  and  derivative works  thereof  for  any  purpose; and  to
 | 
			
		||||
  authorize others  to exercise  some or all  of the  rights granted
 | 
			
		||||
  herein, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
    o Redistribution  of source code  must retain this  license file
 | 
			
		||||
      (`license.txt') unaltered; any additions, deletions or changes
 | 
			
		||||
      to   the  original   files  must   be  clearly   indicated  in
 | 
			
		||||
      accompanying  documentation.   The  copyright notices  of  the
 | 
			
		||||
      unaltered, original  files must be preserved in  all copies of
 | 
			
		||||
      source files.
 | 
			
		||||
 | 
			
		||||
    o Redistribution  in binary form must provide  a disclaimer that
 | 
			
		||||
      states  that the  software is  based in  part on  the  work of
 | 
			
		||||
      Catharon Productions, Inc. in the distribution documentation.
 | 
			
		||||
 | 
			
		||||
  These conditions  apply to any  software derived from or  based on
 | 
			
		||||
  the Catharon Packages, not just  the unmodified files.  If you use
 | 
			
		||||
  our work, you  must acknowledge us.  However, no  fee need be paid
 | 
			
		||||
  to us.
 | 
			
		||||
 | 
			
		||||
3. Advertising
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
  Neither Catharon Productions, Inc.  and contributors nor you shall
 | 
			
		||||
  use  the  name  of  the  other  for  commercial,  advertising,  or
 | 
			
		||||
  promotional purposes without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
  We suggest, but do not  require, that you use the following phrase
 | 
			
		||||
  to refer to this software in your documentation: 'this software is
 | 
			
		||||
  based in part on the Catharon Typography Project'.
 | 
			
		||||
 | 
			
		||||
  As  you have  not signed  this license,  you are  not  required to
 | 
			
		||||
  accept  it.  However,  as  the Catharon  Packages are  copyrighted
 | 
			
		||||
  material, only  this license, or  another one contracted  with the
 | 
			
		||||
  authors, grants you  the right to use, distribute,  and modify it.
 | 
			
		||||
  Therefore,  by  using,  distributing,  or modifying  the  Catharon
 | 
			
		||||
  Packages,  you indicate  that you  understand and  accept  all the
 | 
			
		||||
  terms of this license.
 | 
			
		||||
| 
						 | 
				
			
			@ -3573,6 +3573,7 @@ npm.install = To install the package using npm, run the following command:
 | 
			
		|||
npm.install2 = or add it to the package.json file:
 | 
			
		||||
npm.dependencies = Dependencies
 | 
			
		||||
npm.dependencies.development = Development Dependencies
 | 
			
		||||
npm.dependencies.bundle = Bundled Dependencies
 | 
			
		||||
npm.dependencies.peer = Peer Dependencies
 | 
			
		||||
npm.dependencies.optional = Optional Dependencies
 | 
			
		||||
npm.details.tag = Tag
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -143,9 +143,7 @@ func serveMavenMetadata(ctx *context.Context, params parameters) {
 | 
			
		|||
	ctx.Resp.Header().Set("Content-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", contentTypeXML)
 | 
			
		||||
 | 
			
		||||
	if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil {
 | 
			
		||||
		log.Error("write bytes failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	_, _ = ctx.Resp.Write(xmlMetadataWithHeader)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,6 +64,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
 | 
			
		|||
		Homepage:             metadata.ProjectURL,
 | 
			
		||||
		License:              metadata.License,
 | 
			
		||||
		Dependencies:         metadata.Dependencies,
 | 
			
		||||
		BundleDependencies:   metadata.BundleDependencies,
 | 
			
		||||
		DevDependencies:      metadata.DevelopmentDependencies,
 | 
			
		||||
		PeerDependencies:     metadata.PeerDependencies,
 | 
			
		||||
		OptionalDependencies: metadata.OptionalDependencies,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1112,6 +1112,9 @@ func Routes() *web.Route {
 | 
			
		|||
					m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
 | 
			
		||||
					m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
 | 
			
		||||
				}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
 | 
			
		||||
				m.Group("/actions", func() {
 | 
			
		||||
					m.Get("/tasks", repo.ListActionTasks)
 | 
			
		||||
				}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
 | 
			
		||||
				m.Group("/keys", func() {
 | 
			
		||||
					m.Combo("").Get(repo.ListDeployKeys).
 | 
			
		||||
						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
			
		||||
	actions_service "code.gitea.io/gitea/services/actions"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
	secret_service "code.gitea.io/gitea/services/secrets"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -517,3 +518,68 @@ type Action struct{}
 | 
			
		|||
func NewAction() actions_service.API {
 | 
			
		||||
	return Action{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListActionTasks list all the actions of a repository
 | 
			
		||||
func ListActionTasks(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/actions/tasks repository ListActionTasks
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: List a repository's action tasks
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: repo
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: name of the repo
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: page
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: page number of results to return (1-based)
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: limit
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: page size of results, default maximum page size is 50
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/TasksList"
 | 
			
		||||
	//   "400":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "409":
 | 
			
		||||
	//     "$ref": "#/responses/conflict"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
 | 
			
		||||
	tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
 | 
			
		||||
		ListOptions: utils.GetListOptions(ctx),
 | 
			
		||||
		RepoID:      ctx.Repo.Repository.ID,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "ListActionTasks", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res := new(api.ActionTaskResponse)
 | 
			
		||||
	res.TotalCount = total
 | 
			
		||||
 | 
			
		||||
	res.Entries = make([]*api.ActionTask, len(tasks))
 | 
			
		||||
	for i := range tasks {
 | 
			
		||||
		convertedTask, err := convert.ToActionTask(ctx, tasks[i])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ToActionTask", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		res.Entries[i] = convertedTask
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, &res)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,6 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
	notify_service "code.gitea.io/gitea/services/notify"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SearchIssues searches for issues across the repositories that the user has access to
 | 
			
		||||
| 
						 | 
				
			
			@ -809,12 +808,19 @@ func EditIssue(ctx *context.APIContext) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oldTitle := issue.Title
 | 
			
		||||
	if len(form.Title) > 0 {
 | 
			
		||||
		issue.Title = form.Title
 | 
			
		||||
		err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if form.Body != nil {
 | 
			
		||||
		issue.Content = *form.Body
 | 
			
		||||
		err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if form.Ref != nil {
 | 
			
		||||
		err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
 | 
			
		||||
| 
						 | 
				
			
			@ -882,24 +888,14 @@ func EditIssue(ctx *context.APIContext) {
 | 
			
		|||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		issue.IsClosed = api.StateClosed == api.StateType(*form.State)
 | 
			
		||||
	}
 | 
			
		||||
	statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
 | 
			
		||||
			if issues_model.IsErrDependenciesLeft(err) {
 | 
			
		||||
				ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	if titleChanged {
 | 
			
		||||
		notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if statusChangeComment != nil {
 | 
			
		||||
		notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Refetch from database to assign some automatic values
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/attachment"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/context/upload"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +160,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
 | 
			
		|||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -207,7 +210,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
 | 
			
		|||
		CreatedUnix: issue.UpdatedUnix,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/attachment"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/context/upload"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -156,6 +157,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		|||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,9 +212,14 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		|||
		CreatedUnix: comment.Issue.UpdatedUnix,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
			
		||||
			ctx.Error(http.StatusUnprocessableEntity, "", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := comment.LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -180,7 +180,7 @@ func Migrate(ctx *context.APIContext) {
 | 
			
		|||
		Status:         repo_model.RepositoryBeingMigrated,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		handleMigrateError(ctx, repoOwner, remoteAddr, err)
 | 
			
		||||
		handleMigrateError(ctx, repoOwner, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -207,7 +207,7 @@ func Migrate(ctx *context.APIContext) {
 | 
			
		|||
	}()
 | 
			
		||||
 | 
			
		||||
	if repo, err = migrations.MigrateRepository(graceful.GetManager().HammerContext(), ctx.Doer, repoOwner.Name, opts, nil); err != nil {
 | 
			
		||||
		handleMigrateError(ctx, repoOwner, remoteAddr, err)
 | 
			
		||||
		handleMigrateError(ctx, repoOwner, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +215,7 @@ func Migrate(ctx *context.APIContext) {
 | 
			
		|||
	ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, remoteAddr string, err error) {
 | 
			
		||||
func handleMigrateError(ctx *context.APIContext, repoOwner *user_model.User, err error) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case repo_model.IsErrRepoAlreadyExist(err):
 | 
			
		||||
		ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -601,12 +601,19 @@ func EditPullRequest(ctx *context.APIContext) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oldTitle := issue.Title
 | 
			
		||||
	if len(form.Title) > 0 {
 | 
			
		||||
		issue.Title = form.Title
 | 
			
		||||
		err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeTitle", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if form.Body != nil {
 | 
			
		||||
		err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	if len(form.Body) > 0 {
 | 
			
		||||
		issue.Content = form.Body
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update or remove deadline if set
 | 
			
		||||
| 
						 | 
				
			
			@ -683,24 +690,14 @@ func EditPullRequest(ctx *context.APIContext) {
 | 
			
		|||
			ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		issue.IsClosed = api.StateClosed == api.StateType(*form.State)
 | 
			
		||||
	}
 | 
			
		||||
	statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
 | 
			
		||||
			if issues_model.IsErrDependenciesLeft(err) {
 | 
			
		||||
				ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", err)
 | 
			
		||||
			ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	if titleChanged {
 | 
			
		||||
		notify_service.IssueChangeTitle(ctx, ctx.Doer, issue, oldTitle)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if statusChangeComment != nil {
 | 
			
		||||
		notify_service.IssueChangeStatus(ctx, ctx.Doer, "", issue, statusChangeComment, issue.IsClosed)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// change pull target branch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -422,6 +422,13 @@ type swaggerBlockedUserList struct {
 | 
			
		|||
	Body []api.BlockedUser `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TasksList
 | 
			
		||||
// swagger:response TasksList
 | 
			
		||||
type swaggerRepoTasksList struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.ActionTaskResponse `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// swagger:response Compare
 | 
			
		||||
type swaggerCompare struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,10 +6,8 @@ package user
 | 
			
		|||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/perm"
 | 
			
		||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	unit_model "code.gitea.io/gitea/models/unit"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/utils"
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +42,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
 | 
			
		|||
			ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
 | 
			
		||||
		if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAccess() {
 | 
			
		||||
			apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -114,7 +114,6 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(branchesToSync) > 0 {
 | 
			
		||||
			if gitRepo == nil {
 | 
			
		||||
			var err error
 | 
			
		||||
			gitRepo, err = gitrepo.OpenRepository(ctx, repo)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +123,6 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
 | 
			
		|||
				})
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var (
 | 
			
		||||
				branchNames = make([]string, 0, len(branchesToSync))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -158,7 +158,7 @@ func DashboardPost(ctx *context.Context) {
 | 
			
		|||
		switch form.Op {
 | 
			
		||||
		case "sync_repo_branches":
 | 
			
		||||
			go func() {
 | 
			
		||||
				if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), ctx.Doer.ID); err != nil {
 | 
			
		||||
				if err := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext()); err != nil {
 | 
			
		||||
					log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
 | 
			
		||||
				}
 | 
			
		||||
			}()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -471,8 +471,9 @@ func AuthorizeOAuth(ctx *context.Context) {
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Redirect if user already granted access
 | 
			
		||||
	if grant != nil {
 | 
			
		||||
	// Redirect if user already granted access and the application is confidential.
 | 
			
		||||
	// I.e. always require authorization for public clients as recommended by RFC 6749 Section 10.2
 | 
			
		||||
	if app.ConfidentialClient && grant != nil {
 | 
			
		||||
		code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			handleServerError(ctx, form.State, form.RedirectURI)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -287,7 +287,7 @@ func GetFeedType(name string, req *http.Request) (bool, string, string) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item
 | 
			
		||||
func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, isReleasesOnly bool) (items []*feeds.Item, err error) {
 | 
			
		||||
func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release) (items []*feeds.Item, err error) {
 | 
			
		||||
	for _, rel := range releases {
 | 
			
		||||
		err := rel.LoadAttributes(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas
 | 
			
		|||
		Created:     time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	feed.Items, err = releasesToFeedItems(ctx, releases, isReleasesOnly)
 | 
			
		||||
	feed.Items, err = releasesToFeedItems(ctx, releases)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("releasesToFeedItems", err)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -182,7 +182,7 @@ func createProvider(providerName string, source *Source) (goth.Provider, error)
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	// always set the name if provider is created so we can support multiple setups of 1 provider
 | 
			
		||||
	if err == nil && provider != nil {
 | 
			
		||||
	if provider != nil {
 | 
			
		||||
		provider.SetName(providerName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -234,9 +234,7 @@ func (b *Base) plainTextInternal(skip, status int, bs []byte) {
 | 
			
		|||
	b.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
 | 
			
		||||
	b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
 | 
			
		||||
	b.Resp.WriteHeader(status)
 | 
			
		||||
	if _, err := b.Resp.Write(bs); err != nil {
 | 
			
		||||
		log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
 | 
			
		||||
	}
 | 
			
		||||
	_, _ = b.Resp.Write(bs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PlainTextBytes renders bytes as plain text
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ import (
 | 
			
		|||
	"path"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +81,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
	if err == nil || errors.Is(err, syscall.EPIPE) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -810,7 +810,7 @@ func (rt RepoRefType) RefTypeIncludesTags() bool {
 | 
			
		|||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getRefNameFromPath(ctx *Base, repo *Repository, path string, isExist func(string) bool) string {
 | 
			
		||||
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
 | 
			
		||||
	refName := ""
 | 
			
		||||
	parts := strings.Split(path, "/")
 | 
			
		||||
	for i, part := range parts {
 | 
			
		||||
| 
						 | 
				
			
			@ -846,7 +846,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
 | 
			
		|||
		repo.TreePath = path
 | 
			
		||||
		return repo.Repository.DefaultBranch
 | 
			
		||||
	case RepoRefBranch:
 | 
			
		||||
		ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist)
 | 
			
		||||
		ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
 | 
			
		||||
		if len(ref) == 0 {
 | 
			
		||||
			// check if ref is HEAD
 | 
			
		||||
			parts := strings.Split(path, "/")
 | 
			
		||||
| 
						 | 
				
			
			@ -856,7 +856,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
 | 
			
		|||
			}
 | 
			
		||||
 | 
			
		||||
			// maybe it's a renamed branch
 | 
			
		||||
			return getRefNameFromPath(ctx, repo, path, func(s string) bool {
 | 
			
		||||
			return getRefNameFromPath(repo, path, func(s string) bool {
 | 
			
		||||
				b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Error("FindRenamedBranch: %v", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -876,7 +876,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
 | 
			
		|||
 | 
			
		||||
		return ref
 | 
			
		||||
	case RepoRefTag:
 | 
			
		||||
		return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
 | 
			
		||||
		return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
 | 
			
		||||
	case RepoRefCommit:
 | 
			
		||||
		parts := strings.Split(path, "/")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,7 @@ import (
 | 
			
		|||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	actions_model "code.gitea.io/gitea/models/actions"
 | 
			
		||||
	asymkey_model "code.gitea.io/gitea/models/asymkey"
 | 
			
		||||
	"code.gitea.io/gitea/models/auth"
 | 
			
		||||
	git_model "code.gitea.io/gitea/models/git"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +25,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/container"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/services/gitdiff"
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +197,31 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToActionTask convert a actions_model.ActionTask to an api.ActionTask
 | 
			
		||||
func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) {
 | 
			
		||||
	if err := t.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink()
 | 
			
		||||
 | 
			
		||||
	return &api.ActionTask{
 | 
			
		||||
		ID:           t.ID,
 | 
			
		||||
		Name:         t.Job.Name,
 | 
			
		||||
		HeadBranch:   t.Job.Run.PrettyRef(),
 | 
			
		||||
		HeadSHA:      t.Job.CommitSHA,
 | 
			
		||||
		RunNumber:    t.Job.Run.Index,
 | 
			
		||||
		Event:        t.Job.Run.TriggerEvent,
 | 
			
		||||
		DisplayTitle: t.Job.Run.Title,
 | 
			
		||||
		Status:       t.Status.String(),
 | 
			
		||||
		WorkflowID:   t.Job.Run.WorkflowID,
 | 
			
		||||
		URL:          url,
 | 
			
		||||
		CreatedAt:    t.Created.AsLocalTime(),
 | 
			
		||||
		UpdatedAt:    t.Updated.AsLocalTime(),
 | 
			
		||||
		RunStartedAt: t.Started.AsLocalTime(),
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
 | 
			
		||||
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
 | 
			
		||||
	verif := asymkey_model.ParseCommitWithSignature(ctx, c)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -211,13 +211,11 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
 | 
			
		|||
		IsArchived:  label.IsArchived(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	labelBelongsToRepo := label.BelongsToRepo()
 | 
			
		||||
 | 
			
		||||
	// calculate URL
 | 
			
		||||
	if label.BelongsToRepo() && repo != nil {
 | 
			
		||||
		if repo != nil {
 | 
			
		||||
	if labelBelongsToRepo && repo != nil {
 | 
			
		||||
		result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
 | 
			
		||||
		}
 | 
			
		||||
	} else { // BelongsToOrg
 | 
			
		||||
		if org != nil {
 | 
			
		||||
			result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID)
 | 
			
		||||
| 
						 | 
				
			
			@ -226,6 +224,10 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if labelBelongsToRepo && repo == nil {
 | 
			
		||||
		log.Error("ToLabel did not get repo to calculate url for label with id '%d'", label.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ type commonStorageCheckOptions struct {
 | 
			
		|||
	name       string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func commonCheckStorage(ctx context.Context, logger log.Logger, autofix bool, opts *commonStorageCheckOptions) error {
 | 
			
		||||
func commonCheckStorage(logger log.Logger, autofix bool, opts *commonStorageCheckOptions) error {
 | 
			
		||||
	totalCount, orphanedCount := 0, 0
 | 
			
		||||
	totalSize, orphanedSize := int64(0), int64(0)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +98,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.Attachments || opts.All {
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.Attachments,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -116,7 +116,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
				logger.Info("LFS isn't enabled (skipped)")
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.LFS,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -132,7 +132,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.Avatars || opts.All {
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.Avatars,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +146,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.RepoAvatars || opts.All {
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.RepoAvatars,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +160,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		if opts.RepoArchives || opts.All {
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.RepoArchives,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -182,7 +182,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo
 | 
			
		|||
				logger.Info("Packages isn't enabled (skipped)")
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			if err := commonCheckStorage(ctx, logger, autofix,
 | 
			
		||||
			if err := commonCheckStorage(logger, autofix,
 | 
			
		||||
				&commonStorageCheckOptions{
 | 
			
		||||
					storer: storage.Packages,
 | 
			
		||||
					isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -982,25 +982,24 @@ func (g *GiteaLocalUploader) Finish() error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
 | 
			
		||||
	var userid int64
 | 
			
		||||
	var userID int64
 | 
			
		||||
	var err error
 | 
			
		||||
	if g.sameApp {
 | 
			
		||||
		userid, err = g.remapLocalUser(source, target)
 | 
			
		||||
		userID, err = g.remapLocalUser(source)
 | 
			
		||||
	} else {
 | 
			
		||||
		userid, err = g.remapExternalUser(source, target)
 | 
			
		||||
		userID, err = g.remapExternalUser(source)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if userid > 0 {
 | 
			
		||||
		return target.RemapExternalUser("", 0, userid)
 | 
			
		||||
	if userID > 0 {
 | 
			
		||||
		return target.RemapExternalUser("", 0, userID)
 | 
			
		||||
	}
 | 
			
		||||
	return target.RemapExternalUser(source.GetExternalName(), source.GetExternalID(), g.doer.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) (int64, error) {
 | 
			
		||||
func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrated) (int64, error) {
 | 
			
		||||
	userid, ok := g.userMap[source.GetExternalID()]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		name, err := user_model.GetUserNameByID(g.ctx, source.GetExternalID())
 | 
			
		||||
| 
						 | 
				
			
			@ -1018,7 +1017,7 @@ func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrat
 | 
			
		|||
	return userid, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (g *GiteaLocalUploader) remapExternalUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) (userid int64, err error) {
 | 
			
		||||
func (g *GiteaLocalUploader) remapExternalUser(source user_model.ExternalUserMigrated) (userid int64, err error) {
 | 
			
		||||
	userid, ok := g.userMap[source.GetExternalID()]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		userid, err = user_model.GetUserIDByExternalUserID(g.ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID()))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -90,7 +90,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
 | 
			
		|||
 | 
			
		||||
	pullMirrorsRequested := 0
 | 
			
		||||
	if pullLimit != 0 {
 | 
			
		||||
		if err := repo_model.MirrorsIterate(ctx, pullLimit, func(idx int, bean any) error {
 | 
			
		||||
		if err := repo_model.MirrorsIterate(ctx, pullLimit, func(_ int, bean any) error {
 | 
			
		||||
			if err := handler(bean); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,7 +49,7 @@ var ErrSubmitReviewOnClosedPR = errors.New("can't submit review for a closed or
 | 
			
		|||
 | 
			
		||||
// checkInvalidation checks if the line of code comment got changed by another commit.
 | 
			
		||||
// If the line got changed the comment is going to be invalidated.
 | 
			
		||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
 | 
			
		||||
func checkInvalidation(ctx context.Context, c *issues_model.Comment, repo *git.Repository, branch string) error {
 | 
			
		||||
	// FIXME differentiate between previous and proposed line
 | 
			
		||||
	commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine()))
 | 
			
		||||
	if err != nil && (strings.Contains(err.Error(), "fatal: no such path") || notEnoughLines.MatchString(err.Error())) {
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ func InvalidateCodeComments(ctx context.Context, prs issues_model.PullRequestLis
 | 
			
		|||
		return fmt.Errorf("find code comments: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, comment := range codeComments {
 | 
			
		||||
		if err := checkInvalidation(ctx, comment, doer, repo, branch); err != nil {
 | 
			
		||||
		if err := checkInvalidation(ctx, comment, repo, branch); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.
 | 
			
		|||
			AddTestPullRequestTask(ctx, doer, pr.BaseRepo.ID, pr.BaseBranch, false, "", "", 0)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		return updateHeadByRebaseOnToBase(ctx, pr, doer, message)
 | 
			
		||||
		return updateHeadByRebaseOnToBase(ctx, pr, doer)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := pr.LoadBaseRepo(ctx); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// updateHeadByRebaseOnToBase handles updating a PR's head branch by rebasing it on the PR current base branch
 | 
			
		||||
func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, message string) error {
 | 
			
		||||
func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) error {
 | 
			
		||||
	// "Clone" base repo and add the cache headers for the head repo and branch
 | 
			
		||||
	mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, "")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR
 | 
			
		|||
			return fmt.Errorf("getRepositoryByID: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := adoptRepository(ctx, repoPath, doer, repo, opts.DefaultBranch); err != nil {
 | 
			
		||||
		if err := adoptRepository(ctx, repoPath, repo, opts.DefaultBranch); err != nil {
 | 
			
		||||
			return fmt.Errorf("createDelegateHooks: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -111,7 +111,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR
 | 
			
		|||
	return repo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, repo *repo_model.Repository, defaultBranch string) (err error) {
 | 
			
		||||
func adoptRepository(ctx context.Context, repoPath string, repo *repo_model.Repository, defaultBranch string) (err error) {
 | 
			
		||||
	isExist, err := util.IsExist(repoPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to check if %s exists. Error: %v", repoPath, err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -491,7 +491,7 @@ func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addRepoToBranchSyncQueue(repoID, doerID int64) error {
 | 
			
		||||
func addRepoToBranchSyncQueue(repoID int64) error {
 | 
			
		||||
	return branchSyncQueue.Push(&BranchSyncOptions{
 | 
			
		||||
		RepoID: repoID,
 | 
			
		||||
	})
 | 
			
		||||
| 
						 | 
				
			
			@ -507,9 +507,9 @@ func initBranchSyncQueue(ctx context.Context) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
 | 
			
		||||
func AddAllRepoBranchesToSyncQueue(ctx context.Context) error {
 | 
			
		||||
	if err := db.Iterate(ctx, builder.Eq{"is_empty": false}, func(ctx context.Context, repo *repo_model.Repository) error {
 | 
			
		||||
		return addRepoToBranchSyncQueue(repo.ID, doerID)
 | 
			
		||||
		return addRepoToBranchSyncQueue(repo.ID)
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("run sync all branches failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -211,7 +211,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		for _, file := range opts.Files {
 | 
			
		||||
			if err := handleCheckErrors(file, commit, opts, repo); err != nil {
 | 
			
		||||
			if err := handleCheckErrors(file, commit, opts); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +277,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// handles the check for various issues for ChangeRepoFiles
 | 
			
		||||
func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions, repo *repo_model.Repository) error {
 | 
			
		||||
func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error {
 | 
			
		||||
	if file.Operation == "update" || file.Operation == "delete" {
 | 
			
		||||
		fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ func TestUpdateUser(t *testing.T) {
 | 
			
		|||
		Description:                  optional.Some("description"),
 | 
			
		||||
		AllowGitHook:                 optional.Some(true),
 | 
			
		||||
		AllowImportLocal:             optional.Some(true),
 | 
			
		||||
		MaxRepoCreation:              optional.Some[int](10),
 | 
			
		||||
		MaxRepoCreation:              optional.Some(10),
 | 
			
		||||
		IsRestricted:                 optional.Some(true),
 | 
			
		||||
		IsActive:                     optional.Some(false),
 | 
			
		||||
		IsAdmin:                      optional.Some(true),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,15 @@
 | 
			
		|||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
 | 
			
		||||
	{{if .PackageDescriptor.Metadata.BundleDependencies}}
 | 
			
		||||
		<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.npm.dependencies.bundle"}}</h4>
 | 
			
		||||
		<div class="ui attached segment">
 | 
			
		||||
			{{range .PackageDescriptor.Metadata.BundleDependencies}}
 | 
			
		||||
				{{.}}
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	{{end}}
 | 
			
		||||
 | 
			
		||||
	{{if .PackageDescriptor.Metadata.Keywords}}
 | 
			
		||||
		<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4>
 | 
			
		||||
		<div class="ui attached segment">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,13 +62,13 @@
 | 
			
		|||
	</div>
 | 
			
		||||
 | 
			
		||||
	{{if or .Labels .Assignees}}
 | 
			
		||||
	<div class="tw-flex tw-justify-between">
 | 
			
		||||
		<div class="labels-list tw-flex-1">
 | 
			
		||||
	<div class="issue-card-bottom">
 | 
			
		||||
		<div class="labels-list">
 | 
			
		||||
			{{range .Labels}}
 | 
			
		||||
				<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
		<div class="tw-flex tw-flex-wrap tw-content-start tw-gap-1">
 | 
			
		||||
		<div class="issue-card-assignees">
 | 
			
		||||
			{{range .Assignees}}
 | 
			
		||||
				<a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										155
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										155
									
								
								templates/swagger/v1_json.tmpl
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -3924,6 +3924,66 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/actions/tasks": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "repository"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "List a repository's action tasks",
 | 
			
		||||
        "operationId": "ListActionTasks",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "description": "page number of results to return (1-based)",
 | 
			
		||||
            "name": "page",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "description": "page size of results, default maximum page size is 50",
 | 
			
		||||
            "name": "limit",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/TasksList"
 | 
			
		||||
          },
 | 
			
		||||
          "400": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          },
 | 
			
		||||
          "403": {
 | 
			
		||||
            "$ref": "#/responses/forbidden"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/notFound"
 | 
			
		||||
          },
 | 
			
		||||
          "409": {
 | 
			
		||||
            "$ref": "#/responses/conflict"
 | 
			
		||||
          },
 | 
			
		||||
          "422": {
 | 
			
		||||
            "$ref": "#/responses/validationError"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/actions/variables": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
| 
						 | 
				
			
			@ -7605,6 +7665,9 @@
 | 
			
		|||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          },
 | 
			
		||||
          "422": {
 | 
			
		||||
            "$ref": "#/responses/validationError"
 | 
			
		||||
          },
 | 
			
		||||
          "423": {
 | 
			
		||||
            "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
          }
 | 
			
		||||
| 
						 | 
				
			
			@ -8231,6 +8294,9 @@
 | 
			
		|||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          },
 | 
			
		||||
          "422": {
 | 
			
		||||
            "$ref": "#/responses/validationError"
 | 
			
		||||
          },
 | 
			
		||||
          "423": {
 | 
			
		||||
            "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
          }
 | 
			
		||||
| 
						 | 
				
			
			@ -18312,6 +18378,89 @@
 | 
			
		|||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "ActionTask": {
 | 
			
		||||
      "description": "ActionTask represents a ActionTask",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "created_at": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "format": "date-time",
 | 
			
		||||
          "x-go-name": "CreatedAt"
 | 
			
		||||
        },
 | 
			
		||||
        "display_title": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "DisplayTitle"
 | 
			
		||||
        },
 | 
			
		||||
        "event": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Event"
 | 
			
		||||
        },
 | 
			
		||||
        "head_branch": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "HeadBranch"
 | 
			
		||||
        },
 | 
			
		||||
        "head_sha": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "HeadSHA"
 | 
			
		||||
        },
 | 
			
		||||
        "id": {
 | 
			
		||||
          "type": "integer",
 | 
			
		||||
          "format": "int64",
 | 
			
		||||
          "x-go-name": "ID"
 | 
			
		||||
        },
 | 
			
		||||
        "name": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Name"
 | 
			
		||||
        },
 | 
			
		||||
        "run_number": {
 | 
			
		||||
          "type": "integer",
 | 
			
		||||
          "format": "int64",
 | 
			
		||||
          "x-go-name": "RunNumber"
 | 
			
		||||
        },
 | 
			
		||||
        "run_started_at": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "format": "date-time",
 | 
			
		||||
          "x-go-name": "RunStartedAt"
 | 
			
		||||
        },
 | 
			
		||||
        "status": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Status"
 | 
			
		||||
        },
 | 
			
		||||
        "updated_at": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "format": "date-time",
 | 
			
		||||
          "x-go-name": "UpdatedAt"
 | 
			
		||||
        },
 | 
			
		||||
        "url": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "URL"
 | 
			
		||||
        },
 | 
			
		||||
        "workflow_id": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "WorkflowID"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "ActionTaskResponse": {
 | 
			
		||||
      "description": "ActionTaskResponse returns a ActionTask",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "total_count": {
 | 
			
		||||
          "type": "integer",
 | 
			
		||||
          "format": "int64",
 | 
			
		||||
          "x-go-name": "TotalCount"
 | 
			
		||||
        },
 | 
			
		||||
        "workflow_runs": {
 | 
			
		||||
          "type": "array",
 | 
			
		||||
          "items": {
 | 
			
		||||
            "$ref": "#/definitions/ActionTask"
 | 
			
		||||
          },
 | 
			
		||||
          "x-go-name": "Entries"
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "x-go-package": "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
    },
 | 
			
		||||
    "ActionVariable": {
 | 
			
		||||
      "description": "ActionVariable return value of the query API",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
| 
						 | 
				
			
			@ -25895,6 +26044,12 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "TasksList": {
 | 
			
		||||
      "description": "TasksList",
 | 
			
		||||
      "schema": {
 | 
			
		||||
        "$ref": "#/definitions/ActionTaskResponse"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "Team": {
 | 
			
		||||
      "description": "Team",
 | 
			
		||||
      "schema": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -240,3 +240,31 @@ func TestAPIDeleteCommentAttachment(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: attachment.ID, CommentID: comment.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICreateCommentAttachmentWithUnallowedFile(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
 | 
			
		||||
 | 
			
		||||
	filename := "file.bad"
 | 
			
		||||
	body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	// Setup multi-part.
 | 
			
		||||
	writer := multipart.NewWriter(body)
 | 
			
		||||
	_, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets", repoOwner.Name, repo.Name, comment.ID), body).
 | 
			
		||||
		AddTokenAuth(token).
 | 
			
		||||
		SetHeader("Content-Type", writer.FormDataContentType())
 | 
			
		||||
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -173,6 +173,33 @@ func TestAPICreateIssueAttachmentAutoDate(t *testing.T) {
 | 
			
		|||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICreateIssueAttachmentWithUnallowedFile(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue)
 | 
			
		||||
 | 
			
		||||
	filename := "file.bad"
 | 
			
		||||
	body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	// Setup multi-part.
 | 
			
		||||
	writer := multipart.NewWriter(body)
 | 
			
		||||
	_, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestWithBody(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets", repoOwner.Name, repo.Name, issue.Index), body).
 | 
			
		||||
		AddTokenAuth(token)
 | 
			
		||||
	req.Header.Add("Content-Type", writer.FormDataContentType())
 | 
			
		||||
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusUnprocessableEntity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIEditIssueAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -188,6 +188,10 @@ func TestAPIEditIssue(t *testing.T) {
 | 
			
		|||
	issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
 | 
			
		||||
	repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID})
 | 
			
		||||
 | 
			
		||||
	// check comment history
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: issueAfter.ID, OldTitle: issueBefore.Title, NewTitle: title})
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: issueAfter.ID, ContentText: body, IsFirstCreated: false})
 | 
			
		||||
 | 
			
		||||
	// check deleted user
 | 
			
		||||
	assert.Equal(t, int64(500), issueAfter.PosterID)
 | 
			
		||||
	assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -169,7 +169,7 @@ nwIDAQAB
 | 
			
		|||
			assert.Nil(t, u)
 | 
			
		||||
			assert.Error(t, err)
 | 
			
		||||
 | 
			
		||||
			signRequest := func(t *testing.T, rw *RequestWrapper, version string) {
 | 
			
		||||
			signRequest := func(rw *RequestWrapper, version string) {
 | 
			
		||||
				req := rw.Request
 | 
			
		||||
				username := req.Header.Get("X-Ops-Userid")
 | 
			
		||||
				if version != "1.0" && version != "1.3" {
 | 
			
		||||
| 
						 | 
				
			
			@ -255,7 +255,7 @@ nwIDAQAB
 | 
			
		|||
				t.Run(v, func(t *testing.T) {
 | 
			
		||||
					defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
					signRequest(t, req, v)
 | 
			
		||||
					signRequest(req, v)
 | 
			
		||||
					u, err = auth.Verify(req.Request, nil, nil, nil)
 | 
			
		||||
					assert.NotNil(t, u)
 | 
			
		||||
					assert.NoError(t, err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -223,23 +223,33 @@ func TestAPIEditPull(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	session := loginUser(t, owner10.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
	title := "create a success pr"
 | 
			
		||||
	req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
 | 
			
		||||
		Head:  "develop",
 | 
			
		||||
		Base:  "master",
 | 
			
		||||
		Title: "create a success pr",
 | 
			
		||||
		Title: title,
 | 
			
		||||
	}).AddTokenAuth(token)
 | 
			
		||||
	pull := new(api.PullRequest)
 | 
			
		||||
	apiPull := new(api.PullRequest)
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	DecodeJSON(t, resp, pull)
 | 
			
		||||
	assert.EqualValues(t, "master", pull.Base.Name)
 | 
			
		||||
	DecodeJSON(t, resp, apiPull)
 | 
			
		||||
	assert.EqualValues(t, "master", apiPull.Base.Name)
 | 
			
		||||
 | 
			
		||||
	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{
 | 
			
		||||
	newTitle := "edit a this pr"
 | 
			
		||||
	newBody := "edited body"
 | 
			
		||||
	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, apiPull.Index), &api.EditPullRequestOption{
 | 
			
		||||
		Base:  "feature/1",
 | 
			
		||||
		Title: "edit a this pr",
 | 
			
		||||
		Title: newTitle,
 | 
			
		||||
		Body:  &newBody,
 | 
			
		||||
	}).AddTokenAuth(token)
 | 
			
		||||
	resp = MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	DecodeJSON(t, resp, pull)
 | 
			
		||||
	assert.EqualValues(t, "feature/1", pull.Base.Name)
 | 
			
		||||
	DecodeJSON(t, resp, apiPull)
 | 
			
		||||
	assert.EqualValues(t, "feature/1", apiPull.Base.Name)
 | 
			
		||||
	// check comment history
 | 
			
		||||
	pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID})
 | 
			
		||||
	err := pull.LoadIssue(db.DefaultContext)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: pull.Issue.ID, OldTitle: title, NewTitle: newTitle})
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &issues_model.ContentHistory{IssueID: pull.Issue.ID, ContentText: newBody, IsFirstCreated: false})
 | 
			
		||||
 | 
			
		||||
	req = NewRequestWithJSON(t, http.MethodPatch, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner10.Name, repo10.Name, pull.Index), &api.EditPullRequestOption{
 | 
			
		||||
		Base: "not-exist",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,7 +77,7 @@ func TestAPIListReleases(t *testing.T) {
 | 
			
		|||
	testFilterByLen(true, url.Values{"draft": {"true"}, "pre-release": {"true"}}, 0, "there is no pre-release draft")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createNewReleaseUsingAPI(t *testing.T, session *TestSession, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release {
 | 
			
		||||
func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User, repo *repo_model.Repository, name, target, title, desc string) *api.Release {
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
 | 
			
		||||
	req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
 | 
			
		||||
		TagName:      name,
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +120,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
 | 
			
		|||
	target, err := gitRepo.GetTagCommitID("v0.0.1")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	newRelease := createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", target, "v0.0.1", "test")
 | 
			
		||||
	newRelease := createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", target, "v0.0.1", "test")
 | 
			
		||||
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d", owner.Name, repo.Name, newRelease.ID)
 | 
			
		||||
	req := NewRequest(t, "GET", urlStr).
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ func TestAPICreateReleaseToDefaultBranch(t *testing.T) {
 | 
			
		|||
	session := loginUser(t, owner.LowerName)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
 | 
			
		||||
	createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test")
 | 
			
		||||
	createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -185,7 +185,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
 | 
			
		|||
	err = gitRepo.CreateTag("v0.0.1", "master")
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test")
 | 
			
		||||
	createNewReleaseUsingAPI(t, token, owner, repo, "v0.0.1", "", "v0.0.1", "test")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIGetLatestRelease(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -237,7 +237,7 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) {
 | 
			
		|||
	session := loginUser(t, owner.LowerName)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
 | 
			
		||||
	createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
	createNewReleaseUsingAPI(t, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
 | 
			
		||||
	// delete release
 | 
			
		||||
	req := NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag", owner.Name, repo.Name)).
 | 
			
		||||
| 
						 | 
				
			
			@ -263,7 +263,7 @@ func TestAPIUploadAssetRelease(t *testing.T) {
 | 
			
		|||
	session := loginUser(t, owner.LowerName)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
 | 
			
		||||
	r := createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
	r := createNewReleaseUsingAPI(t, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
 | 
			
		||||
	filename := "image.png"
 | 
			
		||||
	buff := generateImg()
 | 
			
		||||
| 
						 | 
				
			
			@ -335,7 +335,7 @@ func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	name := "ReleaseDownloadCount"
 | 
			
		||||
 | 
			
		||||
	createNewReleaseUsingAPI(t, session, token, owner, repo, name, "", name, "test")
 | 
			
		||||
	createNewReleaseUsingAPI(t, token, owner, repo, name, "", name, "test")
 | 
			
		||||
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, name)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,7 +80,7 @@ func TestAPIDeleteTagByName(t *testing.T) {
 | 
			
		|||
	_ = MakeRequest(t, req, http.StatusNoContent)
 | 
			
		||||
 | 
			
		||||
	// Make sure that actual releases can't be deleted outright
 | 
			
		||||
	createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
	createNewReleaseUsingAPI(t, token, owner, repo, "release-tag", "", "Release Tag", "test")
 | 
			
		||||
 | 
			
		||||
	req = NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag", owner.Name, repo.Name)).
 | 
			
		||||
		AddTokenAuth(token)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	unit_model "code.gitea.io/gitea/models/unit"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
| 
						 | 
				
			
			@ -326,6 +327,39 @@ func TestAPIOrgRepos(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories.
 | 
			
		||||
func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"})
 | 
			
		||||
	org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID})
 | 
			
		||||
 | 
			
		||||
	// Disable code repository unit.
 | 
			
		||||
	var units []unit_model.Type
 | 
			
		||||
	units = append(units, unit_model.TypeCode)
 | 
			
		||||
 | 
			
		||||
	if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil {
 | 
			
		||||
		assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode))
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, "user2")
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name).
 | 
			
		||||
		AddTokenAuth(token)
 | 
			
		||||
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	var apiRepos []*api.Repository
 | 
			
		||||
	DecodeJSON(t, resp, &apiRepos)
 | 
			
		||||
 | 
			
		||||
	var repoNames []string
 | 
			
		||||
	for _, r := range apiRepos {
 | 
			
		||||
		repoNames = append(repoNames, r.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Contains(t, repoNames, repo21.Name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
 | 
			
		||||
| 
						 | 
				
			
			@ -684,7 +718,9 @@ func TestAPIRepoGetReviewers(t *testing.T) {
 | 
			
		|||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	var reviewers []*api.User
 | 
			
		||||
	DecodeJSON(t, resp, &reviewers)
 | 
			
		||||
	assert.Len(t, reviewers, 4)
 | 
			
		||||
	if assert.Len(t, reviewers, 3) {
 | 
			
		||||
		assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIRepoGetAssignees(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ func testGit(t *testing.T, u *url.URL) {
 | 
			
		|||
		rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
		mediaTest(t, &httpContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
 | 
			
		||||
		t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "master", "test/head"))
 | 
			
		||||
		t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &httpContext, "test/head"))
 | 
			
		||||
		t.Run("InternalReferences", doInternalReferences(&httpContext, dstPath))
 | 
			
		||||
		t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
 | 
			
		||||
		t.Run("AutoMerge", doAutoPRMerge(&httpContext, dstPath))
 | 
			
		||||
| 
						 | 
				
			
			@ -125,7 +125,7 @@ func testGit(t *testing.T, u *url.URL) {
 | 
			
		|||
			rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
			mediaTest(t, &sshContext, little, big, littleLFS, bigLFS)
 | 
			
		||||
 | 
			
		||||
			t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "master", "test/head2"))
 | 
			
		||||
			t.Run("CreateAgitFlowPull", doCreateAgitFlowPull(dstPath, &sshContext, "test/head2"))
 | 
			
		||||
			t.Run("InternalReferences", doInternalReferences(&sshContext, dstPath))
 | 
			
		||||
			t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
 | 
			
		||||
			t.Run("MergeFork", func(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -333,9 +333,6 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin
 | 
			
		|||
		}
 | 
			
		||||
		written += n
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Commit
 | 
			
		||||
	// Now here we should explicitly allow lfs filters to run
 | 
			
		||||
| 
						 | 
				
			
			@ -750,7 +747,7 @@ func doInternalReferences(ctx *APITestContext, dstPath string) func(t *testing.T
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
 | 
			
		||||
func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string) func(t *testing.T) {
 | 
			
		||||
	return func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -455,8 +455,6 @@ func TestRecentlyPushed(t *testing.T) {
 | 
			
		|||
		t.Run("unrelated branches are not shown", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
 | 
			
		||||
 | 
			
		||||
			// Create a new branch with no relation to the default branch.
 | 
			
		||||
			// 1. Create a new Tree object
 | 
			
		||||
			cmd := git.NewCommand(db.DefaultContext, "write-tree")
 | 
			
		||||
| 
						 | 
				
			
			@ -473,7 +471,7 @@ func TestRecentlyPushed(t *testing.T) {
 | 
			
		|||
			_, _, gitErr = cmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
 | 
			
		||||
			assert.NoError(t, gitErr)
 | 
			
		||||
			// 4. Sync the git repo to the database
 | 
			
		||||
			syncErr := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), adminUser.ID)
 | 
			
		||||
			syncErr := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext())
 | 
			
		||||
			assert.NoError(t, syncErr)
 | 
			
		||||
			// 5. Add a fresh commit, so that FindRecentlyPushedBranches has
 | 
			
		||||
			// something to find.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										31
									
								
								tests/integration/repo_archive_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/integration/repo_archive_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
// Copyright 2024 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package integration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/test"
 | 
			
		||||
	"code.gitea.io/gitea/routers"
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRepoDownloadArchive(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
	defer test.MockVariableValue(&setting.EnableGzip, true)()
 | 
			
		||||
	defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "GET", "/user2/repo1/archive/master.zip")
 | 
			
		||||
	req.Header.Set("Accept-Encoding", "gzip")
 | 
			
		||||
	resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	bs, err := io.ReadAll(resp.Body)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Empty(t, resp.Header().Get("Content-Encoding"))
 | 
			
		||||
	assert.Equal(t, 320, len(bs))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +215,7 @@ func TestBadges(t *testing.T) {
 | 
			
		|||
				token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
 | 
			
		||||
				err := release.CreateNewTag(git.DefaultContext, repo.Owner, repo, "main", "repo-name-2.0", "dash in the tag name")
 | 
			
		||||
				assert.NoError(t, err)
 | 
			
		||||
				createNewReleaseUsingAPI(t, session, token, repo.Owner, repo, "repo-name-2.0", "main", "dashed release", "dashed release")
 | 
			
		||||
				createNewReleaseUsingAPI(t, token, repo.Owner, repo, "repo-name-2.0", "main", "dashed release", "dashed release")
 | 
			
		||||
 | 
			
		||||
				req := NewRequestf(t, "GET", "/user2/%s/badges/release.svg", repo.Name)
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusSeeOther)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ import (
 | 
			
		|||
	git_model "code.gitea.io/gitea/models/git"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/git"
 | 
			
		||||
	"code.gitea.io/gitea/modules/graceful"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
| 
						 | 
				
			
			@ -170,7 +169,6 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
func TestDatabaseMissingABranch(t *testing.T) {
 | 
			
		||||
	onGiteaRun(t, func(t *testing.T, URL *url.URL) {
 | 
			
		||||
		adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
 | 
			
		||||
		session := loginUser(t, "user2")
 | 
			
		||||
 | 
			
		||||
		// Create two branches
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +176,7 @@ func TestDatabaseMissingABranch(t *testing.T) {
 | 
			
		|||
		testCreateBranch(t, session, "user2", "repo1", "branch/master", "will-be-missing", http.StatusSeeOther)
 | 
			
		||||
 | 
			
		||||
		// Run the repo branch sync, to ensure the db and git agree.
 | 
			
		||||
		err2 := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), adminUser.ID)
 | 
			
		||||
		err2 := repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext())
 | 
			
		||||
		assert.NoError(t, err2)
 | 
			
		||||
 | 
			
		||||
		// Delete one branch from git only, leaving it in the database
 | 
			
		||||
| 
						 | 
				
			
			@ -197,7 +195,7 @@ func TestDatabaseMissingABranch(t *testing.T) {
 | 
			
		|||
		assert.GreaterOrEqual(t, firstBranchCount, 3)
 | 
			
		||||
 | 
			
		||||
		// Run the repo branch sync again
 | 
			
		||||
		err2 = repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext(), adminUser.ID)
 | 
			
		||||
		err2 = repo_service.AddAllRepoBranchesToSyncQueue(graceful.GetManager().ShutdownContext())
 | 
			
		||||
		assert.NoError(t, err2)
 | 
			
		||||
 | 
			
		||||
		// Verify that loading the repo's branches page works still, and that it
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -78,7 +78,7 @@ func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getExpectedFileResponseForRepofilesDelete(u *url.URL) *api.FileResponse {
 | 
			
		||||
func getExpectedFileResponseForRepofilesDelete() *api.FileResponse {
 | 
			
		||||
	// Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined
 | 
			
		||||
	return &api.FileResponse{
 | 
			
		||||
		Content: nil,
 | 
			
		||||
| 
						 | 
				
			
			@ -418,7 +418,7 @@ func testDeleteRepoFiles(t *testing.T, u *url.URL) {
 | 
			
		|||
	t.Run("Delete README.md file", func(t *testing.T) {
 | 
			
		||||
		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u)
 | 
			
		||||
		expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
 | 
			
		||||
		assert.NotNil(t, filesResponse)
 | 
			
		||||
		assert.Nil(t, filesResponse.Files[0])
 | 
			
		||||
		assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
 | 
			
		||||
| 
						 | 
				
			
			@ -460,7 +460,7 @@ func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
 | 
			
		|||
	t.Run("Delete README.md without Branch Name", func(t *testing.T) {
 | 
			
		||||
		filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u)
 | 
			
		||||
		expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
 | 
			
		||||
		assert.NotNil(t, filesResponse)
 | 
			
		||||
		assert.Nil(t, filesResponse.Files[0])
 | 
			
		||||
		assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,10 @@
 | 
			
		|||
  padding: 0 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#user-heatmap .vch__day__square:hover {
 | 
			
		||||
  outline: 1.5px solid var(--color-text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* move the "? contributions in the last ? months" text from top to bottom */
 | 
			
		||||
#user-heatmap .total-contributions {
 | 
			
		||||
  font-size: 11px;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2368,18 +2368,12 @@ td .commit-summary {
 | 
			
		|||
  display: inline-flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  gap: 2.5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.labels-list a {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.labels-list .label {
 | 
			
		||||
  padding: 0 6px;
 | 
			
		||||
  margin: 0 !important;
 | 
			
		||||
  min-height: 20px;
 | 
			
		||||
  display: inline-flex !important;
 | 
			
		||||
  line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,3 +23,18 @@
 | 
			
		|||
.issue-card.sortable-chosen .issue-card-title {
 | 
			
		||||
  cursor: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.issue-card-bottom {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  gap: 0.25em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.issue-card-assignees {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  gap: 0.25em;
 | 
			
		||||
  justify-content: end;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue