mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-19 21:28:33 +02:00
Merge branch 'main' into lunny/reafctor_setdefaultbranch
This commit is contained in:
commit
95e2224841
@ -378,6 +378,7 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(315, "Add Ephemeral to ActionRunner", v1_24.AddEphemeralToActionRunner),
|
||||
newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables),
|
||||
newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard),
|
||||
newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
17
models/migrations/v1_24/v318.go
Normal file
17
models/migrations/v1_24/v318.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_24 //nolint
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
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{})
|
||||
}
|
@ -25,7 +25,8 @@ type Permission struct {
|
||||
units []*repo_model.RepoUnit
|
||||
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.
|
||||
@ -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.
|
||||
// It doesn't count the "everyone access mode".
|
||||
// It doesn't count the "public(anonymous/everyone) access mode".
|
||||
func (p *Permission) HasAnyUnitAccess() bool {
|
||||
for _, v := range p.unitsMode {
|
||||
if v >= perm_model.AccessModeRead {
|
||||
@ -49,7 +50,12 @@ func (p *Permission) HasAnyUnitAccess() bool {
|
||||
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 {
|
||||
if v >= perm_model.AccessModeRead {
|
||||
return true
|
||||
@ -73,14 +79,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
||||
if m, ok := p.unitsMode[unitType]; ok {
|
||||
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
|
||||
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 })
|
||||
return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone)
|
||||
}
|
||||
@ -171,27 +179,38 @@ func (p *Permission) LogString() string {
|
||||
format += "\n\tunitsMode[%-v]: %-v"
|
||||
args = append(args, key.LogString(), value.LogString())
|
||||
}
|
||||
format += "\n\tanonymousAccessMode: %-v"
|
||||
args = append(args, p.anonymousAccessMode)
|
||||
format += "\n\teveryoneAccessMode: %-v"
|
||||
args = append(args, p.everyoneAccessMode)
|
||||
format += "\n\t]>"
|
||||
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) {
|
||||
// apply public (anonymous) access permissions
|
||||
for _, u := range perm.units {
|
||||
applyPublicAccessPermission(u.Type, u.AnonymousAccessMode, &perm.anonymousAccessMode)
|
||||
}
|
||||
|
||||
if user == nil || user.ID <= 0 {
|
||||
// for anonymous access, it could be:
|
||||
// AccessMode is None or Read, units has repo units, unitModes is nil
|
||||
return
|
||||
}
|
||||
|
||||
// apply everyone access permissions
|
||||
// apply public (everyone) access permissions
|
||||
for _, u := range perm.units {
|
||||
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
|
||||
if perm.everyoneAccessMode == nil {
|
||||
perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode)
|
||||
}
|
||||
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
|
||||
}
|
||||
applyPublicAccessPermission(u.Type, u.EveryoneAccessMode, &perm.everyoneAccessMode)
|
||||
}
|
||||
|
||||
if perm.unitsMode == nil {
|
||||
@ -209,6 +228,11 @@ func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for t := range perm.anonymousAccessMode {
|
||||
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
||||
break
|
||||
}
|
||||
}
|
||||
for t := range perm.everyoneAccessMode {
|
||||
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
||||
break
|
||||
|
@ -22,14 +22,21 @@ func TestHasAnyUnitAccess(t *testing.T) {
|
||||
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||
}
|
||||
assert.False(t, perm.HasAnyUnitAccess())
|
||||
assert.False(t, perm.HasAnyUnitAccessOrEveryoneAccess())
|
||||
assert.False(t, perm.HasAnyUnitAccessOrPublicAccess())
|
||||
|
||||
perm = Permission{
|
||||
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||
everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead},
|
||||
}
|
||||
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{
|
||||
AccessMode: perm_model.AccessModeRead,
|
||||
@ -43,7 +50,7 @@ func TestHasAnyUnitAccess(t *testing.T) {
|
||||
assert.True(t, perm.HasAnyUnitAccess())
|
||||
}
|
||||
|
||||
func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
func TestApplyPublicAccessRepoPermission(t *testing.T) {
|
||||
perm := Permission{
|
||||
AccessMode: perm_model.AccessModeNone,
|
||||
units: []*repo_model.RepoUnit{
|
||||
@ -53,6 +60,15 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
finalProcessRepoUnitPermission(nil, &perm)
|
||||
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{
|
||||
AccessMode: perm_model.AccessModeNone,
|
||||
units: []*repo_model.RepoUnit{
|
||||
|
@ -42,12 +42,13 @@ func (err ErrUnitTypeNotExist) Unwrap() error {
|
||||
|
||||
// RepoUnit describes all units of a repository
|
||||
type RepoUnit struct { //revive:disable-line:exported
|
||||
ID int64
|
||||
RepoID int64 `xorm:"INDEX(s)"`
|
||||
Type unit.Type `xorm:"INDEX(s)"`
|
||||
Config convert.Conversion `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||
ID int64
|
||||
RepoID int64 `xorm:"INDEX(s)"`
|
||||
Type unit.Type `xorm:"INDEX(s)"`
|
||||
Config convert.Conversion `xorm:"TEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
|
||||
AnonymousAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||
EveryoneAccessMode perm.AccessMode `xorm:"NOT NULL DEFAULT 0"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -4,6 +4,9 @@
|
||||
package badge
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
)
|
||||
|
||||
@ -11,54 +14,35 @@ import (
|
||||
// We use 10x scale to calculate more precisely
|
||||
// Then scale down to normal size in tmpl file
|
||||
|
||||
type Label 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 {
|
||||
type Text struct {
|
||||
text string
|
||||
width int
|
||||
x int
|
||||
}
|
||||
|
||||
func (m Message) Text() string {
|
||||
return m.text
|
||||
func (t Text) Text() string {
|
||||
return t.text
|
||||
}
|
||||
|
||||
func (m Message) Width() int {
|
||||
return m.width
|
||||
func (t Text) Width() int {
|
||||
return t.width
|
||||
}
|
||||
|
||||
func (m Message) X() int {
|
||||
return m.x
|
||||
func (t Text) X() int {
|
||||
return t.x
|
||||
}
|
||||
|
||||
func (m Message) TextLength() int {
|
||||
return int(float64(m.width-defaultOffset) * 9.5)
|
||||
func (t Text) TextLength() int {
|
||||
return int(float64(t.width-defaultOffset) * 10)
|
||||
}
|
||||
|
||||
type Badge struct {
|
||||
Color string
|
||||
FontSize int
|
||||
Label Label
|
||||
Message Message
|
||||
IDPrefix string
|
||||
FontFamily string
|
||||
Color string
|
||||
FontSize int
|
||||
Label Text
|
||||
Message Text
|
||||
}
|
||||
|
||||
func (b Badge) Width() int {
|
||||
@ -66,10 +50,10 @@ func (b Badge) Width() int {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultOffset = 9
|
||||
defaultFontSize = 11
|
||||
DefaultColor = "#9f9f9f" // Grey
|
||||
defaultFontWidth = 7 // approximate speculation
|
||||
defaultOffset = 10
|
||||
defaultFontSize = 11
|
||||
DefaultColor = "#9f9f9f" // Grey
|
||||
DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif"
|
||||
)
|
||||
|
||||
var StatusColorMap = map[actions_model.Status]string{
|
||||
@ -85,20 +69,43 @@ var StatusColorMap = map[actions_model.Status]string{
|
||||
|
||||
// GenerateBadge generates badge with given template
|
||||
func GenerateBadge(label, message, color string) Badge {
|
||||
lw := defaultFontWidth*len(label) + defaultOffset
|
||||
mw := defaultFontWidth*len(message) + defaultOffset
|
||||
x := lw*10 + mw*5 - 10
|
||||
lw := calculateTextWidth(label) + defaultOffset
|
||||
mw := calculateTextWidth(message) + defaultOffset
|
||||
|
||||
lx := lw * 5
|
||||
mx := lw*10 + mw*5 - 10
|
||||
return Badge{
|
||||
Label: Label{
|
||||
FontFamily: DefaultFontFamily,
|
||||
Label: Text{
|
||||
text: label,
|
||||
width: lw,
|
||||
x: lx,
|
||||
},
|
||||
Message: Message{
|
||||
Message: Text{
|
||||
text: message,
|
||||
width: mw,
|
||||
x: x,
|
||||
x: mx,
|
||||
},
|
||||
FontSize: defaultFontSize * 10,
|
||||
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,
|
||||
}
|
||||
})
|
@ -14,25 +14,26 @@ type Batch struct {
|
||||
Writer WriteCloserError
|
||||
}
|
||||
|
||||
func (repo *Repository) NewBatch(ctx context.Context) (*Batch, error) {
|
||||
// NewBatch creates a new batch for the given repository, the Close must be invoked before release the batch
|
||||
func NewBatch(ctx context.Context, repoPath string) (*Batch, error) {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
|
||||
if err := ensureValidGitRepository(ctx, repoPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var batch Batch
|
||||
batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repo.Path)
|
||||
batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repoPath)
|
||||
return &batch, nil
|
||||
}
|
||||
|
||||
func (repo *Repository) NewBatchCheck(ctx context.Context) (*Batch, error) {
|
||||
func NewBatchCheck(ctx context.Context, repoPath string) (*Batch, error) {
|
||||
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
|
||||
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
|
||||
if err := ensureValidGitRepository(ctx, repoPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var check Batch
|
||||
check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repo.Path)
|
||||
check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repoPath)
|
||||
return &check, nil
|
||||
}
|
||||
|
||||
|
@ -350,9 +350,10 @@ func (c *Command) run(ctx context.Context, skip int, opts *RunOpts) error {
|
||||
// We need to check if the context is canceled by the program on Windows.
|
||||
// This is because Windows does not have signal checking when terminating the process.
|
||||
// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
|
||||
// `err.Error()` returns "exit status 1" when using the `git check-attr` command after the context is canceled.
|
||||
if runtime.GOOS == "windows" &&
|
||||
err != nil &&
|
||||
err.Error() == "" &&
|
||||
(err.Error() == "" || err.Error() == "exit status 1") &&
|
||||
cmd.ProcessState.ExitCode() == 1 &&
|
||||
ctx.Err() == context.Canceled {
|
||||
return ctx.Err()
|
||||
|
@ -67,7 +67,7 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
|
||||
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
|
||||
if repo.batch == nil {
|
||||
var err error
|
||||
repo.batch, err = repo.NewBatch(ctx)
|
||||
repo.batch, err = NewBatch(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -81,7 +81,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bu
|
||||
}
|
||||
|
||||
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
|
||||
tempBatch, err := repo.NewBatch(ctx)
|
||||
tempBatch, err := NewBatch(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -92,7 +92,7 @@ func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bu
|
||||
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
|
||||
if repo.check == nil {
|
||||
var err error
|
||||
repo.check, err = repo.NewBatchCheck(ctx)
|
||||
repo.check, err = NewBatchCheck(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -106,7 +106,7 @@ func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError
|
||||
}
|
||||
|
||||
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
|
||||
tempBatchCheck, err := repo.NewBatchCheck(ctx)
|
||||
tempBatchCheck, err := NewBatchCheck(ctx, repo.Path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/indexer"
|
||||
path_filter "code.gitea.io/gitea/modules/indexer/code/bleve/token/path"
|
||||
"code.gitea.io/gitea/modules/indexer/code/internal"
|
||||
@ -217,12 +216,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
|
||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
|
||||
if len(changes.Updates) > 0 {
|
||||
r, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
gitBatch, err := r.NewBatch(ctx)
|
||||
gitBatch, err := git.NewBatch(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/analyze"
|
||||
"code.gitea.io/gitea/modules/charset"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/indexer"
|
||||
"code.gitea.io/gitea/modules/indexer/code/internal"
|
||||
indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
|
||||
@ -209,12 +208,7 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) elasti
|
||||
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
|
||||
reqs := make([]elastic.BulkableRequest, 0)
|
||||
if len(changes.Updates) > 0 {
|
||||
r, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
batch, err := r.NewBatch(ctx)
|
||||
batch, err := git.NewBatch(ctx, repo.RepoPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.R
|
||||
if err = runJobs.LoadRepos(ctx); err != nil {
|
||||
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 DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex])
|
||||
|
@ -4,16 +4,21 @@
|
||||
package devtest
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
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/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@ -45,84 +50,121 @@ func FetchActionTest(ctx *context.Context) {
|
||||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
||||
func prepareMockData(ctx *context.Context) {
|
||||
if ctx.Req.URL.Path == "/devtest/gitea-ui" {
|
||||
now := time.Now()
|
||||
ctx.Data["TimeNow"] = now
|
||||
ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
|
||||
ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
|
||||
ctx.Data["TimePast2m"] = 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["TimeFuture1y"] = now.Add(1 * 366 * 86400 * time.Second)
|
||||
func prepareMockDataGiteaUI(ctx *context.Context) {
|
||||
now := time.Now()
|
||||
ctx.Data["TimeNow"] = now
|
||||
ctx.Data["TimePast5s"] = now.Add(-5 * time.Second)
|
||||
ctx.Data["TimeFuture5s"] = now.Add(5 * time.Second)
|
||||
ctx.Data["TimePast2m"] = 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["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 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()},
|
||||
},
|
||||
})
|
||||
var badgeSVGs []template.HTML
|
||||
for i, b := range badges {
|
||||
b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-"
|
||||
b.FontFamily = selectedFontFamilyName
|
||||
h, err := ctx.RenderToHTML("shared/actions/runner_badge", map[string]any{"Badge": b})
|
||||
if err != nil {
|
||||
ctx.ServerError("RenderToHTML", err)
|
||||
return
|
||||
}
|
||||
badgeSVGs = append(badgeSVGs, h)
|
||||
}
|
||||
ctx.Data["BadgeSVGs"] = badgeSVGs
|
||||
ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames
|
||||
ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName
|
||||
}
|
||||
|
||||
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)
|
||||
if ctx.Req.Method == "POST" {
|
||||
_ = ctx.Req.ParseForm()
|
||||
|
@ -4,26 +4,12 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
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.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack)
|
||||
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/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)
|
||||
}, optSignInIgnoreCsrf, reqGitSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
||||
}, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb())
|
||||
}
|
||||
|
@ -154,8 +154,8 @@ func createCommon(ctx *context.Context) {
|
||||
ctx.Data["Licenses"] = repo_module.Licenses
|
||||
ctx.Data["Readmes"] = repo_module.Readmes
|
||||
ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate
|
||||
ctx.Data["CanCreateRepo"] = ctx.Doer.CanCreateRepo()
|
||||
ctx.Data["MaxCreationLimit"] = ctx.Doer.MaxCreationLimit()
|
||||
ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepo()
|
||||
ctx.Data["MaxCreationLimitOfDoer"] = ctx.Doer.MaxCreationLimit()
|
||||
ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats
|
||||
ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1639,7 +1639,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Group("/devtest", func() {
|
||||
m.Any("", devtest.List)
|
||||
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.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
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() && !canWriteAsMaintainer(ctx) {
|
||||
if !ctx.Repo.Permission.HasAnyUnitAccessOrPublicAccess() && !canWriteAsMaintainer(ctx) {
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
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))}}
|
||||
|
||||
<div class="ui container tw-max-w-full">
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4 tw-gap-3">
|
||||
<h2 class="tw-mb-0 tw-flex-1 tw-break-anywhere">{{.Project.Title}}</h2>
|
||||
<div class="project-toolbar-right">
|
||||
<div class="ui secondary filter menu labels">
|
||||
{{$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_user_assign" dict
|
||||
"QueryParamKey" "assignee"
|
||||
"QueryLink" $queryLink
|
||||
"UserSearchList" $.Assignees
|
||||
"SelectedUserId" $.AssigneeID
|
||||
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
||||
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-text-block tw-flex-wrap tw-mb-4">
|
||||
<h2 class="tw-mb-0">{{.Project.Title}}</h2>
|
||||
<div class="tw-flex-1"></div>
|
||||
<div class="ui secondary menu tw-m-0">
|
||||
{{$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_user_assign" dict
|
||||
"QueryParamKey" "assignee"
|
||||
"QueryLink" $queryLink
|
||||
"UserSearchList" $.Assignees
|
||||
"SelectedUserId" $.AssigneeID
|
||||
"TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee")
|
||||
"TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee")
|
||||
"TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee")
|
||||
}}
|
||||
</div>
|
||||
{{if $canWriteProject}}
|
||||
<div class="ui compact mini menu">
|
||||
<a class="item" href="{{.Link}}/edit?redirect=project">
|
||||
|
@ -7,25 +7,21 @@
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
{{template "repo/create_helper" .}}
|
||||
|
||||
{{if not .CanCreateRepo}}
|
||||
<div class="ui negative message">
|
||||
<p>{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimit}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
<form class="ui form left-right-form new-repo-form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div id="create-repo-error-message" class="ui negative message tw-text-center tw-hidden"></div>
|
||||
<div class="inline required field {{if .Err_Owner}}error{{end}}">
|
||||
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
|
||||
<div class="ui selection owner dropdown">
|
||||
<input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required>
|
||||
<span class="text truncated-item-container" title="{{.ContextUser.Name}}">
|
||||
{{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}}
|
||||
<span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span>
|
||||
</span>
|
||||
<div class="ui selection dropdown" id="repo_owner_dropdown">
|
||||
<input type="hidden" name="uid" value="{{.ContextUser.ID}}">
|
||||
<span class="text truncated-item-name"></span>
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="menu">
|
||||
<div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}">
|
||||
<div class="item truncated-item-container" data-value="{{.SignedUser.ID}}" title="{{.SignedUser.Name}}"
|
||||
{{if not .CanCreateRepoInDoer}}
|
||||
data-create-repo-disallowed-prompt="{{ctx.Locale.TrN .MaxCreationLimit "repo.form.reach_limit_of_creation_1" "repo.form.reach_limit_of_creation_n" .MaxCreationLimitOfDoer}}"
|
||||
{{end}}
|
||||
>
|
||||
{{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}}
|
||||
<span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
|
||||
</div>
|
||||
@ -212,7 +208,7 @@
|
||||
<br>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui primary button{{if not .CanCreateRepo}} disabled{{end}}">
|
||||
<button class="ui primary button">
|
||||
{{ctx.Locale.Tr "repo.create_repo"}}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -14,9 +14,10 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="list-header">
|
||||
{{template "repo/issue/navbar" .}}
|
||||
<div class="list-header flex-text-block">
|
||||
{{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 .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>
|
||||
|
@ -2,8 +2,9 @@
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
|
||||
{{template "repo/header" .}}
|
||||
<div class="ui container padded">
|
||||
<div class="tw-flex tw-justify-between tw-items-center tw-mb-4">
|
||||
{{template "repo/issue/navbar" .}}
|
||||
<div class="flex-text-block tw-justify-end tw-mb-4">
|
||||
<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>
|
||||
</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}}">
|
||||
<title>{{.Badge.Label.Text}}: {{.Badge.Message.Text}}</title>
|
||||
<linearGradient id="s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".7" />
|
||||
<stop offset=".1" stop-color="#aaa" stop-opacity=".1" />
|
||||
<stop offset=".9" stop-color="#000" stop-opacity=".3" />
|
||||
<stop offset="1" stop-color="#000" stop-opacity=".5" />
|
||||
<linearGradient id="{{.Badge.IDPrefix}}s" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
|
||||
<stop offset="1" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<clipPath id="r">
|
||||
<rect width="{{.Badge.Width}}" height="18" rx="4" fill="#fff" />
|
||||
<clipPath id="{{.Badge.IDPrefix}}r">
|
||||
<rect width="{{.Badge.Width}}" height="20" rx="3" fill="#fff" />
|
||||
</clipPath>
|
||||
<g clip-path="url(#r)">
|
||||
<rect width="{{.Badge.Label.Width}}" height="18" fill="#555" />
|
||||
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="18" fill="{{.Badge.Color}}" />
|
||||
<rect width="{{.Badge.Width}}" height="18" fill="url(#s)" />
|
||||
<g clip-path="url(#{{.Badge.IDPrefix}}r)">
|
||||
<rect width="{{.Badge.Label.Width}}" height="20" fill="#555" />
|
||||
<rect x="{{.Badge.Label.Width}}" width="{{.Badge.Message.Width}}" height="20" fill="{{.Badge.Color}}" />
|
||||
<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 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>
|
||||
|
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 {
|
||||
treePath string
|
||||
fileContent string
|
||||
outcome *mockTaskOutcome
|
||||
outcome []*mockTaskOutcome
|
||||
zstdEnabled bool
|
||||
}{
|
||||
{
|
||||
@ -46,21 +46,44 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo job1 with zstd enabled
|
||||
job2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo job2 with zstd enabled
|
||||
`,
|
||||
outcome: &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
logRows: []*runnerv1.LogRow{
|
||||
{
|
||||
Time: timestamppb.New(now.Add(1 * time.Second)),
|
||||
Content: " \U0001F433 docker create image",
|
||||
outcome: []*mockTaskOutcome{
|
||||
{
|
||||
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(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",
|
||||
},
|
||||
{
|
||||
Time: timestamppb.New(now.Add(3 * time.Second)),
|
||||
Content: "\U0001F3C1 Job succeeded",
|
||||
},
|
||||
{
|
||||
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(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
|
||||
steps:
|
||||
- run: echo job1 with zstd disabled
|
||||
job2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo job2 with zstd disabled
|
||||
`,
|
||||
outcome: &mockTaskOutcome{
|
||||
result: runnerv1.Result_RESULT_SUCCESS,
|
||||
logRows: []*runnerv1.LogRow{
|
||||
{
|
||||
Time: timestamppb.New(now.Add(4 * time.Second)),
|
||||
Content: " \U0001F433 docker create image",
|
||||
outcome: []*mockTaskOutcome{
|
||||
{
|
||||
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(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",
|
||||
},
|
||||
{
|
||||
Time: timestamppb.New(now.Add(6 * time.Second)),
|
||||
Content: "\U0001F3C1 Job succeeded",
|
||||
},
|
||||
{
|
||||
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(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)
|
||||
createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts)
|
||||
|
||||
// fetch and execute task
|
||||
task := runner.fetchTask(t)
|
||||
runner.execTask(t, task, tc.outcome)
|
||||
// fetch and execute tasks
|
||||
for jobIndex, outcome := range tc.outcome {
|
||||
task := runner.fetchTask(t)
|
||||
runner.execTask(t, task, outcome)
|
||||
|
||||
// check whether the log file exists
|
||||
logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id)
|
||||
if setting.Actions.LogCompression.IsZstd() {
|
||||
logFileName += ".zst"
|
||||
// check whether the log file exists
|
||||
logFileName := fmt.Sprintf("%s/%02x/%d.log", repo.FullName(), task.Id%256, task.Id)
|
||||
if setting.Actions.LogCompression.IsZstd() {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -16,7 +18,10 @@ import (
|
||||
)
|
||||
|
||||
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) {
|
||||
@ -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")
|
||||
}
|
||||
|
@ -31,16 +31,16 @@ func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOw
|
||||
|
||||
// Step2: click the "Use this template" button
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
link, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/repo/create\"]").Attr("href")
|
||||
link, exists := htmlDoc.doc.Find(`a.ui.button[href^="/repo/create"]`).Attr("href")
|
||||
assert.True(t, exists, "The template has changed")
|
||||
req = NewRequest(t, "GET", link)
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
// Step3: fill the form of the create
|
||||
// Step3: fill the form on the "create" page
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
link, exists = htmlDoc.doc.Find("form.ui.form[action^=\"/repo/create\"]").Attr("action")
|
||||
link, exists = htmlDoc.doc.Find(`form.ui.form[action^="/repo/create"]`).Attr("action")
|
||||
assert.True(t, exists, "The template has changed")
|
||||
_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", generateOwner.ID)).Attr("data-value")
|
||||
_, exists = htmlDoc.doc.Find(fmt.Sprintf(`#repo_owner_dropdown .item[data-value="%d"]`, generateOwner.ID)).Attr("data-value")
|
||||
assert.True(t, exists, "Generate owner '%s' is not present in select box", generateOwnerName)
|
||||
req = NewRequestWithValues(t, "POST", link, map[string]string{
|
||||
"_csrf": htmlDoc.GetCSRF(),
|
||||
|
@ -989,14 +989,7 @@ table th[data-sortt-desc] .svg {
|
||||
box-shadow: 0 0 0 1px var(--color-secondary) inset;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
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;
|
||||
}
|
||||
|
||||
/* for "image" emojis like ":git:" ":gitea:" and ":github:" (see CUSTOM_EMOJIS config option) */
|
||||
.emoji img {
|
||||
border-width: 0 !important;
|
||||
margin: 0 !important;
|
||||
@ -1155,6 +1148,11 @@ table th[data-sortt-desc] .svg {
|
||||
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
|
||||
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) {
|
||||
|
@ -8,18 +8,6 @@
|
||||
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 {
|
||||
background-color: var(--color-project-column-bg) !important;
|
||||
border: 1px solid var(--color-secondary) !important;
|
||||
|
@ -336,11 +336,6 @@
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.markup .emoji {
|
||||
max-width: none;
|
||||
vertical-align: text-top;
|
||||
}
|
||||
|
||||
.markup span.frame {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {hideElem, showElem, toggleElem} from '../utils/dom.ts';
|
||||
import {hideElem, querySingleVisibleElem, showElem, toggleElem} from '../utils/dom.ts';
|
||||
import {htmlEscape} from 'escape-goat';
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {sanitizeRepoName} from './repo-common.ts';
|
||||
@ -6,7 +6,9 @@ import {sanitizeRepoName} from './repo-common.ts';
|
||||
const {appSubUrl} = window.config;
|
||||
|
||||
function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
||||
const inputRepoOwnerUid = form.querySelector<HTMLInputElement>('#uid');
|
||||
const elSubmitButton = querySingleVisibleElem<HTMLInputElement>(form, '.ui.primary.button');
|
||||
const elCreateRepoErrorMessage = form.querySelector('#create-repo-error-message');
|
||||
const elRepoOwnerDropdown = form.querySelector('#repo_owner_dropdown');
|
||||
const elRepoTemplateDropdown = form.querySelector<HTMLInputElement>('#repo_template_search');
|
||||
const inputRepoTemplate = form.querySelector<HTMLInputElement>('#repo_template');
|
||||
const elTemplateUnits = form.querySelector('#template_units');
|
||||
@ -19,11 +21,23 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
||||
inputRepoTemplate.addEventListener('change', checkTemplate);
|
||||
checkTemplate();
|
||||
|
||||
const $dropdown = fomanticQuery(elRepoTemplateDropdown);
|
||||
const $repoOwnerDropdown = fomanticQuery(elRepoOwnerDropdown);
|
||||
const $repoTemplateDropdown = fomanticQuery(elRepoTemplateDropdown);
|
||||
const onChangeOwner = function () {
|
||||
$dropdown.dropdown('setting', {
|
||||
const ownerId = $repoOwnerDropdown.dropdown('get value');
|
||||
const $ownerItem = $repoOwnerDropdown.dropdown('get item', ownerId);
|
||||
hideElem(elCreateRepoErrorMessage);
|
||||
elSubmitButton.disabled = false;
|
||||
if ($ownerItem?.length) {
|
||||
const elOwnerItem = $ownerItem[0];
|
||||
elCreateRepoErrorMessage.textContent = elOwnerItem.getAttribute('data-create-repo-disallowed-prompt') ?? '';
|
||||
const hasError = Boolean(elCreateRepoErrorMessage.textContent);
|
||||
toggleElem(elCreateRepoErrorMessage, hasError);
|
||||
elSubmitButton.disabled = hasError;
|
||||
}
|
||||
$repoTemplateDropdown.dropdown('setting', {
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${inputRepoOwnerUid.value}`,
|
||||
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${ownerId}`,
|
||||
onResponse(response: any) {
|
||||
const results = [];
|
||||
results.push({name: '', value: ''}); // empty item means not using template
|
||||
@ -33,14 +47,14 @@ function initRepoNewTemplateSearch(form: HTMLFormElement) {
|
||||
value: String(tmplRepo.repository.id),
|
||||
});
|
||||
}
|
||||
$dropdown.fomanticExt.onResponseKeepSelectedItem($dropdown, inputRepoTemplate.value);
|
||||
$repoTemplateDropdown.fomanticExt.onResponseKeepSelectedItem($repoTemplateDropdown, inputRepoTemplate.value);
|
||||
return {results};
|
||||
},
|
||||
cache: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
inputRepoOwnerUid.addEventListener('change', onChangeOwner);
|
||||
$repoOwnerDropdown.dropdown('setting', 'onChange', onChangeOwner);
|
||||
onChangeOwner();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user