mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 23:38:48 +02:00
Merge branch 'main' into lunny/issue_dev
This commit is contained in:
commit
097c649387
@ -378,6 +378,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
||||||
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
||||||
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
||||||
|
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||||
newMigration(318, "Add table issue_dev_link", v1_24.CreateTableIssueDevLink),
|
newMigration(318, "Add table issue_dev_link", v1_24.CreateTableIssueDevLink),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
|
|||||||
@ -4,11 +4,16 @@
|
|||||||
package v1_24 //nolint
|
package v1_24 //nolint
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
<<<<<<< HEAD
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
=======
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
>>>>>>> main
|
||||||
|
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
func CreateTableIssueDevLink(x *xorm.Engine) error {
|
func CreateTableIssueDevLink(x *xorm.Engine) error {
|
||||||
type IssueDevLink struct {
|
type IssueDevLink struct {
|
||||||
ID int64 `xorm:"pk autoincr"`
|
ID int64 `xorm:"pk autoincr"`
|
||||||
@ -19,4 +24,11 @@ func CreateTableIssueDevLink(x *xorm.Engine) error {
|
|||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(IssueDevLink))
|
return x.Sync(new(IssueDevLink))
|
||||||
|
=======
|
||||||
|
func AddRepoUnitAnonymousAccessMode(x *xorm.Engine) error {
|
||||||
|
type RepoUnit struct { //revive:disable-line:exported
|
||||||
|
AnonymousAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
}
|
||||||
|
return x.Sync(&RepoUnit{})
|
||||||
|
>>>>>>> main
|
||||||
}
|
}
|
||||||
|
|||||||
22
models/migrations/v1_24/v319.go
Normal file
22
models/migrations/v1_24/v319.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_24 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateTableIssueDevLink(x *xorm.Engine) error {
|
||||||
|
type IssueDevLink struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
IssueID int64 `xorm:"INDEX"`
|
||||||
|
LinkType int
|
||||||
|
LinkedRepoID int64 `xorm:"INDEX"` // it can link to self repo or other repo
|
||||||
|
LinkIndex string // branch name, pull request number or commit sha
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
}
|
||||||
|
return x.Sync(new(IssueDevLink))
|
||||||
|
}
|
||||||
@ -25,7 +25,8 @@ type Permission struct {
|
|||||||
units []*repo_model.RepoUnit
|
units []*repo_model.RepoUnit
|
||||||
unitsMode map[unit.Type]perm_model.AccessMode
|
unitsMode map[unit.Type]perm_model.AccessMode
|
||||||
|
|
||||||
everyoneAccessMode map[unit.Type]perm_model.AccessMode
|
everyoneAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for every signed-in user
|
||||||
|
anonymousAccessMode map[unit.Type]perm_model.AccessMode // the unit's minimal access mode for anonymous (non-signed-in) user
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOwner returns true if current user is the owner of repository.
|
// IsOwner returns true if current user is the owner of repository.
|
||||||
@ -39,7 +40,7 @@ func (p *Permission) IsAdmin() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
||||||
// It doesn't count the "everyone access mode".
|
// It doesn't count the "public(anonymous/everyone) access mode".
|
||||||
func (p *Permission) HasAnyUnitAccess() bool {
|
func (p *Permission) HasAnyUnitAccess() bool {
|
||||||
for _, v := range p.unitsMode {
|
for _, v := range p.unitsMode {
|
||||||
if v >= perm_model.AccessModeRead {
|
if v >= perm_model.AccessModeRead {
|
||||||
@ -49,7 +50,12 @@ func (p *Permission) HasAnyUnitAccess() bool {
|
|||||||
return p.AccessMode >= perm_model.AccessModeRead
|
return p.AccessMode >= perm_model.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) HasAnyUnitAccessOrEveryoneAccess() bool {
|
func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool {
|
||||||
|
for _, v := range p.anonymousAccessMode {
|
||||||
|
if v >= perm_model.AccessModeRead {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, v := range p.everyoneAccessMode {
|
for _, v := range p.everyoneAccessMode {
|
||||||
if v >= perm_model.AccessModeRead {
|
if v >= perm_model.AccessModeRead {
|
||||||
return true
|
return true
|
||||||
@ -73,14 +79,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnitAccessMode returns current user access mode to the specify unit of the repository
|
// UnitAccessMode returns current user access mode to the specify unit of the repository
|
||||||
// It also considers "everyone access mode"
|
// It also considers "public (anonymous/everyone) access mode"
|
||||||
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
||||||
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
||||||
if m, ok := p.unitsMode[unitType]; ok {
|
if m, ok := p.unitsMode[unitType]; ok {
|
||||||
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
|
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
|
||||||
}
|
}
|
||||||
// if the units map does not contain the access mode, return the default access mode if the unit exists
|
// if the units map does not contain the access mode, return the default access mode if the unit exists
|
||||||
unitDefaultAccessMode := max(p.AccessMode, p.everyoneAccessMode[unitType])
|
unitDefaultAccessMode := p.AccessMode
|
||||||
|
unitDefaultAccessMode = max(unitDefaultAccessMode, p.anonymousAccessMode[unitType])
|
||||||
|
unitDefaultAccessMode = max(unitDefaultAccessMode, p.everyoneAccessMode[unitType])
|
||||||
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
|
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
|
||||||
return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone)
|
return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone)
|
||||||
}
|
}
|
||||||
@ -171,27 +179,38 @@ func (p *Permission) LogString() string {
|
|||||||
format += "\n\tunitsMode[%-v]: %-v"
|
format += "\n\tunitsMode[%-v]: %-v"
|
||||||
args = append(args, key.LogString(), value.LogString())
|
args = append(args, key.LogString(), value.LogString())
|
||||||
}
|
}
|
||||||
|
format += "\n\tanonymousAccessMode: %-v"
|
||||||
|
args = append(args, p.anonymousAccessMode)
|
||||||
format += "\n\teveryoneAccessMode: %-v"
|
format += "\n\teveryoneAccessMode: %-v"
|
||||||
args = append(args, p.everyoneAccessMode)
|
args = append(args, p.everyoneAccessMode)
|
||||||
format += "\n\t]>"
|
format += "\n\t]>"
|
||||||
return fmt.Sprintf(format, args...)
|
return fmt.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) {
|
||||||
|
if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] {
|
||||||
|
if *modeMap == nil {
|
||||||
|
*modeMap = make(map[unit.Type]perm_model.AccessMode)
|
||||||
|
}
|
||||||
|
(*modeMap)[unitType] = accessMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
||||||
|
// apply public (anonymous) access permissions
|
||||||
|
for _, u := range perm.units {
|
||||||
|
applyPublicAccessPermission(u.Type, u.AnonymousAccessMode, &perm.anonymousAccessMode)
|
||||||
|
}
|
||||||
|
|
||||||
if user == nil || user.ID <= 0 {
|
if user == nil || user.ID <= 0 {
|
||||||
// for anonymous access, it could be:
|
// for anonymous access, it could be:
|
||||||
// AccessMode is None or Read, units has repo units, unitModes is nil
|
// AccessMode is None or Read, units has repo units, unitModes is nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply everyone access permissions
|
// apply public (everyone) access permissions
|
||||||
for _, u := range perm.units {
|
for _, u := range perm.units {
|
||||||
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
|
applyPublicAccessPermission(u.Type, u.EveryoneAccessMode, &perm.everyoneAccessMode)
|
||||||
if perm.everyoneAccessMode == nil {
|
|
||||||
perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode)
|
|
||||||
}
|
|
||||||
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if perm.unitsMode == nil {
|
if perm.unitsMode == nil {
|
||||||
@ -209,6 +228,11 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for t := range perm.anonymousAccessMode {
|
||||||
|
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
for t := range perm.everyoneAccessMode {
|
for t := range perm.everyoneAccessMode {
|
||||||
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
||||||
break
|
break
|
||||||
|
|||||||
@ -22,14 +22,21 @@ func TestHasAnyUnitAccess(t *testing.T) {
|
|||||||
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
}
|
}
|
||||||
assert.False(t, perm.HasAnyUnitAccess())
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
assert.False(t, perm.HasAnyUnitAccessOrEveryoneAccess())
|
assert.False(t, perm.HasAnyUnitAccessOrPublicAccess())
|
||||||
|
|
||||||
perm = Permission{
|
perm = Permission{
|
||||||
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead},
|
everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead},
|
||||||
}
|
}
|
||||||
assert.False(t, perm.HasAnyUnitAccess())
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
assert.True(t, perm.HasAnyUnitAccessOrEveryoneAccess())
|
assert.True(t, perm.HasAnyUnitAccessOrPublicAccess())
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
|
anonymousAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead},
|
||||||
|
}
|
||||||
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
|
assert.True(t, perm.HasAnyUnitAccessOrPublicAccess())
|
||||||
|
|
||||||
perm = Permission{
|
perm = Permission{
|
||||||
AccessMode: perm_model.AccessModeRead,
|
AccessMode: perm_model.AccessModeRead,
|
||||||
@ -43,7 +50,7 @@ func TestHasAnyUnitAccess(t *testing.T) {
|
|||||||
assert.True(t, perm.HasAnyUnitAccess())
|
assert.True(t, perm.HasAnyUnitAccess())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyEveryoneRepoPermission(t *testing.T) {
|
func TestApplyPublicAccessRepoPermission(t *testing.T) {
|
||||||
perm := Permission{
|
perm := Permission{
|
||||||
AccessMode: perm_model.AccessModeNone,
|
AccessMode: perm_model.AccessModeNone,
|
||||||
units: []*repo_model.RepoUnit{
|
units: []*repo_model.RepoUnit{
|
||||||
@ -53,6 +60,15 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
|||||||
finalProcessRepoUnitPermission(nil, &perm)
|
finalProcessRepoUnitPermission(nil, &perm)
|
||||||
assert.False(t, perm.CanRead(unit.TypeWiki))
|
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, AnonymousAccessMode: perm_model.AccessModeRead},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
finalProcessRepoUnitPermission(nil, &perm)
|
||||||
|
assert.True(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
perm = Permission{
|
perm = Permission{
|
||||||
AccessMode: perm_model.AccessModeNone,
|
AccessMode: perm_model.AccessModeNone,
|
||||||
units: []*repo_model.RepoUnit{
|
units: []*repo_model.RepoUnit{
|
||||||
|
|||||||
@ -42,12 +42,13 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
|
|||||||
|
|
||||||
// RepoUnit describes all units of a repository
|
// RepoUnit describes all units of a repository
|
||||||
type RepoUnit struct { //revive:disable-line:exported
|
type RepoUnit struct { //revive:disable-line:exported
|
||||||
ID int64
|
ID int64
|
||||||
RepoID int64 `xorm:"INDEX(s)"`
|
RepoID int64 `xorm:"INDEX(s)"`
|
||||||
Type unit.Type `xorm:"INDEX(s)"`
|
Type unit.Type `xorm:"INDEX(s)"`
|
||||||
Config convert.Conversion `xorm:"TEXT"`
|
Config convert.Conversion `xorm:"TEXT"`
|
||||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||||
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
AnonymousAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||||
|
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@ -4,6 +4,9 @@
|
|||||||
package badge
|
package badge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,54 +14,35 @@ import (
|
|||||||
// We use 10x scale to calculate more precisely
|
// We use 10x scale to calculate more precisely
|
||||||
// Then scale down to normal size in tmpl file
|
// Then scale down to normal size in tmpl file
|
||||||
|
|
||||||
type Label struct {
|
type Text struct {
|
||||||
text string
|
|
||||||
width int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Label) Text() string {
|
|
||||||
return l.text
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Label) Width() int {
|
|
||||||
return l.width
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Label) TextLength() int {
|
|
||||||
return int(float64(l.width-defaultOffset) * 9.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l Label) X() int {
|
|
||||||
return l.width*5 + 10
|
|
||||||
}
|
|
||||||
|
|
||||||
type Message struct {
|
|
||||||
text string
|
text string
|
||||||
width int
|
width int
|
||||||
x int
|
x int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) Text() string {
|
func (t Text) Text() string {
|
||||||
return m.text
|
return t.text
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) Width() int {
|
func (t Text) Width() int {
|
||||||
return m.width
|
return t.width
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) X() int {
|
func (t Text) X() int {
|
||||||
return m.x
|
return t.x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Message) TextLength() int {
|
func (t Text) TextLength() int {
|
||||||
return int(float64(m.width-defaultOffset) * 9.5)
|
return int(float64(t.width-defaultOffset) * 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Badge struct {
|
type Badge struct {
|
||||||
Color string
|
IDPrefix string
|
||||||
FontSize int
|
FontFamily string
|
||||||
Label Label
|
Color string
|
||||||
Message Message
|
FontSize int
|
||||||
|
Label Text
|
||||||
|
Message Text
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Badge) Width() int {
|
func (b Badge) Width() int {
|
||||||
@ -66,10 +50,10 @@ func (b Badge) Width() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultOffset = 9
|
defaultOffset = 10
|
||||||
defaultFontSize = 11
|
defaultFontSize = 11
|
||||||
DefaultColor = "#9f9f9f" // Grey
|
DefaultColor = "#9f9f9f" // Grey
|
||||||
defaultFontWidth = 7 // approximate speculation
|
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
|
||||||
)
|
)
|
||||||
|
|
||||||
var StatusColorMap = map[actions_model.Status]string{
|
var StatusColorMap = map[actions_model.Status]string{
|
||||||
@ -85,20 +69,43 @@ var StatusColorMap = map[actions_model.Status]string{
|
|||||||
|
|
||||||
// GenerateBadge generates badge with given template
|
// GenerateBadge generates badge with given template
|
||||||
func GenerateBadge(label, message, color string) Badge {
|
func GenerateBadge(label, message, color string) Badge {
|
||||||
lw := defaultFontWidth*len(label) + defaultOffset
|
lw := calculateTextWidth(label) + defaultOffset
|
||||||
mw := defaultFontWidth*len(message) + defaultOffset
|
mw := calculateTextWidth(message) + defaultOffset
|
||||||
x := lw*10 + mw*5 - 10
|
|
||||||
|
lx := lw * 5
|
||||||
|
mx := lw*10 + mw*5 - 10
|
||||||
return Badge{
|
return Badge{
|
||||||
Label: Label{
|
FontFamily: DefaultFontFamily,
|
||||||
|
Label: Text{
|
||||||
text: label,
|
text: label,
|
||||||
width: lw,
|
width: lw,
|
||||||
|
x: lx,
|
||||||
},
|
},
|
||||||
Message: Message{
|
Message: Text{
|
||||||
text: message,
|
text: message,
|
||||||
width: mw,
|
width: mw,
|
||||||
x: x,
|
x: mx,
|
||||||
},
|
},
|
||||||
FontSize: defaultFontSize * 10,
|
FontSize: defaultFontSize * 10,
|
||||||
Color: color,
|
Color: color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func calculateTextWidth(text string) int {
|
||||||
|
width := 0
|
||||||
|
widthData := DejaVuGlyphWidthData()
|
||||||
|
for _, char := range strings.TrimSpace(text) {
|
||||||
|
charWidth, ok := widthData[char]
|
||||||
|
if !ok {
|
||||||
|
// use the width of 'm' in case of missing glyph width data for a printable character
|
||||||
|
if unicode.IsPrint(char) {
|
||||||
|
charWidth = widthData['m']
|
||||||
|
} else {
|
||||||
|
charWidth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
width += int(charWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|||||||
208
modules/badge/badge_glyph_width.go
Normal file
208
modules/badge/badge_glyph_width.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package badge
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, <rune>, 11, font.HintingNone)` with DejaVu Sans
|
||||||
|
// v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip).
|
||||||
|
//
|
||||||
|
// Fonts defined in "DefaultFontFamily" all have similar widths (including "DejaVu Sans"),
|
||||||
|
// and these widths are fixed and don't seem to change.
|
||||||
|
//
|
||||||
|
// A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images.
|
||||||
|
|
||||||
|
var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 {
|
||||||
|
return map[rune]uint8{
|
||||||
|
32: 3,
|
||||||
|
33: 4,
|
||||||
|
34: 5,
|
||||||
|
35: 9,
|
||||||
|
36: 7,
|
||||||
|
37: 10,
|
||||||
|
38: 9,
|
||||||
|
39: 3,
|
||||||
|
40: 4,
|
||||||
|
41: 4,
|
||||||
|
42: 6,
|
||||||
|
43: 9,
|
||||||
|
44: 3,
|
||||||
|
45: 4,
|
||||||
|
46: 3,
|
||||||
|
47: 4,
|
||||||
|
48: 7,
|
||||||
|
49: 7,
|
||||||
|
50: 7,
|
||||||
|
51: 7,
|
||||||
|
52: 7,
|
||||||
|
53: 7,
|
||||||
|
54: 7,
|
||||||
|
55: 7,
|
||||||
|
56: 7,
|
||||||
|
57: 7,
|
||||||
|
58: 4,
|
||||||
|
59: 4,
|
||||||
|
60: 9,
|
||||||
|
61: 9,
|
||||||
|
62: 9,
|
||||||
|
63: 6,
|
||||||
|
64: 11,
|
||||||
|
65: 8,
|
||||||
|
66: 8,
|
||||||
|
67: 8,
|
||||||
|
68: 8,
|
||||||
|
69: 7,
|
||||||
|
70: 6,
|
||||||
|
71: 9,
|
||||||
|
72: 8,
|
||||||
|
73: 3,
|
||||||
|
74: 3,
|
||||||
|
75: 7,
|
||||||
|
76: 6,
|
||||||
|
77: 9,
|
||||||
|
78: 8,
|
||||||
|
79: 9,
|
||||||
|
80: 7,
|
||||||
|
81: 9,
|
||||||
|
82: 8,
|
||||||
|
83: 7,
|
||||||
|
84: 7,
|
||||||
|
85: 8,
|
||||||
|
86: 8,
|
||||||
|
87: 11,
|
||||||
|
88: 8,
|
||||||
|
89: 7,
|
||||||
|
90: 8,
|
||||||
|
91: 4,
|
||||||
|
92: 4,
|
||||||
|
93: 4,
|
||||||
|
94: 9,
|
||||||
|
95: 6,
|
||||||
|
96: 6,
|
||||||
|
97: 7,
|
||||||
|
98: 7,
|
||||||
|
99: 6,
|
||||||
|
100: 7,
|
||||||
|
101: 7,
|
||||||
|
102: 4,
|
||||||
|
103: 7,
|
||||||
|
104: 7,
|
||||||
|
105: 3,
|
||||||
|
106: 3,
|
||||||
|
107: 6,
|
||||||
|
108: 3,
|
||||||
|
109: 11,
|
||||||
|
110: 7,
|
||||||
|
111: 7,
|
||||||
|
112: 7,
|
||||||
|
113: 7,
|
||||||
|
114: 5,
|
||||||
|
115: 6,
|
||||||
|
116: 4,
|
||||||
|
117: 7,
|
||||||
|
118: 7,
|
||||||
|
119: 9,
|
||||||
|
120: 7,
|
||||||
|
121: 7,
|
||||||
|
122: 6,
|
||||||
|
123: 7,
|
||||||
|
124: 4,
|
||||||
|
125: 7,
|
||||||
|
126: 9,
|
||||||
|
161: 4,
|
||||||
|
162: 7,
|
||||||
|
163: 7,
|
||||||
|
164: 7,
|
||||||
|
165: 7,
|
||||||
|
166: 4,
|
||||||
|
167: 6,
|
||||||
|
168: 6,
|
||||||
|
169: 11,
|
||||||
|
170: 5,
|
||||||
|
171: 7,
|
||||||
|
172: 9,
|
||||||
|
174: 11,
|
||||||
|
175: 6,
|
||||||
|
176: 6,
|
||||||
|
177: 9,
|
||||||
|
178: 4,
|
||||||
|
179: 4,
|
||||||
|
180: 6,
|
||||||
|
181: 7,
|
||||||
|
182: 7,
|
||||||
|
183: 3,
|
||||||
|
184: 6,
|
||||||
|
185: 4,
|
||||||
|
186: 5,
|
||||||
|
187: 7,
|
||||||
|
188: 11,
|
||||||
|
189: 11,
|
||||||
|
190: 11,
|
||||||
|
191: 6,
|
||||||
|
192: 8,
|
||||||
|
193: 8,
|
||||||
|
194: 8,
|
||||||
|
195: 8,
|
||||||
|
196: 8,
|
||||||
|
197: 8,
|
||||||
|
198: 11,
|
||||||
|
199: 8,
|
||||||
|
200: 7,
|
||||||
|
201: 7,
|
||||||
|
202: 7,
|
||||||
|
203: 7,
|
||||||
|
204: 3,
|
||||||
|
205: 3,
|
||||||
|
206: 3,
|
||||||
|
207: 3,
|
||||||
|
208: 9,
|
||||||
|
209: 8,
|
||||||
|
210: 9,
|
||||||
|
211: 9,
|
||||||
|
212: 9,
|
||||||
|
213: 9,
|
||||||
|
214: 9,
|
||||||
|
215: 9,
|
||||||
|
216: 9,
|
||||||
|
217: 8,
|
||||||
|
218: 8,
|
||||||
|
219: 8,
|
||||||
|
220: 8,
|
||||||
|
221: 7,
|
||||||
|
222: 7,
|
||||||
|
223: 7,
|
||||||
|
224: 7,
|
||||||
|
225: 7,
|
||||||
|
226: 7,
|
||||||
|
227: 7,
|
||||||
|
228: 7,
|
||||||
|
229: 7,
|
||||||
|
230: 11,
|
||||||
|
231: 6,
|
||||||
|
232: 7,
|
||||||
|
233: 7,
|
||||||
|
234: 7,
|
||||||
|
235: 7,
|
||||||
|
236: 3,
|
||||||
|
237: 3,
|
||||||
|
238: 3,
|
||||||
|
239: 3,
|
||||||
|
240: 7,
|
||||||
|
241: 7,
|
||||||
|
242: 7,
|
||||||
|
243: 7,
|
||||||
|
244: 7,
|
||||||
|
245: 7,
|
||||||
|
246: 7,
|
||||||
|
247: 9,
|
||||||
|
248: 7,
|
||||||
|
249: 7,
|
||||||
|
250: 7,
|
||||||
|
251: 7,
|
||||||
|
252: 7,
|
||||||
|
253: 7,
|
||||||
|
254: 7,
|
||||||
|
255: 7,
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -22,7 +22,7 @@ func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.R
|
|||||||
if err = runJobs.LoadRepos(ctx); err != nil {
|
if err = runJobs.LoadRepos(ctx); err != nil {
|
||||||
return fmt.Errorf("LoadRepos: %w", err)
|
return fmt.Errorf("LoadRepos: %w", err)
|
||||||
}
|
}
|
||||||
if 0 < jobIndex || jobIndex >= int64(len(runJobs)) {
|
if jobIndex < 0 || jobIndex >= int64(len(runJobs)) {
|
||||||
return util.NewNotExistErrorf("job index is out of range: %d", jobIndex)
|
return util.NewNotExistErrorf("job index is out of range: %d", jobIndex)
|
||||||
}
|
}
|
||||||
return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex])
|
return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex])
|
||||||
|
|||||||
@ -4,16 +4,21 @@
|
|||||||
package devtest
|
package devtest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/asymkey"
|
"code.gitea.io/gitea/models/asymkey"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/badge"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,84 +50,121 @@ func FetchActionTest(ctx *context.Context) {
|
|||||||
ctx.JSONRedirect("")
|
ctx.JSONRedirect("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareMockData(ctx *context.Context) {
|
func prepareMockDataGiteaUI(ctx *context.Context) {
|
||||||
if ctx.Req.URL.Path == "/devtest/gitea-ui" {
|
now := time.Now()
|
||||||
now := time.Now()
|
ctx.Data["TimeNow"] = now
|
||||||
ctx.Data["TimeNow"] = now
|
ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
|
||||||
ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
|
ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
|
||||||
ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
|
ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute)
|
||||||
ctx.Data["TimePast2m"] = now.Add(-2 * time.Minute)
|
ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute)
|
||||||
ctx.Data["TimeFuture2m"] = now.Add(2 * time.Minute)
|
ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second)
|
||||||
ctx.Data["TimePast1y"] = now.Add(-1 * 366 * 86400 * time.Second)
|
ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
|
||||||
ctx.Data["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
|
}
|
||||||
|
|
||||||
|
func prepareMockDataBadgeCommitSign(ctx *context.Context) {
|
||||||
|
var commits []*asymkey.SignCommit
|
||||||
|
mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}})
|
||||||
|
mockUser := mockUsers[0]
|
||||||
|
commits = append(commits, &asymkey.SignCommit{
|
||||||
|
Verification: &asymkey.CommitVerification{},
|
||||||
|
UserCommit: &user_model.UserCommit{
|
||||||
|
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
commits = append(commits, &asymkey.SignCommit{
|
||||||
|
Verification: &asymkey.CommitVerification{
|
||||||
|
Verified: true,
|
||||||
|
Reason: "name / key-id",
|
||||||
|
SigningUser: mockUser,
|
||||||
|
SigningKey: &asymkey.GPGKey{KeyID: "12345678"},
|
||||||
|
TrustStatus: "trusted",
|
||||||
|
},
|
||||||
|
UserCommit: &user_model.UserCommit{
|
||||||
|
User: mockUser,
|
||||||
|
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
commits = append(commits, &asymkey.SignCommit{
|
||||||
|
Verification: &asymkey.CommitVerification{
|
||||||
|
Verified: true,
|
||||||
|
Reason: "name / key-id",
|
||||||
|
SigningUser: mockUser,
|
||||||
|
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
|
||||||
|
TrustStatus: "untrusted",
|
||||||
|
},
|
||||||
|
UserCommit: &user_model.UserCommit{
|
||||||
|
User: mockUser,
|
||||||
|
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
commits = append(commits, &asymkey.SignCommit{
|
||||||
|
Verification: &asymkey.CommitVerification{
|
||||||
|
Verified: true,
|
||||||
|
Reason: "name / key-id",
|
||||||
|
SigningUser: mockUser,
|
||||||
|
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
|
||||||
|
TrustStatus: "other(unmatch)",
|
||||||
|
},
|
||||||
|
UserCommit: &user_model.UserCommit{
|
||||||
|
User: mockUser,
|
||||||
|
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
commits = append(commits, &asymkey.SignCommit{
|
||||||
|
Verification: &asymkey.CommitVerification{
|
||||||
|
Warning: true,
|
||||||
|
Reason: "gpg.error",
|
||||||
|
SigningEmail: "test@example.com",
|
||||||
|
},
|
||||||
|
UserCommit: &user_model.UserCommit{
|
||||||
|
User: mockUser,
|
||||||
|
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.Data["MockCommits"] = commits
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareMockDataBadgeActionsSvg(ctx *context.Context) {
|
||||||
|
fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",")
|
||||||
|
selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0])
|
||||||
|
var badges []badge.Badge
|
||||||
|
badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green"))
|
||||||
|
for r := rune(0); r < 256; r++ {
|
||||||
|
if unicode.IsPrint(r) {
|
||||||
|
s := strings.Repeat(string(r), 15)
|
||||||
|
badges = append(badges, badge.GenerateBadge(s, util.TruncateRunes(s, 7), "green"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Req.URL.Path == "/devtest/commit-sign-badge" {
|
var badgeSVGs []template.HTML
|
||||||
var commits []*asymkey.SignCommit
|
for i, b := range badges {
|
||||||
mockUsers, _ := db.Find[user_model.User](ctx, user_model.SearchUserOptions{ListOptions: db.ListOptions{PageSize: 1}})
|
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
|
||||||
mockUser := mockUsers[0]
|
b.FontFamily = selectedFontFamilyName
|
||||||
commits = append(commits, &asymkey.SignCommit{
|
h, err := ctx.RenderToHTML("shared/actions/runner_badge", map[string]any{"Badge": b})
|
||||||
Verification: &asymkey.CommitVerification{},
|
if err != nil {
|
||||||
UserCommit: &user_model.UserCommit{
|
ctx.ServerError("RenderToHTML", err)
|
||||||
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
return
|
||||||
},
|
}
|
||||||
})
|
badgeSVGs = append(badgeSVGs, h)
|
||||||
commits = append(commits, &asymkey.SignCommit{
|
}
|
||||||
Verification: &asymkey.CommitVerification{
|
ctx.Data["BadgeSVGs"] = badgeSVGs
|
||||||
Verified: true,
|
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
|
||||||
Reason: "name / key-id",
|
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
|
||||||
SigningUser: mockUser,
|
}
|
||||||
SigningKey: &asymkey.GPGKey{KeyID: "12345678"},
|
|
||||||
TrustStatus: "trusted",
|
|
||||||
},
|
|
||||||
UserCommit: &user_model.UserCommit{
|
|
||||||
User: mockUser,
|
|
||||||
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
commits = append(commits, &asymkey.SignCommit{
|
|
||||||
Verification: &asymkey.CommitVerification{
|
|
||||||
Verified: true,
|
|
||||||
Reason: "name / key-id",
|
|
||||||
SigningUser: mockUser,
|
|
||||||
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
|
|
||||||
TrustStatus: "untrusted",
|
|
||||||
},
|
|
||||||
UserCommit: &user_model.UserCommit{
|
|
||||||
User: mockUser,
|
|
||||||
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
commits = append(commits, &asymkey.SignCommit{
|
|
||||||
Verification: &asymkey.CommitVerification{
|
|
||||||
Verified: true,
|
|
||||||
Reason: "name / key-id",
|
|
||||||
SigningUser: mockUser,
|
|
||||||
SigningSSHKey: &asymkey.PublicKey{Fingerprint: "aa:bb:cc:dd:ee"},
|
|
||||||
TrustStatus: "other(unmatch)",
|
|
||||||
},
|
|
||||||
UserCommit: &user_model.UserCommit{
|
|
||||||
User: mockUser,
|
|
||||||
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
commits = append(commits, &asymkey.SignCommit{
|
|
||||||
Verification: &asymkey.CommitVerification{
|
|
||||||
Warning: true,
|
|
||||||
Reason: "gpg.error",
|
|
||||||
SigningEmail: "test@example.com",
|
|
||||||
},
|
|
||||||
UserCommit: &user_model.UserCommit{
|
|
||||||
User: mockUser,
|
|
||||||
Commit: &git.Commit{ID: git.Sha1ObjectFormat.EmptyObjectID()},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
ctx.Data["MockCommits"] = commits
|
func prepareMockData(ctx *context.Context) {
|
||||||
|
switch ctx.Req.URL.Path {
|
||||||
|
case "/devtest/gitea-ui":
|
||||||
|
prepareMockDataGiteaUI(ctx)
|
||||||
|
case "/devtest/badge-commit-sign":
|
||||||
|
prepareMockDataBadgeCommitSign(ctx)
|
||||||
|
case "/devtest/badge-actions-svg":
|
||||||
|
prepareMockDataBadgeActionsSvg(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Tmpl(ctx *context.Context) {
|
func TmplCommon(ctx *context.Context) {
|
||||||
prepareMockData(ctx)
|
prepareMockData(ctx)
|
||||||
if ctx.Req.Method == "POST" {
|
if ctx.Req.Method == "POST" {
|
||||||
_ = ctx.Req.ParseForm()
|
_ = ctx.Req.ParseForm()
|
||||||
|
|||||||
@ -4,26 +4,12 @@
|
|||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/web/repo"
|
"code.gitea.io/gitea/routers/web/repo"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
||||||
reqGitSignIn := func(ctx *context.Context) {
|
|
||||||
if !setting.Service.RequireSignInView {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// rely on the results of Contexter
|
|
||||||
if !ctx.IsSigned {
|
|
||||||
// TODO: support digit auth - which would be Authorization header with digit
|
|
||||||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`)
|
|
||||||
ctx.HTTPError(http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.Group("/{username}/{reponame}", func() {
|
m.Group("/{username}/{reponame}", func() {
|
||||||
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
||||||
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
|
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack)
|
||||||
@ -36,5 +22,5 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) {
|
|||||||
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
|
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject)
|
||||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
|
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile)
|
||||||
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
|
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile)
|
||||||
}, optSignInIgnoreCsrf, reqGitSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
}, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1640,7 +1640,7 @@ func registerRoutes(m *web.Router) {
|
|||||||
m.Group("/devtest", func() {
|
m.Group("/devtest", func() {
|
||||||
m.Any("", devtest.List)
|
m.Any("", devtest.List)
|
||||||
m.Any("/fetch-action-test", devtest.FetchActionTest)
|
m.Any("/fetch-action-test", devtest.FetchActionTest)
|
||||||
m.Any("/{sub}", devtest.Tmpl)
|
m.Any("/{sub}", devtest.TmplCommon)
|
||||||
m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
|
m.Get("/repo-action-view/{run}/{job}", devtest.MockActionsView)
|
||||||
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
|
m.Post("/actions-mock/runs/{run}/jobs/{job}", web.Bind(actions.ViewRequest{}), devtest.MockActionsRunsJobs)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -346,7 +346,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) {
|
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
|
||||||
if ctx.FormString("go-get") == "1" {
|
if ctx.FormString("go-get") == "1" {
|
||||||
EarlyResponseForGoGetMeta(ctx)
|
EarlyResponseForGoGetMeta(ctx)
|
||||||
return
|
return
|
||||||
|
|||||||
18
templates/devtest/badge-actions-svg.tmpl
Normal file
18
templates/devtest/badge-actions-svg.tmpl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{{template "devtest/devtest-header"}}
|
||||||
|
<div class="page-content devtest ui container">
|
||||||
|
<div>
|
||||||
|
<h1>Actions SVG</h1>
|
||||||
|
<form class="tw-my-3">
|
||||||
|
{{range $fontName := .BadgeFontFamilyNames}}
|
||||||
|
<label><input name="font" type="radio" value="{{$fontName}}" {{Iif (eq $.SelectedFontFamilyName $fontName) "checked"}}>{{$fontName}}</label>
|
||||||
|
{{end}}
|
||||||
|
<button>submit</button>
|
||||||
|
</form>
|
||||||
|
<div class="flex-text-block tw-flex-wrap">
|
||||||
|
{{range $badgeSVG := .BadgeSVGs}}
|
||||||
|
<div>{{$badgeSVG}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "devtest/devtest-footer"}}
|
||||||
@ -1,25 +1,22 @@
|
|||||||
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
{{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}}
|
||||||
|
|
||||||
<div class="ui container tw-max-w-full">
|
<div class="ui container tw-max-w-full">
|
||||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4 tw-gap-3">
|
<div class="flex-text-block tw-flex-wrap tw-mb-4">
|
||||||
<h2 class="tw-mb-0 tw-flex-1 tw-break-anywhere">{{.Project.Title}}</h2>
|
<h2 class="tw-mb-0">{{.Project.Title}}</h2>
|
||||||
<div class="project-toolbar-right">
|
<div class="tw-flex-1"></div>
|
||||||
<div class="ui secondary filter menu labels">
|
<div class="ui secondary menu tw-m-0">
|
||||||
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
{{$queryLink := QueryBuild "?" "labels" .SelectLabels "assignee" $.AssigneeID "archived_labels" (Iif $.ShowArchivedLabels "true")}}
|
||||||
|
{{template "repo/issue/filter_item_label" dict "Labels" .Labels "QueryLink" $queryLink "SupportArchivedLabel" true}}
|
||||||
{{template "repo/issue/filter_item_label" dict "Labels" .Labels "QueryLink" $queryLink "SupportArchivedLabel" true}}
|
{{template "repo/issue/filter_item_user_assign" dict
|
||||||
|
"QueryParamKey" "assignee"
|
||||||
{{template "repo/issue/filter_item_user_assign" dict
|
"QueryLink" $queryLink
|
||||||
"QueryParamKey" "assignee"
|
"UserSearchList" $.Assignees
|
||||||
"QueryLink" $queryLink
|
"SelectedUserId" $.AssigneeID
|
||||||
"UserSearchList" $.Assignees
|
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
||||||
"SelectedUserId" $.AssigneeID
|
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||||
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
||||||
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
}}
|
||||||
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
</div>
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{if $canWriteProject}}
|
{{if $canWriteProject}}
|
||||||
<div class="ui compact mini menu">
|
<div class="ui compact mini menu">
|
||||||
<a class="item" href="{{.Link}}/edit?redirect=project">
|
<a class="item" href="{{.Link}}/edit?redirect=project">
|
||||||
|
|||||||
@ -14,9 +14,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="list-header">
|
<div class="list-header flex-text-block">
|
||||||
{{template "repo/issue/navbar" .}}
|
|
||||||
{{template "repo/issue/search" .}}
|
{{template "repo/issue/search" .}}
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/labels">{{ctx.Locale.Tr "repo.labels"}}</a>
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/milestones">{{ctx.Locale.Tr "repo.milestones"}}</a>
|
||||||
{{if not .Repository.IsArchived}}
|
{{if not .Repository.IsArchived}}
|
||||||
{{if .PageIsIssueList}}
|
{{if .PageIsIssueList}}
|
||||||
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
<a class="ui small primary button issue-list-new" href="{{.RepoLink}}/issues/new{{if .NewIssueChooseTemplate}}/choose{{end}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||||
|
|||||||
@ -2,8 +2,9 @@
|
|||||||
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
|
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
|
||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container padded">
|
<div class="ui container padded">
|
||||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
|
<div class="flex-text-block tw-justify-end tw-mb-4">
|
||||||
{{template "repo/issue/navbar" .}}
|
<a class="ui small button" href="{{.RepoLink}}/labels">{{ctx.Locale.Tr "repo.labels"}}</a>
|
||||||
|
<a class="ui small button" href="{{.RepoLink}}/milestones">{{ctx.Locale.Tr "repo.milestones"}}</a>
|
||||||
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
<a class="ui small primary button" href="{{.RepoLink}}/issues/new/choose?project={{.Project.ID}}">{{ctx.Locale.Tr "repo.issues.new"}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,25 +1,27 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{.Badge.Width}}" height="18"
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{.Badge.Width}}" height="20"
|
||||||
role="img" aria-label="{{.Badge.Label.Text}}: {{.Badge.Message.Text}}">
|
role="img" aria-label="{{.Badge.Label.Text}}: {{.Badge.Message.Text}}">
|
||||||
<title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title>
|
<title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title>
|
||||||
<linearGradient id="s" x2="0" y2="100%">
|
<linearGradient id="{{.Badge.IDPrefix}}s" x2="0" y2="100%">
|
||||||
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
|
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||||
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
|
<stop offset="1" stop-opacity=".1" />
|
||||||
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
|
|
||||||
<stop offset="1" stop-color="#000" stop-opacity=".5" />
|
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<clipPath id="r">
|
<clipPath id="{{.Badge.IDPrefix}}r">
|
||||||
<rect width="{{.Badge.Width}}" height="18" rx="4" fill="#fff" />
|
<rect width="{{.Badge.Width}}" height="20" rx="3" fill="#fff" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<g clip-path="url(#r)">
|
<g clip-path="url(#{{.Badge.IDPrefix}}r)">
|
||||||
<rect width="{{.Badge.Label.Width}}" height="18" fill="#555" />
|
<rect width="{{.Badge.Label.Width}}" height="20" fill="#555" />
|
||||||
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="18" fill="{{.Badge.Color}}" />
|
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="20" fill="{{.Badge.Color}}" />
|
||||||
<rect width="{{.Badge.Width}}" height="18" fill="url(#s)" />
|
<rect width="{{.Badge.Width}}" height="20" fill="url(#{{.Badge.IDPrefix}}s)" />
|
||||||
|
</g>
|
||||||
|
<g fill="#fff" text-anchor="middle" font-family="{{.Badge.FontFamily}}"
|
||||||
|
text-rendering="geometricPrecision" font-size="{{.Badge.FontSize}}">
|
||||||
|
<text aria-hidden="true" x="{{.Badge.Label.X}}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)"
|
||||||
|
textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text>
|
||||||
|
<text x="{{.Badge.Label.X}}" y="140"
|
||||||
|
transform="scale(.1)" fill="#fff" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text>
|
||||||
|
<text aria-hidden="true" x="{{.Badge.Message.X}}" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)"
|
||||||
|
textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text>
|
||||||
|
<text x="{{.Badge.Message.X}}" y="140" transform="scale(.1)" fill="#fff"
|
||||||
|
textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text>
|
||||||
</g>
|
</g>
|
||||||
<g fill="#fff" text-anchor="middle" font-family="Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision"
|
|
||||||
font-size="{{.Badge.FontSize}}"><text aria-hidden="true" x="{{.Badge.Label.X}}" y="140" fill="#010101" fill-opacity=".3"
|
|
||||||
transform="scale(.1)" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text><text x="{{.Badge.Label.X}}" y="130"
|
|
||||||
transform="scale(.1)" fill="#fff" textLength="{{.Badge.Label.TextLength}}">{{.Badge.Label.Text}}</text><text aria-hidden="true"
|
|
||||||
x="{{.Badge.Message.X}}" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)"
|
|
||||||
textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text><text x="{{.Badge.Message.X}}" y="130" transform="scale(.1)"
|
|
||||||
fill="#fff" textLength="{{.Badge.Message.TextLength}}">{{.Badge.Message.Text}}</text></g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@ -31,7 +31,7 @@ func TestDownloadTaskLogs(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
treePath string
|
treePath string
|
||||||
fileContent string
|
fileContent string
|
||||||
outcome *mockTaskOutcome
|
outcome []*mockTaskOutcome
|
||||||
zstdEnabled bool
|
zstdEnabled bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -46,21 +46,44 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo job1 with zstd enabled
|
- run: echo job1 with zstd enabled
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo job2 with zstd enabled
|
||||||
`,
|
`,
|
||||||
outcome: &mockTaskOutcome{
|
outcome: []*mockTaskOutcome{
|
||||||
result: runnerv1.Result_RESULT_SUCCESS,
|
{
|
||||||
logRows: []*runnerv1.LogRow{
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
{
|
logRows: []*runnerv1.LogRow{
|
||||||
Time: timestamppb.New(now.Add(1 * time.Second)),
|
{
|
||||||
Content: " \U0001F433 docker create image",
|
Time: timestamppb.New(now.Add(1 * time.Second)),
|
||||||
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(2 * time.Second)),
|
||||||
|
Content: "job1 zstd enabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(3 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
Time: timestamppb.New(now.Add(2 * time.Second)),
|
{
|
||||||
Content: "job1 zstd enabled",
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
},
|
logRows: []*runnerv1.LogRow{
|
||||||
{
|
{
|
||||||
Time: timestamppb.New(now.Add(3 * time.Second)),
|
Time: timestamppb.New(now.Add(1 * time.Second)),
|
||||||
Content: "\U0001F3C1 Job succeeded",
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(2 * time.Second)),
|
||||||
|
Content: "job2 zstd enabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(3 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -78,21 +101,44 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo job1 with zstd disabled
|
- run: echo job1 with zstd disabled
|
||||||
|
job2:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo job2 with zstd disabled
|
||||||
`,
|
`,
|
||||||
outcome: &mockTaskOutcome{
|
outcome: []*mockTaskOutcome{
|
||||||
result: runnerv1.Result_RESULT_SUCCESS,
|
{
|
||||||
logRows: []*runnerv1.LogRow{
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
{
|
logRows: []*runnerv1.LogRow{
|
||||||
Time: timestamppb.New(now.Add(4 * time.Second)),
|
{
|
||||||
Content: " \U0001F433 docker create image",
|
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||||
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(5 * time.Second)),
|
||||||
|
Content: "job1 zstd disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
Time: timestamppb.New(now.Add(5 * time.Second)),
|
{
|
||||||
Content: "job1 zstd disabled",
|
result: runnerv1.Result_RESULT_SUCCESS,
|
||||||
},
|
logRows: []*runnerv1.LogRow{
|
||||||
{
|
{
|
||||||
Time: timestamppb.New(now.Add(6 * time.Second)),
|
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||||
Content: "\U0001F3C1 Job succeeded",
|
Content: " \U0001F433 docker create image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(5 * time.Second)),
|
||||||
|
Content: "job2 zstd disabled",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||||
|
Content: "\U0001F3C1 Job succeeded",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -124,54 +170,55 @@ jobs:
|
|||||||
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent)
|
opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent)
|
||||||
createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts)
|
createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts)
|
||||||
|
|
||||||
// fetch and execute task
|
// fetch and execute tasks
|
||||||
task := runner.fetchTask(t)
|
for jobIndex, outcome := range tc.outcome {
|
||||||
runner.execTask(t, task, tc.outcome)
|
task := runner.fetchTask(t)
|
||||||
|
runner.execTask(t, task, outcome)
|
||||||
|
|
||||||
// check whether the log file exists
|
// check whether the log file exists
|
||||||
logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id)
|
logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id)
|
||||||
if setting.Actions.LogCompression.IsZstd() {
|
if setting.Actions.LogCompression.IsZstd() {
|
||||||
logFileName += ".zst"
|
logFileName += ".zst"
|
||||||
|
}
|
||||||
|
_, err := storage.Actions.Stat(logFileName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// download task logs and check content
|
||||||
|
runIndex := task.Context.GetFields()["run_number"].GetStringValue()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/%d/logs", user2.Name, repo.Name, runIndex, jobIndex)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
||||||
|
assert.Len(t, logTextLines, len(outcome.logRows))
|
||||||
|
for idx, lr := range outcome.logRows {
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content),
|
||||||
|
logTextLines[idx],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
runID, _ := strconv.ParseInt(task.Context.GetFields()["run_id"].GetStringValue(), 10, 64)
|
||||||
|
|
||||||
|
jobs, err := actions_model.GetRunJobsByRunID(t.Context(), runID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, jobs, len(tc.outcome))
|
||||||
|
jobID := jobs[jobIndex].ID
|
||||||
|
|
||||||
|
// download task logs from API and check content
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, jobID)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
||||||
|
assert.Len(t, logTextLines, len(outcome.logRows))
|
||||||
|
for idx, lr := range outcome.logRows {
|
||||||
|
assert.Equal(
|
||||||
|
t,
|
||||||
|
fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content),
|
||||||
|
logTextLines[idx],
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_, err := storage.Actions.Stat(logFileName)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// download task logs and check content
|
|
||||||
runIndex := task.Context.GetFields()["run_number"].GetStringValue()
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%s/jobs/0/logs", user2.Name, repo.Name, runIndex)).
|
|
||||||
AddTokenAuth(token)
|
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
|
||||||
logTextLines := strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
|
||||||
assert.Len(t, logTextLines, len(tc.outcome.logRows))
|
|
||||||
for idx, lr := range tc.outcome.logRows {
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content),
|
|
||||||
logTextLines[idx],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
runID, _ := strconv.ParseInt(task.Context.GetFields()["run_id"].GetStringValue(), 10, 64)
|
|
||||||
|
|
||||||
jobs, err := actions_model.GetRunJobsByRunID(t.Context(), runID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, jobs, 1)
|
|
||||||
jobID := jobs[0].ID
|
|
||||||
|
|
||||||
// download task logs from API and check content
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/actions/jobs/%d/logs", user2.Name, repo.Name, jobID)).
|
|
||||||
AddTokenAuth(token)
|
|
||||||
resp = MakeRequest(t, req, http.StatusOK)
|
|
||||||
logTextLines = strings.Split(strings.TrimSpace(resp.Body.String()), "\n")
|
|
||||||
assert.Len(t, logTextLines, len(tc.outcome.logRows))
|
|
||||||
for idx, lr := range tc.outcome.logRows {
|
|
||||||
assert.Equal(
|
|
||||||
t,
|
|
||||||
fmt.Sprintf("%s %s", lr.Time.AsTime().Format("2006-01-02T15:04:05.0000000Z07:00"), lr.Content),
|
|
||||||
logTextLines[idx],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
resetFunc()
|
resetFunc()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -16,7 +18,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGitSmartHTTP(t *testing.T) {
|
func TestGitSmartHTTP(t *testing.T) {
|
||||||
onGiteaRun(t, testGitSmartHTTP)
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
|
testGitSmartHTTP(t, u)
|
||||||
|
testRenamedRepoRedirect(t)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
||||||
@ -73,3 +78,21 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testRenamedRepoRedirect(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.Service.RequireSignInView, true)()
|
||||||
|
|
||||||
|
// git client requires to get a 301 redirect response before 401 unauthorized response
|
||||||
|
req := NewRequest(t, "GET", "/user2/oldrepo1/info/refs")
|
||||||
|
resp := MakeRequest(t, req, http.StatusMovedPermanently)
|
||||||
|
redirect := resp.Header().Get("Location")
|
||||||
|
assert.Equal(t, "/user2/repo1/info/refs", redirect)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", redirect)
|
||||||
|
resp = MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
assert.Equal(t, "Unauthorized\n", resp.Body.String())
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", redirect).AddBasicAuth("user2")
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "65f1bf27bc3bf70f64657658635e66094edbcb4d\trefs/tags/v1.1")
|
||||||
|
}
|
||||||
|
|||||||
@ -989,14 +989,7 @@ table th[data-sortt-desc] .svg {
|
|||||||
box-shadow: 0 0 0 1px var(--color-secondary) inset;
|
box-shadow: 0 0 0 1px var(--color-secondary) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji {
|
/* for "image" emojis like ":git:" ":gitea:" and ":github:" (see CUSTOM_EMOJIS config option) */
|
||||||
font-size: 1.25em;
|
|
||||||
line-height: var(--line-height-default);
|
|
||||||
font-style: normal !important;
|
|
||||||
font-weight: var(--font-weight-normal) !important;
|
|
||||||
vertical-align: -0.075em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji img {
|
.emoji img {
|
||||||
border-width: 0 !important;
|
border-width: 0 !important;
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
@ -1155,6 +1148,11 @@ table th[data-sortt-desc] .svg {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-text-block > .ui.button,
|
||||||
|
.flex-text-inline > .ui.button {
|
||||||
|
margin: 0; /* fomantic buttons have default margin, when we use them in a flex container with gap, we do not need these margins */
|
||||||
|
}
|
||||||
|
|
||||||
/* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content
|
/* to override Fomantic's default display: block for ".menu .item", and use a slightly larger gap for menu item content
|
||||||
the "!important" is necessary to override Fomantic UI menu item styles, meanwhile we should keep the "hidden" items still hidden */
|
the "!important" is necessary to override Fomantic UI menu item styles, meanwhile we should keep the "hidden" items still hidden */
|
||||||
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
|
.ui.dropdown .menu.flex-items-menu > .item:not(.hidden, .filtered, .tw-hidden) {
|
||||||
|
|||||||
@ -8,18 +8,6 @@
|
|||||||
margin: 0 0.5em;
|
margin: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-toolbar-right .filter.menu {
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.project-toolbar-right .dropdown .menu {
|
|
||||||
left: auto !important;
|
|
||||||
right: auto !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-column {
|
.project-column {
|
||||||
background-color: var(--color-project-column-bg) !important;
|
background-color: var(--color-project-column-bg) !important;
|
||||||
border: 1px solid var(--color-secondary) !important;
|
border: 1px solid var(--color-secondary) !important;
|
||||||
|
|||||||
@ -336,11 +336,6 @@
|
|||||||
padding-right: 28px;
|
padding-right: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup .emoji {
|
|
||||||
max-width: none;
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markup span.frame {
|
.markup span.frame {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user