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:
Earl Warren 2024-05-07 22:47:53 +00:00
commit a2c8fe0370
89 changed files with 911 additions and 404 deletions

View file

@ -4,6 +4,7 @@ reportUnusedDisableDirectives: true
ignorePatterns: ignorePatterns:
- /web_src/js/vendor - /web_src/js/vendor
- /web_src/fomantic - /web_src/fomantic
- /public/assets/js
parserOptions: parserOptions:
sourceType: module sourceType: module

View file

@ -767,7 +767,7 @@ generate-backend: $(TAGS_PREREQ) generate-go
.PHONY: generate-go .PHONY: generate-go
generate-go: $(TAGS_PREREQ) generate-go: $(TAGS_PREREQ)
@echo "Running go generate..." @echo "Running go generate..."
@CC= GOOS= GOARCH= $(GO) generate -tags '$(TAGS)' ./... @CC= GOOS= GOARCH= CGO_ENABLED=0 $(GO) generate -tags '$(TAGS)' ./...
.PHONY: merge-locales .PHONY: merge-locales
merge-locales: merge-locales:

View file

@ -1471,7 +1471,7 @@ LEVEL = Info
;; Batch size to send for batched queues ;; Batch size to send for batched queues
;BATCH_LENGTH = 20 ;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 ;; 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`. ;; 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" ;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 ;; For "memory" only, GC interval in seconds, default is 60
;INTERVAL = 60 ;INTERVAL = 60
;; ;;
;; For "redis", "redis-cluster" and "memcache", connection host address ;; For "redis" and "memcache", connection host address
;; redis: `redis://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)
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
;; memcache: `127.0.0.1:11211` ;; memcache: `127.0.0.1:11211`
;; twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000` ;; twoqueue: `{"size":50000,"recent_ratio":0.25,"ghost_ratio":0.5}` or `50000`
;HOST = ;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] ;; Default is "memory". "db" will reuse the configuration in [database]
;PROVIDER = memory ;PROVIDER = memory
;; ;;
;; Provider config options ;; Provider config options
;; memory: doesn't have any config yet ;; memory: doesn't have any config yet
;; file: session file path, e.g. `data/sessions` ;; file: session file path, e.g. `data/sessions`
;; redis: `redis://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)
;; redis-cluster: `redis+cluster://127.0.0.1:6379/0?pool_size=100&idle_timeout=180s`
;; mysql: go-sql-driver/mysql dsn config string, e.g. `root:password@/session_table` ;; 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`_. ;PROVIDER_CONFIG = data/sessions ; Relative paths will be made absolute against _`AppWorkPath`_.
;; ;;

2
go.mod
View file

@ -57,6 +57,7 @@ require (
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.1.2 github.com/gorilla/feeds v1.1.2
github.com/gorilla/sessions v1.2.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/go-version v1.6.0
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.4.0 github.com/huandu/xstrings v1.4.0
@ -203,6 +204,7 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // 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-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect

6
go.sum
View file

@ -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.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= 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/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 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 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= 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/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 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= 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 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=

View file

@ -362,36 +362,16 @@ func GetLatestCommitStatusForRepoCommitIDs(ctx context.Context, repoID int64, co
// FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts // FindRepoRecentCommitStatusContexts returns repository's recent commit status contexts
func FindRepoRecentCommitStatusContexts(ctx context.Context, repoID int64, before time.Duration) ([]string, error) { 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) start := timeutil.TimeStampNow().AddDuration(-before)
results := make([]result, 0, 10)
sess := getBase().And("updated_unix >= ?", start). var contexts []string
Select("max( `index` ) as `index`, sha"). if err := db.GetEngine(ctx).Table("commit_status").
GroupBy("context_hash, sha").OrderBy("max( `index` ) desc") Where("repo_id = ?", repoID).And("updated_unix >= ?", start).
Cols("context").Distinct().Find(&contexts); err != nil {
err := sess.Find(&results)
if err != nil {
return nil, err return nil, err
} }
contexts := make([]string, 0, len(results)) return contexts, nil
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 // NewCommitStatusOptions holds options for creating a CommitStatus

View file

@ -5,11 +5,15 @@ package git_test
import ( import (
"testing" "testing"
"time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "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" "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "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)) 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])
}
}

View file

@ -450,65 +450,6 @@ func UpdateIssueMentions(ctx context.Context, issueID int64, mentions []*user_mo
return nil 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. // 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) { func UpdateIssueDeadline(ctx context.Context, issue *Issue, deadlineUnix timeutil.TimeStamp, doer *user_model.User) (err error) {
// if the deadline hasn't changed do nothing // if the deadline hasn't changed do nothing

View file

@ -34,7 +34,7 @@ func TestXRef_AddCrossReferences(t *testing.T) {
// Comment on PR to reopen issue #1 // Comment on PR to reopen issue #1
content = fmt.Sprintf("content2, reopens #%d", itarget.Index) 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}) 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, issues_model.CommentTypeCommentRef, ref.Type)
assert.Equal(t, pr.RepoID, ref.RefRepoID) 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)) 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}) 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}) r1 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c1.ID})
// Must be ignored // 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}) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i2.ID, RefIssueID: pr.Issue.ID, RefCommentID: c2.ID})
// Must be superseded by c4/r4 // 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}) 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}) r4 := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{IssueID: i3.ID, RefIssueID: pr.Issue.ID, RefCommentID: c4.ID})
refs, err := pr.ResolveCrossReferences(db.DefaultContext) refs, err := pr.ResolveCrossReferences(db.DefaultContext)
@ -168,7 +168,7 @@ func testCreatePR(t *testing.T, repo, doer int64, title, content string) *issues
return pr 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}) d := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: doer})
i := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue}) 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} c := &issues_model.Comment{Type: issues_model.CommentTypeComment, PosterID: doer, Poster: d, IssueID: issue, Issue: i, Content: content}

View file

@ -291,15 +291,15 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) {
func TestAccessibleReposEnv_RepoIDs(t *testing.T) { func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) 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) env, err := organization.AccessibleReposEnv(db.DefaultContext, org, userID)
assert.NoError(t, err) assert.NoError(t, err)
repoIDs, err := env.RepoIDs(1, 100) repoIDs, err := env.RepoIDs(1, 100)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expectedRepoIDs, repoIDs) assert.Equal(t, expectedRepoIDs, repoIDs)
} }
testSuccess(2, 1, 100, []int64{3, 5, 32}) testSuccess(2, []int64{3, 5, 32})
testSuccess(4, 0, 100, []int64{3, 32}) testSuccess(4, []int64{3, 32})
} }
func TestAccessibleReposEnv_Repos(t *testing.T) { func TestAccessibleReposEnv_Repos(t *testing.T) {

View file

@ -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. // and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(uniqueUserIDs)+1) users := make([]*user_model.User, 0, len(uniqueUserIDs)+1)
if len(userIDs) > 0 { 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 return nil, err
} }
} }
@ -117,7 +120,8 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64)
return nil, err 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 { if repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate {
// This a private repository: // This a private repository:

View file

@ -26,10 +26,17 @@ func TestRepoAssignees(t *testing.T) {
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21})
users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21) users, err = repo_model.GetRepoAssignees(db.DefaultContext, repo21)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, users, 3) if assert.Len(t, users, 3) {
assert.Equal(t, users[0].ID, int64(15)) assert.ElementsMatch(t, []int64{15, 16, 18}, []int64{users[0].ID, users[1].ID, users[2].ID})
assert.Equal(t, users[1].ID, int64(18)) }
assert.Equal(t, users[2].ID, int64(16))
// 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) { func TestRepoGetReviewers(t *testing.T) {
@ -41,17 +48,19 @@ func TestRepoGetReviewers(t *testing.T) {
ctx := db.DefaultContext ctx := db.DefaultContext
reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2) reviewers, err := repo_model.GetReviewers(ctx, repo1, 2, 2)
assert.NoError(t, err) 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. // should include doer if doer is not PR poster.
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2) reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 2)
assert.NoError(t, err) 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 // should not include PR poster, if PR poster would be otherwise eligible
reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4) reviewers, err = repo_model.GetReviewers(ctx, repo1, 11, 4)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, reviewers, 3) assert.Len(t, reviewers, 2)
// test private user repo // test private user repo
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})

View file

@ -211,14 +211,14 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
webhook_module.HookEventIssueAssign, webhook_module.HookEventIssueAssign,
webhook_module.HookEventIssueLabel, webhook_module.HookEventIssueLabel,
webhook_module.HookEventIssueMilestone: webhook_module.HookEventIssueMilestone:
return matchIssuesEvent(commit, payload.(*api.IssuePayload), evt) return matchIssuesEvent(payload.(*api.IssuePayload), evt)
case // issue_comment case // issue_comment
webhook_module.HookEventIssueComment, webhook_module.HookEventIssueComment,
// `pull_request_comment` is same as `issue_comment` // `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 // See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment
webhook_module.HookEventPullRequestComment: webhook_module.HookEventPullRequestComment:
return matchIssueCommentEvent(commit, payload.(*api.IssueCommentPayload), evt) return matchIssueCommentEvent(payload.(*api.IssueCommentPayload), evt)
case // pull_request case // pull_request
webhook_module.HookEventPullRequest, webhook_module.HookEventPullRequest,
@ -232,19 +232,19 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web
case // pull_request_review case // pull_request_review
webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewApproved,
webhook_module.HookEventPullRequestReviewRejected: webhook_module.HookEventPullRequestReviewRejected:
return matchPullRequestReviewEvent(commit, payload.(*api.PullRequestPayload), evt) return matchPullRequestReviewEvent(payload.(*api.PullRequestPayload), evt)
case // pull_request_review_comment case // pull_request_review_comment
webhook_module.HookEventPullRequestReviewComment: webhook_module.HookEventPullRequestReviewComment:
return matchPullRequestReviewCommentEvent(commit, payload.(*api.PullRequestPayload), evt) return matchPullRequestReviewCommentEvent(payload.(*api.PullRequestPayload), evt)
case // release case // release
webhook_module.HookEventRelease: webhook_module.HookEventRelease:
return matchReleaseEvent(commit, payload.(*api.ReleasePayload), evt) return matchReleaseEvent(payload.(*api.ReleasePayload), evt)
case // registry_package case // registry_package
webhook_module.HookEventPackage: webhook_module.HookEventPackage:
return matchPackageEvent(commit, payload.(*api.PackagePayload), evt) return matchPackageEvent(payload.(*api.PackagePayload), evt)
default: default:
log.Warn("unsupported event %q", triggedEvent) 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()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true
@ -498,7 +498,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
return activityTypeMatched && matchTimes == len(evt.Acts()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true
@ -530,7 +530,7 @@ func matchIssueCommentEvent(commit *git.Commit, issueCommentPayload *api.IssueCo
return matchTimes == len(evt.Acts()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true
@ -579,7 +579,7 @@ func matchPullRequestReviewEvent(commit *git.Commit, prPayload *api.PullRequestP
return matchTimes == len(evt.Acts()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true
@ -628,7 +628,7 @@ func matchPullRequestReviewCommentEvent(commit *git.Commit, prPayload *api.PullR
return matchTimes == len(evt.Acts()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true
@ -665,7 +665,7 @@ func matchReleaseEvent(commit *git.Commit, payload *api.ReleasePayload, evt *job
return matchTimes == len(evt.Acts()) 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 // with no special filter parameters
if len(evt.Acts()) == 0 { if len(evt.Acts()) == 0 {
return true return true

View file

@ -4,12 +4,11 @@
package pwn package pwn
import ( import (
"math/rand/v2"
"net/http" "net/http"
"strings"
"testing" "testing"
"time" "time"
"github.com/h2non/gock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -18,86 +17,34 @@ var client = New(WithHTTP(&http.Client{
})) }))
func TestPassword(t *testing.T) { func TestPassword(t *testing.T) {
// Check input error defer gock.Off()
_, err := client.CheckPassword("", false)
count, err := client.CheckPassword("", false)
assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword") assert.ErrorIs(t, err, ErrEmptyPassword, "blank input should return ErrEmptyPassword")
assert.Equal(t, -1, count)
// Should fail gock.New("https://api.pwnedpasswords.com").Get("/range/5c1d8").Times(1).Reply(200).BodyString("EAF2F254732680E8AC339B84F3266ECCBB5:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2")
fail := "password1234" count, err = client.CheckPassword("pwned", false)
count, err := client.CheckPassword(fail, false)
assert.NotEmpty(t, count, "%s should fail as a password", fail)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 1, count)
// Should fail (with padding) gock.New("https://api.pwnedpasswords.com").Get("/range/ba189").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4")
failPad := "administrator" count, err = client.CheckPassword("notpwned", false)
count, err = client.CheckPassword(failPad, true)
assert.NotEmpty(t, count, "%s should fail as a password", failPad)
assert.NoError(t, err) 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 gock.New("https://api.pwnedpasswords.com").Get("/range/a1733").Times(1).Reply(200).BodyString("C4CE0F1F0062B27B9E2F41AF0C08218017C:1\r\nFC446EB88938834178CB9322C1EE273C2A7:2\r\nFE81480327C992FE62065A827429DD1318B:0")
// with hopefully minimal error. Try five times? count, err = client.CheckPassword("paddedpwned", true)
assert.Condition(t, func() bool { assert.NoError(t, err)
for i := 0; i <= 5; i++ { assert.Equal(t, 1, count)
count, err = client.CheckPassword(testPassword(), false)
assert.NoError(t, err)
if count == 0 {
return true
}
}
return false
}, "no generated passwords passed. there is a chance this is a fluke")
// Again, but with padded responses gock.New("https://api.pwnedpasswords.com").Get("/range/5617b").Times(1).Reply(200).BodyString("FD4CB34F0378BCB15D23F6FFD28F0775C9E:3\r\nFDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0")
assert.Condition(t, func() bool { count, err = client.CheckPassword("paddednotpwned", true)
for i := 0; i <= 5; i++ { assert.NoError(t, err)
count, err = client.CheckPassword(testPassword(), true) assert.Equal(t, 0, count)
assert.NoError(t, err)
if count == 0 { gock.New("https://api.pwnedpasswords.com").Get("/range/79082").Times(1).Reply(200).BodyString("FDF342FCD8C3611DAE4D76E8A992A3E4169:4\r\nFE81480327C992FE62065A827429DD1318B:0\r\nAFEF386F56EB0B4BE314E07696E5E6E6536:0")
return true count, err = client.CheckPassword("paddednotpwnedzero", true)
} assert.NoError(t, err)
} assert.Equal(t, 0, count)
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)
} }

View file

@ -29,7 +29,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
var revs map[string]*Commit var revs map[string]*Commit
if commit.repo.LastCommitCache != nil { if commit.repo.LastCommitCache != nil {
var unHitPaths []string 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -97,7 +97,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
return commitsInfo, treeCommit, nil 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 var unHitEntryPaths []string
results := make(map[string]*Commit) results := make(map[string]*Commit)
for _, p := range paths { for _, p := range paths {

View file

@ -18,7 +18,7 @@ import (
) )
// ParseTreeEntries parses the output of a `git ls-tree -l` command. // 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) return parseTreeEntries(data, nil)
} }

View file

@ -67,7 +67,7 @@ func TestParseTreeEntries(t *testing.T) {
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries(Sha1ObjectFormat, []byte(testCase.Input)) entries, err := ParseTreeEntries([]byte(testCase.Input))
assert.NoError(t, err) assert.NoError(t, err)
if len(entries) > 1 { if len(entries) > 1 {
fmt.Println(testCase.Expected[0].ID) fmt.Println(testCase.Expected[0].ID)

View file

@ -17,13 +17,13 @@ import (
) )
// ParseTreeEntries parses the output of a `git ls-tree -l` command. // ParseTreeEntries parses the output of a `git ls-tree -l` command.
func ParseTreeEntries(objectFormat ObjectFormat, data []byte) ([]*TreeEntry, error) { func ParseTreeEntries(data []byte) ([]*TreeEntry, error) {
return parseTreeEntries(objectFormat, data, nil) return parseTreeEntries(data, nil)
} }
var sepSpace = []byte{' '} var sepSpace = []byte{' '}
func parseTreeEntries(objectFormat ObjectFormat, data []byte, ptree *Tree) ([]*TreeEntry, error) { func parseTreeEntries(data []byte, ptree *Tree) ([]*TreeEntry, error) {
var err error var err error
entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1) entries := make([]*TreeEntry, 0, bytes.Count(data, []byte{'\n'})+1)
for pos := 0; pos < len(data); { for pos := 0; pos < len(data); {

View file

@ -12,8 +12,6 @@ import (
) )
func TestParseTreeEntriesLong(t *testing.T) { func TestParseTreeEntriesLong(t *testing.T) {
objectFormat := Sha1ObjectFormat
testCases := []struct { testCases := []struct {
Input string Input string
Expected []*TreeEntry Expected []*TreeEntry
@ -56,7 +54,7 @@ func TestParseTreeEntriesLong(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input)) entries, err := ParseTreeEntries([]byte(testCase.Input))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected)) assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries { for i, entry := range entries {
@ -66,8 +64,6 @@ func TestParseTreeEntriesLong(t *testing.T) {
} }
func TestParseTreeEntriesShort(t *testing.T) { func TestParseTreeEntriesShort(t *testing.T) {
objectFormat := Sha1ObjectFormat
testCases := []struct { testCases := []struct {
Input string Input string
Expected []*TreeEntry Expected []*TreeEntry
@ -91,7 +87,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
entries, err := ParseTreeEntries(objectFormat, []byte(testCase.Input)) entries, err := ParseTreeEntries([]byte(testCase.Input))
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, entries, len(testCase.Expected)) assert.Len(t, entries, len(testCase.Expected))
for i, entry := range entries { for i, entry := range entries {
@ -102,7 +98,7 @@ func TestParseTreeEntriesShort(t *testing.T) {
func TestParseTreeEntriesInvalid(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 // 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.Error(t, err)
assert.Len(t, entries, 0) assert.Len(t, entries, 0)
} }

View file

@ -77,11 +77,8 @@ func (t *Tree) ListEntries() (Entries, error) {
return nil, runErr return nil, runErr
} }
objectFormat, err := t.repo.GetObjectFormat() var err error
if err != nil { t.entries, err = parseTreeEntries(stdout, t)
return nil, err
}
t.entries, err = parseTreeEntries(objectFormat, stdout, t)
if err == nil { if err == nil {
t.entriesParsed = true t.entriesParsed = true
} }
@ -104,11 +101,8 @@ func (t *Tree) listEntriesRecursive(extraArgs TrustedCmdArgs) (Entries, error) {
return nil, runErr return nil, runErr
} }
objectFormat, err := t.repo.GetObjectFormat() var err error
if err != nil { t.entriesRecursive, err = parseTreeEntries(stdout, t)
return nil, err
}
t.entriesRecursive, err = parseTreeEntries(objectFormat, stdout, t)
if err == nil { if err == nil {
t.entriesRecursiveParsed = true t.entriesRecursiveParsed = true
} }

View file

@ -17,11 +17,14 @@ import (
"time" "time"
charsetModule "code.gitea.io/gitea/modules/charset" charsetModule "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/klauspost/compress/gzhttp"
) )
type ServeHeaderOptions struct { type ServeHeaderOptions struct {
@ -38,6 +41,11 @@ type ServeHeaderOptions struct {
func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) { func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
header := w.Header() 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 contentType := typesniffer.ApplicationOctetStream
if opts.ContentType != "" { if opts.ContentType != "" {
if opts.ContentTypeCharset != "" { if opts.ContentTypeCharset != "" {

View file

@ -62,8 +62,8 @@ func isIndexable(entry *git.TreeEntry) bool {
} }
// parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command // parseGitLsTreeOutput parses the output of a `git ls-tree -r --full-name` command
func parseGitLsTreeOutput(objectFormat git.ObjectFormat, stdout []byte) ([]internal.FileUpdate, error) { func parseGitLsTreeOutput(stdout []byte) ([]internal.FileUpdate, error) {
entries, err := git.ParseTreeEntries(objectFormat, stdout) entries, err := git.ParseTreeEntries(stdout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -91,10 +91,8 @@ func genesisChanges(ctx context.Context, repo *repo_model.Repository, revision s
return nil, runErr return nil, runErr
} }
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
var err error var err error
changes.Updates, err = parseGitLsTreeOutput(objectFormat, stdout) changes.Updates, err = parseGitLsTreeOutput(stdout)
return &changes, err return &changes, err
} }
@ -172,8 +170,6 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio
return nil, err return nil, err
} }
objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) changes.Updates, err = parseGitLsTreeOutput(lsTreeStdout)
changes.Updates, err = parseGitLsTreeOutput(objectFormat, lsTreeStdout)
return &changes, err return &changes, err
} }

View file

@ -58,11 +58,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
case *ast.Paragraph: case *ast.Paragraph:
g.applyElementDir(v) g.applyElementDir(v)
case *ast.Image: case *ast.Image:
g.transformImage(ctx, v, reader) g.transformImage(ctx, v)
case *ast.Link: case *ast.Link:
g.transformLink(ctx, v, reader) g.transformLink(ctx, v)
case *ast.List: case *ast.List:
g.transformList(ctx, v, reader, rc) g.transformList(ctx, v, rc)
case *ast.Text: case *ast.Text:
if v.SoftLineBreak() && !v.HardLineBreak() { if v.SoftLineBreak() && !v.HardLineBreak() {
if ctx.Metas["mode"] != "document" { if ctx.Metas["mode"] != "document" {

View file

@ -13,7 +13,7 @@ import (
"github.com/yuin/goldmark/util" "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() { for _, attr := range v.Attributes() {
if _, ok := attr.Value.([]byte); !ok { if _, ok := attr.Value.([]byte); !ok {
v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value)))

View file

@ -10,10 +10,9 @@ import (
giteautil "code.gitea.io/gitea/modules/util" giteautil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast" "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: // Images need two things:
// //
// 1. Their src needs to munged to be a real value // 1. Their src needs to munged to be a real value

View file

@ -12,10 +12,9 @@ import (
giteautil "code.gitea.io/gitea/modules/util" giteautil "code.gitea.io/gitea/modules/util"
"github.com/yuin/goldmark/ast" "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 // Links need their href to munged to be a real value
link := v.Destination link := v.Destination

View file

@ -11,7 +11,6 @@ import (
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
east "github.com/yuin/goldmark/extension/ast" east "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
) )
@ -50,7 +49,7 @@ func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node
return ast.WalkContinue, nil 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() { if v.HasChildren() {
children := make([]ast.Node, 0, v.ChildCount()) children := make([]ast.Node, 0, v.ChildCount())
child := v.FirstChild() child := v.FirstChild()

View file

@ -54,7 +54,7 @@ func (r *stripRenderer) Render(w io.Writer, source []byte, doc ast.Node) error {
} }
return ast.WalkContinue, nil return ast.WalkContinue, nil
case *ast.Link: case *ast.Link:
r.processLink(w, v.Destination) r.processLink(v.Destination)
return ast.WalkSkipChildren, nil return ast.WalkSkipChildren, nil
case *ast.AutoLink: case *ast.AutoLink:
// This could be a reference to an issue or pull - if so convert it // 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])) _, _ = 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 // Links are processed out of band
r.links = append(r.links, string(link)) r.links = append(r.links, string(link))
} }

View file

@ -22,7 +22,7 @@ func TestOption(t *testing.T) {
assert.Equal(t, int(0), none.Value()) assert.Equal(t, int(0), none.Value())
assert.Equal(t, int(1), none.ValueOrDefault(1)) assert.Equal(t, int(1), none.ValueOrDefault(1))
some := optional.Some[int](1) some := optional.Some(1)
assert.True(t, some.Has()) assert.True(t, some.Has())
assert.Equal(t, int(1), some.Value()) assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2)) assert.Equal(t, int(1), some.ValueOrDefault(2))

View file

@ -78,6 +78,7 @@ type PackageMetadataVersion struct {
Repository Repository `json:"repository,omitempty"` Repository Repository `json:"repository,omitempty"`
Keywords []string `json:"keywords,omitempty"` Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"` Dependencies map[string]string `json:"dependencies,omitempty"`
BundleDependencies []string `json:"bundleDependencies,omitempty"`
DevDependencies map[string]string `json:"devDependencies,omitempty"` DevDependencies map[string]string `json:"devDependencies,omitempty"`
PeerDependencies map[string]string `json:"peerDependencies,omitempty"` PeerDependencies map[string]string `json:"peerDependencies,omitempty"`
Bin map[string]string `json:"bin,omitempty"` Bin map[string]string `json:"bin,omitempty"`
@ -218,6 +219,7 @@ func ParsePackage(r io.Reader) (*Package, error) {
ProjectURL: meta.Homepage, ProjectURL: meta.Homepage,
Keywords: meta.Keywords, Keywords: meta.Keywords,
Dependencies: meta.Dependencies, Dependencies: meta.Dependencies,
BundleDependencies: meta.BundleDependencies,
DevelopmentDependencies: meta.DevDependencies, DevelopmentDependencies: meta.DevDependencies,
PeerDependencies: meta.PeerDependencies, PeerDependencies: meta.PeerDependencies,
OptionalDependencies: meta.OptionalDependencies, OptionalDependencies: meta.OptionalDependencies,

View file

@ -16,6 +16,7 @@ type Metadata struct {
ProjectURL string `json:"project_url,omitempty"` ProjectURL string `json:"project_url,omitempty"`
Keywords []string `json:"keywords,omitempty"` Keywords []string `json:"keywords,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"` Dependencies map[string]string `json:"dependencies,omitempty"`
BundleDependencies []string `json:"bundleDependencies,omitempty"`
DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"` DevelopmentDependencies map[string]string `json:"development_dependencies,omitempty"`
PeerDependencies map[string]string `json:"peer_dependencies,omitempty"` PeerDependencies map[string]string `json:"peer_dependencies,omitempty"`
OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"` OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"`

View file

@ -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) 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) parsed, err := mail.ParseAddress(IncomingEmail.ReplyToAddress)
if err != nil { if err != nil {
return err return err

View file

@ -99,7 +99,7 @@ func getStorage(rootCfg ConfigProvider, name, typ string, sec ConfigSection) (*S
return nil, err return nil, err
} }
overrideSec := getStorageOverrideSection(rootCfg, targetSec, sec, tp, name) overrideSec := getStorageOverrideSection(rootCfg, sec, tp, name)
targetType := targetSec.Key("STORAGE_TYPE").String() targetType := targetSec.Key("STORAGE_TYPE").String()
switch targetType { 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 // 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 { if targetSecType == targetSecIsSec {
return nil return nil
} }

View file

@ -85,7 +85,7 @@ type CreatePullRequestOption struct {
// EditPullRequestOption options when modify pull request // EditPullRequestOption options when modify pull request
type EditPullRequestOption struct { type EditPullRequestOption struct {
Title string `json:"title"` Title string `json:"title"`
Body string `json:"body"` Body *string `json:"body"`
Base string `json:"base"` Base string `json:"base"`
Assignee string `json:"assignee"` Assignee string `json:"assignee"`
Assignees []string `json:"assignees"` Assignees []string `json:"assignees"`

View 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
View 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.

View file

@ -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.install2 = or add it to the package.json file:
npm.dependencies = Dependencies npm.dependencies = Dependencies
npm.dependencies.development = Development Dependencies npm.dependencies.development = Development Dependencies
npm.dependencies.bundle = Bundled Dependencies
npm.dependencies.peer = Peer Dependencies npm.dependencies.peer = Peer Dependencies
npm.dependencies.optional = Optional Dependencies npm.dependencies.optional = Optional Dependencies
npm.details.tag = Tag npm.details.tag = Tag

View file

@ -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-Length", strconv.Itoa(len(xmlMetadataWithHeader)))
ctx.Resp.Header().Set("Content-Type", contentTypeXML) ctx.Resp.Header().Set("Content-Type", contentTypeXML)
if _, err := ctx.Resp.Write(xmlMetadataWithHeader); err != nil { _, _ = ctx.Resp.Write(xmlMetadataWithHeader)
log.Error("write bytes failed: %v", err)
}
} }
func servePackageFile(ctx *context.Context, params parameters, serveContent bool) { func servePackageFile(ctx *context.Context, params parameters, serveContent bool) {

View file

@ -64,6 +64,7 @@ func createPackageMetadataVersion(registryURL string, pd *packages_model.Package
Homepage: metadata.ProjectURL, Homepage: metadata.ProjectURL,
License: metadata.License, License: metadata.License,
Dependencies: metadata.Dependencies, Dependencies: metadata.Dependencies,
BundleDependencies: metadata.BundleDependencies,
DevDependencies: metadata.DevelopmentDependencies, DevDependencies: metadata.DevelopmentDependencies,
PeerDependencies: metadata.PeerDependencies, PeerDependencies: metadata.PeerDependencies,
OptionalDependencies: metadata.OptionalDependencies, OptionalDependencies: metadata.OptionalDependencies,

View file

@ -1112,6 +1112,9 @@ func Routes() *web.Route {
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag) m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag) m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true)) }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks)
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
m.Group("/keys", func() { m.Group("/keys", func() {
m.Combo("").Get(repo.ListDeployKeys). m.Combo("").Get(repo.ListDeployKeys).
Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)

View file

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
secret_service "code.gitea.io/gitea/services/secrets" secret_service "code.gitea.io/gitea/services/secrets"
) )
@ -517,3 +518,68 @@ type Action struct{}
func NewAction() actions_service.API { func NewAction() actions_service.API {
return Action{} 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)
}

View file

@ -29,7 +29,6 @@ import (
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue" 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 // SearchIssues searches for issues across the repositories that the user has access to
@ -809,12 +808,19 @@ func EditIssue(ctx *context.APIContext) {
return return
} }
oldTitle := issue.Title
if len(form.Title) > 0 { 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 { 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 { if form.Ref != nil {
err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref) err = issue_service.ChangeIssueRef(ctx, issue, ctx.Doer, *form.Ref)
@ -882,24 +888,14 @@ func EditIssue(ctx *context.APIContext) {
return return
} }
} }
issue.IsClosed = api.StateClosed == api.StateType(*form.State) if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
} if issues_model.IsErrDependenciesLeft(err) {
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer) ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies")
if err != nil { return
if issues_model.IsErrDependenciesLeft(err) { }
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this issue because it still has open dependencies") ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
return return
} }
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", 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 // Refetch from database to assign some automatic values

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment" "code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
@ -159,6 +160,8 @@ func CreateIssueAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
// "423": // "423":
// "$ref": "#/responses/repoArchivedError" // "$ref": "#/responses/repoArchivedError"
@ -207,7 +210,11 @@ func CreateIssueAttachment(ctx *context.APIContext) {
CreatedUnix: issue.UpdatedUnix, CreatedUnix: issue.UpdatedUnix,
}) })
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err) if upload.IsErrFileTypeForbidden(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
}
return return
} }

View file

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/attachment" "code.gitea.io/gitea/services/attachment"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
issue_service "code.gitea.io/gitea/services/issue" issue_service "code.gitea.io/gitea/services/issue"
) )
@ -156,6 +157,8 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "404": // "404":
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
// "422":
// "$ref": "#/responses/validationError"
// "423": // "423":
// "$ref": "#/responses/repoArchivedError" // "$ref": "#/responses/repoArchivedError"
@ -209,9 +212,14 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
CreatedUnix: comment.Issue.UpdatedUnix, CreatedUnix: comment.Issue.UpdatedUnix,
}) })
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err) if upload.IsErrFileTypeForbidden(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
}
return return
} }
if err := comment.LoadAttachments(ctx); err != nil { if err := comment.LoadAttachments(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadAttachments", err) ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
return return

View file

@ -180,7 +180,7 @@ func Migrate(ctx *context.APIContext) {
Status: repo_model.RepositoryBeingMigrated, Status: repo_model.RepositoryBeingMigrated,
}) })
if err != nil { if err != nil {
handleMigrateError(ctx, repoOwner, remoteAddr, err) handleMigrateError(ctx, repoOwner, err)
return 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 { 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 return
} }
@ -215,7 +215,7 @@ func Migrate(ctx *context.APIContext) {
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeAdmin})) 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 { switch {
case repo_model.IsErrRepoAlreadyExist(err): case repo_model.IsErrRepoAlreadyExist(err):
ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.")

View file

@ -601,12 +601,19 @@ func EditPullRequest(ctx *context.APIContext) {
return return
} }
oldTitle := issue.Title
if len(form.Title) > 0 { 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 len(form.Body) > 0 { 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
}
} }
// Update or remove deadline if set // 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") ctx.Error(http.StatusPreconditionFailed, "MergedPRState", "cannot change state of this pull request, it was already merged")
return return
} }
issue.IsClosed = api.StateClosed == api.StateType(*form.State) if err := issue_service.ChangeStatus(ctx, issue, ctx.Doer, "", api.StateClosed == api.StateType(*form.State)); err != nil {
} if issues_model.IsErrDependenciesLeft(err) {
statusChangeComment, titleChanged, err := issues_model.UpdateIssueByAPI(ctx, issue, ctx.Doer) ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies")
if err != nil { return
if issues_model.IsErrDependenciesLeft(err) { }
ctx.Error(http.StatusPreconditionFailed, "DependenciesLeft", "cannot close this pull request because it still has open dependencies") ctx.Error(http.StatusInternalServerError, "ChangeStatus", err)
return return
} }
ctx.Error(http.StatusInternalServerError, "UpdateIssueByAPI", 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 // change pull target branch

View file

@ -422,6 +422,13 @@ type swaggerBlockedUserList struct {
Body []api.BlockedUser `json:"body"` Body []api.BlockedUser `json:"body"`
} }
// TasksList
// swagger:response TasksList
type swaggerRepoTasksList struct {
// in:body
Body api.ActionTaskResponse `json:"body"`
}
// swagger:response Compare // swagger:response Compare
type swaggerCompare struct { type swaggerCompare struct {
// in:body // in:body

View file

@ -6,10 +6,8 @@ package user
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "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) ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return 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)) apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
} }
} }

View file

@ -114,16 +114,14 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
} }
} }
if len(branchesToSync) > 0 { if len(branchesToSync) > 0 {
if gitRepo == nil { var err error
var err error gitRepo, err = gitrepo.OpenRepository(ctx, repo)
gitRepo, err = gitrepo.OpenRepository(ctx, repo) if err != nil {
if err != nil { log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err) ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err), })
}) return
return
}
} }
var ( var (

View file

@ -158,7 +158,7 @@ func DashboardPost(ctx *context.Context) {
switch form.Op { switch form.Op {
case "sync_repo_branches": case "sync_repo_branches":
go func() { 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) log.Error("AddAllRepoBranchesToSyncQueue: %v: %v", ctx.Doer.ID, err)
} }
}() }()

View file

@ -471,8 +471,9 @@ func AuthorizeOAuth(ctx *context.Context) {
return return
} }
// Redirect if user already granted access // Redirect if user already granted access and the application is confidential.
if grant != nil { // 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) code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod)
if err != nil { if err != nil {
handleServerError(ctx, form.State, form.RedirectURI) handleServerError(ctx, form.State, form.RedirectURI)

View file

@ -287,7 +287,7 @@ func GetFeedType(name string, req *http.Request) (bool, string, string) {
} }
// feedActionsToFeedItems convert gitea's Repo's Releases to feeds Item // 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 { for _, rel := range releases {
err := rel.LoadAttributes(ctx) err := rel.LoadAttributes(ctx)
if err != nil { if err != nil {

View file

@ -42,7 +42,7 @@ func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleas
Created: time.Now(), Created: time.Now(),
} }
feed.Items, err = releasesToFeedItems(ctx, releases, isReleasesOnly) feed.Items, err = releasesToFeedItems(ctx, releases)
if err != nil { if err != nil {
ctx.ServerError("releasesToFeedItems", err) ctx.ServerError("releasesToFeedItems", err)
return return

View file

@ -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 // 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) provider.SetName(providerName)
} }

View file

@ -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("Content-Type", "text/plain;charset=utf-8")
b.Resp.Header().Set("X-Content-Type-Options", "nosniff") b.Resp.Header().Set("X-Content-Type-Options", "nosniff")
b.Resp.WriteHeader(status) b.Resp.WriteHeader(status)
if _, err := b.Resp.Write(bs); err != nil { _, _ = b.Resp.Write(bs)
log.ErrorWithSkip(skip, "plainTextInternal (status=%d): write bytes failed: %v", status, err)
}
} }
// PlainTextBytes renders bytes as plain text // PlainTextBytes renders bytes as plain text

View file

@ -13,6 +13,7 @@ import (
"path" "path"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
user_model "code.gitea.io/gitea/models/user" 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) 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 return
} }

View file

@ -810,7 +810,7 @@ func (rt RepoRefType) RefTypeIncludesTags() bool {
return false 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 := "" refName := ""
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
for i, part := range parts { for i, part := range parts {
@ -846,7 +846,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
repo.TreePath = path repo.TreePath = path
return repo.Repository.DefaultBranch return repo.Repository.DefaultBranch
case RepoRefBranch: case RepoRefBranch:
ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
if len(ref) == 0 { if len(ref) == 0 {
// check if ref is HEAD // check if ref is HEAD
parts := strings.Split(path, "/") parts := strings.Split(path, "/")
@ -856,7 +856,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
} }
// maybe it's a renamed branch // 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) b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s)
if err != nil { if err != nil {
log.Error("FindRenamedBranch: %v", err) log.Error("FindRenamedBranch: %v", err)
@ -876,7 +876,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return ref return ref
case RepoRefTag: case RepoRefTag:
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist) return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit: case RepoRefCommit:
parts := strings.Split(path, "/") parts := strings.Split(path, "/")

View file

@ -11,6 +11,7 @@ import (
"strings" "strings"
"time" "time"
actions_model "code.gitea.io/gitea/models/actions"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
@ -24,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff" "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 // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification { func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
verif := asymkey_model.ParseCommitWithSignature(ctx, c) verif := asymkey_model.ParseCommitWithSignature(ctx, c)

View file

@ -211,13 +211,11 @@ func ToLabel(label *issues_model.Label, repo *repo_model.Repository, org *user_m
IsArchived: label.IsArchived(), IsArchived: label.IsArchived(),
} }
labelBelongsToRepo := label.BelongsToRepo()
// calculate URL // calculate URL
if label.BelongsToRepo() && repo != nil { if labelBelongsToRepo && repo != nil {
if repo != nil { result.URL = fmt.Sprintf("%s/labels/%d", repo.APIURL(), label.ID)
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 } else { // BelongsToOrg
if org != nil { if org != nil {
result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID) 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 return result
} }

View file

@ -27,7 +27,7 @@ type commonStorageCheckOptions struct {
name string 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 totalCount, orphanedCount := 0, 0
totalSize, orphanedSize := int64(0), int64(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 opts.Attachments || opts.All {
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.Attachments, storer: storage.Attachments,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { 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)") logger.Info("LFS isn't enabled (skipped)")
return nil return nil
} }
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.LFS, storer: storage.LFS,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { 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 opts.Avatars || opts.All {
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.Avatars, storer: storage.Avatars,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { 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 opts.RepoAvatars || opts.All {
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.RepoAvatars, storer: storage.RepoAvatars,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { 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 opts.RepoArchives || opts.All {
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.RepoArchives, storer: storage.RepoArchives,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { 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)") logger.Info("Packages isn't enabled (skipped)")
return nil return nil
} }
if err := commonCheckStorage(ctx, logger, autofix, if err := commonCheckStorage(logger, autofix,
&commonStorageCheckOptions{ &commonStorageCheckOptions{
storer: storage.Packages, storer: storage.Packages,
isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) {

View file

@ -982,25 +982,24 @@ func (g *GiteaLocalUploader) Finish() error {
} }
func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error { func (g *GiteaLocalUploader) remapUser(source user_model.ExternalUserMigrated, target user_model.ExternalUserRemappable) error {
var userid int64 var userID int64
var err error var err error
if g.sameApp { if g.sameApp {
userid, err = g.remapLocalUser(source, target) userID, err = g.remapLocalUser(source)
} else { } else {
userid, err = g.remapExternalUser(source, target) userID, err = g.remapExternalUser(source)
} }
if err != nil { if err != nil {
return err return err
} }
if userid > 0 { if userID > 0 {
return target.RemapExternalUser("", 0, userid) return target.RemapExternalUser("", 0, userID)
} }
return target.RemapExternalUser(source.GetExternalName(), source.GetExternalID(), g.doer.ID) 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()] userid, ok := g.userMap[source.GetExternalID()]
if !ok { if !ok {
name, err := user_model.GetUserNameByID(g.ctx, source.GetExternalID()) name, err := user_model.GetUserNameByID(g.ctx, source.GetExternalID())
@ -1018,7 +1017,7 @@ func (g *GiteaLocalUploader) remapLocalUser(source user_model.ExternalUserMigrat
return userid, nil 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()] userid, ok := g.userMap[source.GetExternalID()]
if !ok { if !ok {
userid, err = user_model.GetUserIDByExternalUserID(g.ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID())) userid, err = user_model.GetUserIDByExternalUserID(g.ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID()))

View file

@ -90,7 +90,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
pullMirrorsRequested := 0 pullMirrorsRequested := 0
if pullLimit != 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 { if err := handler(bean); err != nil {
return err return err
} }

View file

@ -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. // 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. // 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 // FIXME differentiate between previous and proposed line
commit, err := repo.LineBlame(branch, repo.Path, c.TreePath, uint(c.UnsignedLine())) 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())) { 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) return fmt.Errorf("find code comments: %v", err)
} }
for _, comment := range codeComments { 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 return err
} }
} }

View file

@ -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) 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 { if err := pr.LoadBaseRepo(ctx); err != nil {

View file

@ -18,7 +18,7 @@ import (
) )
// updateHeadByRebaseOnToBase handles updating a PR's head branch by rebasing it on the PR current base branch // 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 // "Clone" base repo and add the cache headers for the head repo and branch
mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, "") mergeCtx, cancel, err := createTemporaryRepoForMerge(ctx, pr, doer, "")
if err != nil { if err != nil {

View file

@ -80,7 +80,7 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR
return fmt.Errorf("getRepositoryByID: %w", err) 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) 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 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) isExist, err := util.IsExist(repoPath)
if err != nil { if err != nil {
log.Error("Unable to check if %s exists. Error: %v", repoPath, err) log.Error("Unable to check if %s exists. Error: %v", repoPath, err)

View file

@ -491,7 +491,7 @@ func handlerBranchSync(items ...*BranchSyncOptions) []*BranchSyncOptions {
return nil return nil
} }
func addRepoToBranchSyncQueue(repoID, doerID int64) error { func addRepoToBranchSyncQueue(repoID int64) error {
return branchSyncQueue.Push(&BranchSyncOptions{ return branchSyncQueue.Push(&BranchSyncOptions{
RepoID: repoID, RepoID: repoID,
}) })
@ -507,9 +507,9 @@ func initBranchSyncQueue(ctx context.Context) error {
return nil 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 { 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 { }); err != nil {
return fmt.Errorf("run sync all branches failed: %v", err) return fmt.Errorf("run sync all branches failed: %v", err)
} }

View file

@ -211,7 +211,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
} }
for _, file := range opts.Files { 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 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 // 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" { if file.Operation == "update" || file.Operation == "delete" {
fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath) fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath)
if err != nil { if err != nil {

View file

@ -35,7 +35,7 @@ func TestUpdateUser(t *testing.T) {
Description: optional.Some("description"), Description: optional.Some("description"),
AllowGitHook: optional.Some(true), AllowGitHook: optional.Some(true),
AllowImportLocal: optional.Some(true), AllowImportLocal: optional.Some(true),
MaxRepoCreation: optional.Some[int](10), MaxRepoCreation: optional.Some(10),
IsRestricted: optional.Some(true), IsRestricted: optional.Some(true),
IsActive: optional.Some(false), IsActive: optional.Some(false),
IsAdmin: optional.Some(true), IsAdmin: optional.Some(true),

View file

@ -45,6 +45,15 @@
</div> </div>
{{end}} {{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}} {{if .PackageDescriptor.Metadata.Keywords}}
<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4> <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.keywords"}}</h4>
<div class="ui attached segment"> <div class="ui attached segment">

View file

@ -62,13 +62,13 @@
</div> </div>
{{if or .Labels .Assignees}} {{if or .Labels .Assignees}}
<div class="tw-flex tw-justify-between"> <div class="issue-card-bottom">
<div class="labels-list tw-flex-1"> <div class="labels-list">
{{range .Labels}} {{range .Labels}}
<a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a> <a target="_blank" href="{{$.Issue.Repo.Link}}/issues?labels={{.ID}}">{{RenderLabel ctx ctx.Locale .}}</a>
{{end}} {{end}}
</div> </div>
<div class="tw-flex tw-flex-wrap tw-content-start tw-gap-1"> <div class="issue-card-assignees">
{{range .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> <a target="_blank" href="{{.HomeLink}}" data-tooltip-content="{{ctx.Locale.Tr "repo.projects.column.assigned_to"}} {{.Name}}">{{ctx.AvatarUtils.Avatar . 28}}</a>
{{end}} {{end}}

View file

@ -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": { "/repos/{owner}/{repo}/actions/variables": {
"get": { "get": {
"produces": [ "produces": [
@ -7605,6 +7665,9 @@
"404": { "404": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"422": {
"$ref": "#/responses/validationError"
},
"423": { "423": {
"$ref": "#/responses/repoArchivedError" "$ref": "#/responses/repoArchivedError"
} }
@ -8231,6 +8294,9 @@
"404": { "404": {
"$ref": "#/responses/error" "$ref": "#/responses/error"
}, },
"422": {
"$ref": "#/responses/validationError"
},
"423": { "423": {
"$ref": "#/responses/repoArchivedError" "$ref": "#/responses/repoArchivedError"
} }
@ -18312,6 +18378,89 @@
}, },
"x-go-package": "code.gitea.io/gitea/modules/structs" "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": { "ActionVariable": {
"description": "ActionVariable return value of the query API", "description": "ActionVariable return value of the query API",
"type": "object", "type": "object",
@ -25895,6 +26044,12 @@
} }
} }
}, },
"TasksList": {
"description": "TasksList",
"schema": {
"$ref": "#/definitions/ActionTaskResponse"
}
},
"Team": { "Team": {
"description": "Team", "description": "Team",
"schema": { "schema": {

View file

@ -240,3 +240,31 @@ func TestAPIDeleteCommentAttachment(t *testing.T) {
unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: attachment.ID, CommentID: comment.ID}) 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)
}

View file

@ -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) { func TestAPIEditIssueAttachment(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()

View file

@ -188,6 +188,10 @@ func TestAPIEditIssue(t *testing.T) {
issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10})
repoAfter := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}) 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 // check deleted user
assert.Equal(t, int64(500), issueAfter.PosterID) assert.Equal(t, int64(500), issueAfter.PosterID)
assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext)) assert.NoError(t, issueAfter.LoadAttributes(db.DefaultContext))

View file

@ -169,7 +169,7 @@ nwIDAQAB
assert.Nil(t, u) assert.Nil(t, u)
assert.Error(t, err) assert.Error(t, err)
signRequest := func(t *testing.T, rw *RequestWrapper, version string) { signRequest := func(rw *RequestWrapper, version string) {
req := rw.Request req := rw.Request
username := req.Header.Get("X-Ops-Userid") username := req.Header.Get("X-Ops-Userid")
if version != "1.0" && version != "1.3" { if version != "1.0" && version != "1.3" {
@ -255,7 +255,7 @@ nwIDAQAB
t.Run(v, func(t *testing.T) { t.Run(v, func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
signRequest(t, req, v) signRequest(req, v)
u, err = auth.Verify(req.Request, nil, nil, nil) u, err = auth.Verify(req.Request, nil, nil, nil)
assert.NotNil(t, u) assert.NotNil(t, u)
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -223,23 +223,33 @@ func TestAPIEditPull(t *testing.T) {
session := loginUser(t, owner10.Name) session := loginUser(t, owner10.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 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{ req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{
Head: "develop", Head: "develop",
Base: "master", Base: "master",
Title: "create a success pr", Title: title,
}).AddTokenAuth(token) }).AddTokenAuth(token)
pull := new(api.PullRequest) apiPull := new(api.PullRequest)
resp := MakeRequest(t, req, http.StatusCreated) resp := MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, pull) DecodeJSON(t, resp, apiPull)
assert.EqualValues(t, "master", pull.Base.Name) 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", Base: "feature/1",
Title: "edit a this pr", Title: newTitle,
Body: &newBody,
}).AddTokenAuth(token) }).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusCreated) resp = MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, pull) DecodeJSON(t, resp, apiPull)
assert.EqualValues(t, "feature/1", pull.Base.Name) 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{ 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", Base: "not-exist",

View file

@ -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") 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) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases", owner.Name, repo.Name)
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{ req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateReleaseOption{
TagName: name, TagName: name,
@ -120,7 +120,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) {
target, err := gitRepo.GetTagCommitID("v0.0.1") target, err := gitRepo.GetTagCommitID("v0.0.1")
assert.NoError(t, err) 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) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d", owner.Name, repo.Name, newRelease.ID)
req := NewRequest(t, "GET", urlStr). req := NewRequest(t, "GET", urlStr).
@ -167,7 +167,7 @@ func TestAPICreateReleaseToDefaultBranch(t *testing.T) {
session := loginUser(t, owner.LowerName) session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 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) { func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
@ -185,7 +185,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) {
err = gitRepo.CreateTag("v0.0.1", "master") err = gitRepo.CreateTag("v0.0.1", "master")
assert.NoError(t, err) 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) { func TestAPIGetLatestRelease(t *testing.T) {
@ -237,7 +237,7 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) {
session := loginUser(t, owner.LowerName) session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 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 // delete release
req := NewRequestf(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/release-tag", owner.Name, repo.Name)). 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) session := loginUser(t, owner.LowerName)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 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" filename := "image.png"
buff := generateImg() buff := generateImg()
@ -335,7 +335,7 @@ func TestAPIGetReleaseArchiveDownloadCount(t *testing.T) {
name := "ReleaseDownloadCount" 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) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner.Name, repo.Name, name)

View file

@ -80,7 +80,7 @@ func TestAPIDeleteTagByName(t *testing.T) {
_ = MakeRequest(t, req, http.StatusNoContent) _ = MakeRequest(t, req, http.StatusNoContent)
// Make sure that actual releases can't be deleted outright // 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)). req = NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/tags/release-tag", owner.Name, repo.Name)).
AddTokenAuth(token) AddTokenAuth(token)

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "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) { func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
@ -684,7 +718,9 @@ func TestAPIRepoGetReviewers(t *testing.T) {
resp := MakeRequest(t, req, http.StatusOK) resp := MakeRequest(t, req, http.StatusOK)
var reviewers []*api.User var reviewers []*api.User
DecodeJSON(t, resp, &reviewers) 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) { func TestAPIRepoGetAssignees(t *testing.T) {

View file

@ -83,7 +83,7 @@ func testGit(t *testing.T, u *url.URL) {
rawTest(t, &httpContext, little, big, littleLFS, bigLFS) rawTest(t, &httpContext, little, big, littleLFS, bigLFS)
mediaTest(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("InternalReferences", doInternalReferences(&httpContext, dstPath))
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&httpContext, dstPath))
t.Run("AutoMerge", doAutoPRMerge(&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) rawTest(t, &sshContext, little, big, littleLFS, bigLFS)
mediaTest(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("InternalReferences", doInternalReferences(&sshContext, dstPath))
t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath)) t.Run("BranchProtectMerge", doBranchProtectPRMerge(&sshContext, dstPath))
t.Run("MergeFork", func(t *testing.T) { t.Run("MergeFork", func(t *testing.T) {
@ -333,9 +333,6 @@ func generateCommitWithNewData(size int, repoPath, email, fullName, prefix strin
} }
written += n written += n
} }
if err != nil {
return "", err
}
// Commit // Commit
// Now here we should explicitly allow lfs filters to run // 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) { return func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()

View file

@ -455,8 +455,6 @@ func TestRecentlyPushed(t *testing.T) {
t.Run("unrelated branches are not shown", func(t *testing.T) { t.Run("unrelated branches are not shown", func(t *testing.T) {
defer tests.PrintCurrentTest(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. // Create a new branch with no relation to the default branch.
// 1. Create a new Tree object // 1. Create a new Tree object
cmd := git.NewCommand(db.DefaultContext, "write-tree") cmd := git.NewCommand(db.DefaultContext, "write-tree")
@ -473,7 +471,7 @@ func TestRecentlyPushed(t *testing.T) {
_, _, gitErr = cmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()}) _, _, gitErr = cmd.RunStdString(&git.RunOpts{Dir: repo.RepoPath()})
assert.NoError(t, gitErr) assert.NoError(t, gitErr)
// 4. Sync the git repo to the database // 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) assert.NoError(t, syncErr)
// 5. Add a fresh commit, so that FindRecentlyPushedBranches has // 5. Add a fresh commit, so that FindRecentlyPushedBranches has
// something to find. // something to find.

View 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))
}

View file

@ -215,7 +215,7 @@ func TestBadges(t *testing.T) {
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 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") err := release.CreateNewTag(git.DefaultContext, repo.Owner, repo, "main", "repo-name-2.0", "dash in the tag name")
assert.NoError(t, err) 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) req := NewRequestf(t, "GET", "/user2/%s/badges/release.svg", repo.Name)
resp := MakeRequest(t, req, http.StatusSeeOther) resp := MakeRequest(t, req, http.StatusSeeOther)

View file

@ -16,7 +16,6 @@ import (
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "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/git"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -170,7 +169,6 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
func TestDatabaseMissingABranch(t *testing.T) { func TestDatabaseMissingABranch(t *testing.T) {
onGiteaRun(t, func(t *testing.T, URL *url.URL) { onGiteaRun(t, func(t *testing.T, URL *url.URL) {
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true})
session := loginUser(t, "user2") session := loginUser(t, "user2")
// Create two branches // Create two branches
@ -178,7 +176,7 @@ func TestDatabaseMissingABranch(t *testing.T) {
testCreateBranch(t, session, "user2", "repo1", "branch/master", "will-be-missing", http.StatusSeeOther) testCreateBranch(t, session, "user2", "repo1", "branch/master", "will-be-missing", http.StatusSeeOther)
// Run the repo branch sync, to ensure the db and git agree. // 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) assert.NoError(t, err2)
// Delete one branch from git only, leaving it in the database // 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) assert.GreaterOrEqual(t, firstBranchCount, 3)
// Run the repo branch sync again // 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) assert.NoError(t, err2)
// Verify that loading the repo's branches page works still, and that it // Verify that loading the repo's branches page works still, and that it

View file

@ -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 // Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined
return &api.FileResponse{ return &api.FileResponse{
Content: nil, Content: nil,
@ -418,7 +418,7 @@ func testDeleteRepoFiles(t *testing.T, u *url.URL) {
t.Run("Delete README.md file", func(t *testing.T) { t.Run("Delete README.md file", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.NoError(t, err) assert.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u) expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
assert.NotNil(t, filesResponse) assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0]) assert.Nil(t, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) 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) { t.Run("Delete README.md without Branch Name", func(t *testing.T) {
filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts)
assert.NoError(t, err) assert.NoError(t, err)
expectedFileResponse := getExpectedFileResponseForRepofilesDelete(u) expectedFileResponse := getExpectedFileResponseForRepofilesDelete()
assert.NotNil(t, filesResponse) assert.NotNil(t, filesResponse)
assert.Nil(t, filesResponse.Files[0]) assert.Nil(t, filesResponse.Files[0])
assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message)

View file

@ -31,6 +31,10 @@
padding: 0 5px; 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 */ /* move the "? contributions in the last ? months" text from top to bottom */
#user-heatmap .total-contributions { #user-heatmap .total-contributions {
font-size: 11px; font-size: 11px;

View file

@ -2368,18 +2368,12 @@ td .commit-summary {
display: inline-flex; display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 2.5px; gap: 2.5px;
} align-items: center;
.labels-list a {
display: flex;
text-decoration: none;
} }
.labels-list .label { .labels-list .label {
padding: 0 6px; padding: 0 6px;
margin: 0 !important;
min-height: 20px; 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 */ line-height: 1.3; /* there is a `font-size: 1.25em` for inside emoji, so here the line-height needs to be larger slightly */
} }

View file

@ -23,3 +23,18 @@
.issue-card.sortable-chosen .issue-card-title { .issue-card.sortable-chosen .issue-card-title {
cursor: inherit; 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;
}